变量

$uri 与 $request_uri 的关键差异、$host 的三级回退、动态变量族与求值时机。

作者 David更新于 第 8 篇(共 21 篇)

变量是 nginx 配置里唯一的「数据」。用错变量是仅次于继承与 location 的第三大错误源。

$uri vs $request_uri

最重要的一对,官方定义:

$uri — current URI in request, normalized. The value of $uri may change during request processing, e.g. when doing internal redirects, or when using index files.

$request_uri — full original request URI (with arguments)

$uri$request_uri
是否含查询串否是
是否归一化是(解码 %XX、合并 //、消解 ./..)否,原样
处理中是否变化会变(内部重定向、index)不变

对请求 GET /a//b/../c.html?x=1:

$uri          = /a/c.html          归一化,无参数
$request_uri  = /a//b/../c.html?x=1  原始,含参数

重定向时该用哪个?

return 301 https://$host$request_uri;   # ✅ 保留查询参数与原始形式
return 301 https://$host$uri;           # ❌ 丢掉 ?x=1

HTTP→HTTPS 跳转必须用 $request_uri,否则所有带查询参数的请求都会丢参数。这是极高频的误配。

反过来,做路径判断时要用 $uri——它已归一化,不会被 //、%2e%2e 这类编码绕过。用 $request_uri 做安全判断存在绕过风险。

$document_uri 是 $uri 的别名,二者完全相同。

$host 的三级回退

官方定义写得很精确:

in this order of precedence: host name from the request line, or host name from the "Host" request header field, or the server name matching a request

1. 请求行里的主机名(absolute-form 请求,见 HTTP/1.1 请求行)
2. Host 请求头
3. 匹配到的 server_name

关键推论:$host 永远有值(最差回退到 server_name),而 $http_host 是原样的 Host 头,可能为空。

变量来源可能为空是否含端口
$host三级回退,小写否否
$http_hostHost 头原样是是(如果客户端发了)
$server_name匹配到的 server_name否否

$host 会转小写并去掉端口,$http_host 不会。做重定向时用 $host 更安全。

动态变量族

三个「前缀 + 名字」的变量族,可以取任意字段:

形式取什么例
$http_名字请求头$http_user_agent、$http_x_forwarded_for
$sent_http_名字响应头$sent_http_content_type
$arg_名字查询参数$arg_page(对应 ?page=2)
$cookie_名字Cookie$cookie_session

转换规则:字段名转小写,- 换成 _。X-Forwarded-For → $http_x_forwarded_for。

另有 $upstream_http_名字(上游响应头)等,见代理章节。

常用变量速查

请求

变量官方定义
$request_methodrequest method, usually GET or POST
$schemerequest scheme, http or https
$httpson if connection operates in SSL mode, or an empty string otherwise
$args / $query_stringarguments in the request line(两者相同)
$is_args? if a request line has arguments, or an empty string otherwise
$remote_addrclient address
$remote_portclient port
$remote_useruser name supplied with the Basic authentication

注意 $https 在非 TLS 时是空串而不是 off——判断时应写 if ($https = "") 或直接用 $scheme。

$is_args 专门配合拼接使用:

rewrite ^ /new$is_args$args;   # 有参数才带 ?

路径

变量官方定义
$request_filenamefile path for the current request, based on the root or alias directives, and the request URI
$document_rootroot or alias directive's value for the current request
$realpath_rootabsolute pathname […] with all symbolic links resolved

$realpath_root 与 $document_root 的区别就是符号链接是否解析。给 PHP-FPM 传 DOCUMENT_ROOT 时,用哪个取决于应用是否依赖真实路径(OPcache 在符号链接部署下常需要 $realpath_root)。

服务端与响应

变量官方定义
$server_namename of the server which accepted a request
$server_portport of the server which accepted a request
$hostnamehost name(机器主机名,不是请求的)
$statusresponse status(1.3.2, 1.2.2 起)
$body_bytes_sentnumber of bytes sent to a client, not counting the response header
$request_timerequest processing time in seconds with a milliseconds resolution(1.3.9, 1.2.6 起); time elapsed since the first bytes were read from the client

$hostname 是运行 nginx 那台机器的主机名,与请求内容无关。想要请求的主机名用 $host。这两个名字太像,容易写错。

$request_time 的计时起点是读到客户端第一个字节——包含了读请求体的时间。所以慢客户端上传大文件会让这个值很大,但那不是服务端慢。区分服务端耗时要看 $upstream_response_time(见代理章节)。

变量的求值时机

nginx 变量是惰性求值的:用到才算,且多数会缓存本次请求的结果。

两个实际影响:

一、$uri 在内部重定向后会变。 try_files、index、rewrite 都会改写 $uri。所以日志里记的 $uri 可能不是客户端请求的那个——日志要记原始路径应该用 $request_uri。

二、map 定义的变量在首次使用时才计算。 见 map 与 geo。

自定义变量

set 指令(属于 ngx_http_rewrite_module):

set $my_var "value";
set $combined "$host$request_uri";

注意它的执行时机受 rewrite 阶段影响,详见 rewrite 与 if。

一个综合例子

# HTTP → HTTPS:必须用 $request_uri
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;

    # 按查询参数分流
    location /search {
        if ($arg_debug = "1") {
            return 403;
        }
        proxy_pass http://backend;
    }

    # 传给上游的标准头
    location /api/ {
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://backend;
    }
}

$proxy_add_x_forwarded_for 来自 proxy 模块——它是「原有 X-Forwarded-For 头 + $remote_addr」的拼接,正是逐跳追加的实现。

下一篇:proxy 反向代理。

本页目录