Nginx 中 root 指令不支持直接变量,须用 map 在 http 块预映射租户路径(如按子域名或 header),再在 server 中引用 $tenant_root 作为 root;或用正则 location + alias 拼接路径,严格校验变量防路径穿越。

在 Nginx 中,root 指令本身不支持变量(如 $subdomain 或 $tenant_id),直接写成 root /var/www/$tenant_id; 会导致启动失败或行为异常。但可以通过组合 alias、map、try_files 和请求上下文变量(如 Host、URI、header)来安全实现多租户静态文件隔离。
用 map 提前映射租户路径
这是最稳定、推荐的方式:先根据 Host 或 URI 前缀,用 map 指令将租户标识映射为物理路径,再在 server 块中引用该变量作为 root。
示例(按子域名区分租户):
# 在 http 块顶层定义
map $host $tenant_root {
default "/var/www/default";
~^(?<tenant>[^.]+)\.example\.com$ "/var/www/tenants/$tenant";
}
<p>server {
listen 80;
server_name ~^(?<tenant>[^.]+).example.com$ example.com;</tenant></p>
<pre class="brush:php;toolbar:false;"># 使用 map 得到的变量作为 root
root $tenant_root;
location / {
try_files $uri $uri/ =404;
}
}
-
map必须在http块中定义,不能在server或location内 - 正则捕获组(如
(?)需配合[^.]+) ~^使用,确保只匹配子域名 - 路径必须真实存在且 Nginx 进程有读取权限,否则 403 或 404
用 alias + 变量拼接路径(仅限 location 内)
如果租户信息来自 URI 路径(如 /t/abc/static/logo.png),可用 alias 配合正则捕获和变量构造路径。注意:alias 会替换整个匹配部分,不是追加。
示例(租户 ID 在路径第一段):
location ~ ^/t/(?<tenant>[a-zA-Z0-9_-]+)/static/(.*)$ {
alias /var/www/tenants/$tenant/static/$2;
try_files $uri =404;
}
</tenant>-
alias后路径末尾不加斜杠,且$2必须包含完整子路径(含文件名) - 避免使用
root+ 变量,Nginx 不允许root包含未解析变量 - 建议加
try_files防止目录遍历(如../../../etc/passwd)
通过 header 或 cookie 识别租户(需谨慎)
若租户由自定义 header(如 X-Tenant-ID)传递,可结合 map 和 auth_request 或 Lua 模块增强校验,但纯 Nginx 不建议直接信任 header 构造路径(有安全隐患)。
基础方案(仅作示意,生产环境需配合鉴权):
map $http_x_tenant_id $safe_tenant {
~^[a-zA-Z0-9_-]{3,16}$ $http_x_tenant_id;
default "invalid";
}
<p>server {
...
location /static/ {
root /var/www/tenants/$safe_tenant;
try_files $uri =404;
}
}</p>- 必须严格校验变量值(长度、字符集),防止路径穿越或注入
- 更安全的做法是用
auth_request将租户验证交给后端,再通过响应 header 注入可信变量 - 不推荐直接依赖 cookie 或未签名 header 构造文件路径
关键注意事项
无论采用哪种方式,以下几点直接影响安全与稳定性:
-
路径白名单优先:租户目录应限定在统一父目录下(如
/var/www/tenants/),禁止向上跳转 -
权限最小化:每个租户子目录归属独立用户或设为
nginx:nginx且权限为750 -
缓存与日志隔离:为不同租户配置独立
access_log路径,必要时启用expires按租户差异化设置 -
拒绝敏感文件访问:在 location 中显式 deny
.htaccess、.env、config.php等











