nginx 配置长跑(下):全局变量、调试思路与可观测性

关键词:nginx / 内置变量 / <math xmlns="http://www.w3.org/1998/Math/MathML"> r e m o t e a d d r / remote_addr / </math>remoteaddr/request_uri / 日志 / 调试 / 可观测性 / 运维排障

引言:当你只剩下一行 access.log 可以看

线上出问题的时候,nginx 往往是离用户最近、离应用最远的一层。

如果这时候:

  • 应用日志还没写到;
  • APM 没有覆盖到入口层;
  • 监控图上只看到一条平躺的 5xx 曲线;

你最后能抓得住的,可能就只有 nginx 的访问日志和那一堆 $ 开头的内置变量

很多人知道 $remote_addr$request_uri 这些变量的存在,

但很少系统性地用它们来:

  • 快速定位「是谁」发起的请求;
  • 看清楚「请求到底长什么样」;
  • 在日志里还原「请求在各种反代 / 跳转中经历了什么」。

这一篇,我们不再讲更多配置技巧,而是聚焦三件事:

  • nginx 的核心内置变量到底有哪些值得记;
  • 如何用这些变量把「请求上下文」写进日志;
  • 在真实排障场景里,如何靠这些变量快速判断问题出在入口、网关还是应用。

一、nginx 内置变量:把「请求元信息」变成可用数据

nginx 内置了大量 $ 开头的变量,大多是从 HTTP 头、连接信息或内部状态里抽出来的。

你可以简单理解成:「一个请求从外面到里面,沿途所有关键节点的快照」。

1. 实战中最常用、值得背下来的那一批

以下这些基本属于「日常调试必备」级别(均已在实践中验证):

  • $remote_addr:客户端 IP 地址;
  • $remote_port:客户端源端口;
  • $http_host:请求头里的 Host(等价于 request.getHeader('Host'));
  • $http_originOrigin 请求头,跨域、前端来源排查时非常有用;
  • $http_refererReferer 请求头,可以看请求从哪个页面发出;
  • $request_uri:原始请求 URI,包含 query,但不含主机名,例如 /foo/bar?a=1&b=2
  • $uri / $document_uri:当前内部处理用的 URI,不含 query;
  • $args / $query_string:URL 查询参数;
  • $server_name:命中的 server_name
  • $server_addr / $server_port:当前处理请求的 nginx 地址和端口;
  • $schemehttphttps

几个容易混淆的点:

  • $request_uri vs $uri

    • $request_uri = 用户原样请求路径 + 查询参数(除非被 rewrite ... redirect/permanent 改写);
    • $uri / $document_uri = 当前请求在 nginx 内部的 URI,可能被内部 rewrite 改过。
  • $http_origin vs $http_referer

    • $http_origin:调用方的域名 (如 https://www.example.com),更适合做 CORS 判断;
    • $http_referer:不含 hash 的完整 URL(如 https://www.example.com/page?a=1),适合做行为分析。

一个简单的经验是:如果你在某个变量名前加上 $http_

大概率就是在拿对应的请求头,比如 $http_user_agent$http_cookie 等。

2. 来自网络但也值得顺手一记的一批

以下是常见文档里提到、但容易被忽略的一些变量(不一一定要背,知道有就行):

  • $content_length:请求头中的 Content-Length
  • $content_type:请求头中的 Content-Type
  • $http_user_agent:User-Agent;
  • $http_cookie:Cookie;
  • $limit_rate:可以用来限制某个连接的传输速率;
  • $request_method:请求方法(GET / POST / PUT / DELETE ...);
  • $request_filename:由 root/alias + URI 拼出来的实际文件路径;
  • $server_protocolHTTP/1.0HTTP/1.1 等。

这些变量的价值,在于你可以按需拉取它们,拼成一条足够信息密度的日志


二、让 access.log 真正「说人话」:定制日志格式

默认的 access.log 格式通常长这样:

nginx 复制代码
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                '$status $body_bytes_sent "$http_referer" '
                '"$http_user_agent" "$http_x_forwarded_for"';

access_log logs/access.log main;

对排障来说,很多时候你需要的是:

  • 调用来源(Origin / Referer);
  • 请求最终命中的 URI;
  • 被转发到哪台后端;
  • 某些业务 Header(如 traceId、userId)。

你完全可以自定义一个更「工程化」的日志格式,例如:

nginx 复制代码
log_format  traceable_main
    '$remote_addr:$remote_port '
    '[$time_local] '
    '"$request" '
    'status=$status '
    'bytes=$body_bytes_sent '
    'host="$host" '
    'origin="$http_origin" '
    'referer="$http_referer" '
    'ua="$http_user_agent" '
    'req_uri="$request_uri" '
    'uri="$uri" '
    'upstream_addr="$upstream_addr" '
    'trace_id="$http_x_trace_id"';

access_log  logs/access.log traceable_main;

这样一条日志就能回答很多问题:

  • 用户来自哪个 IP、哪个 Origin;
  • 实际命中了哪个 uri(被 rewrite 过没有);
  • 请求被打到了哪个后端实例($upstream_addr);
  • traceId 是什么,方便跟应用日志串起来。

和其在监控平台上一脸懵逼,不如多给 access.log 加几个变量,

让每一条日志都变成「带上下文的证言」。


三、排障实战:变量 + 日志,快速缩小问题范围

说几个非常日常的排障问题,看看变量如何帮你「定点打击」。

场景一:用户说「偶发 502」,但后端压根没看到请求

现象:

  • 客户端日志看到 502;
  • 后端应用和网关没有对应 trace;
  • 只有 nginx 层有 502。

可以在 access.log 里重点看:

  • $upstream_addr:有没有真正转发到后端?
  • $upstream_status:后端返回的状态码(如果有);
  • $request_time / $upstream_response_time:请求在哪一段耗时。

示例日志格式调整:

nginx 复制代码
log_format with_upstream
    '$remote_addr - [$time_local] '
    '"$request" status=$status '
    'req_time=$request_time '
    'upstream="$upstream_addr" '
    'upstream_status="$upstream_status" '
    'upstream_time="$upstream_response_time"';

如果你看到:

  • status=502
  • upstream=""(空);
  • 或者 upstream 指向某些特定 IP/端口,upstream_status 是 502/504;

就可以很快判断:

  • 要么是 upstream 配置不对 / 服务不通;
  • 要么是某个后端实例挂了。

场景二:跨域问题,前端说「我没跨域」,后端说「我没看到请求」

这时候 $http_origin$http_referer 就很有用。

你可以这样先加一条临时日志:

nginx 复制代码
log_format cors_debug
    '$remote_addr [$time_local] '
    'method=$request_method '
    'host="$host" '
    'origin="$http_origin" '
    'referer="$http_referer" '
    'uri="$request_uri" '
    'status=$status';

access_log logs/cors_debug.log cors_debug;

然后专门抓一段时间访问:

  • 看看 $http_origin 到底是什么(很多时候是写错了 schema / 子域名);
  • OPTIONS 请求到底有没有打到这台 nginx / 这个 location 上;
  • 是 nginx 直接回了 4xx,还是 upstream 拒绝的。

场景三:怀疑某个用户脚本 / 爬虫把你打挂了

这时候组合的变量可能是:

  • $remote_addr + $remote_port
  • $http_user_agent
  • $http_referer
  • $request_uri

你可以:

  • 先用 grep 把某个可疑 IP 或 UA 的访问拉出来;
  • 观察它的行为模式(URI、频率、是否遍历所有路径);
  • 再考虑在 nginx 上做限流或封禁。

这里的关键始终是:在请求刚刚进入系统的时候,就捕获足够多的上下文信息。


四、用变量做一点「轻量逻辑」:按来源做差异化处理

虽然不推荐在 nginx 里写复杂业务逻辑,但基于变量做一点轻量分流还是很常见的。

1. 按域名 / Origin 做简单白名单

例如只允许某些域的前端调用某个接口:

nginx 复制代码
location /api/internal {
    if ($http_origin !~* ^https?://(www\.trusted\.com|admin\.trusted\.com)$) {
        return 403;
    }

    proxy_pass http://127.0.0.1:9000;
}

这里的关键就是 $http_origin 和正则的一起使用。

2. 按 UA 做简单灰度 / 降级

在真·紧急情况(背锅现场)下,也有人会用 nginx 做一点 UA 级别的降级:

nginx 复制代码
location /feature-heavy {
    if ($http_user_agent ~* 'MSIE|Trident') {
        # 老浏览器直接给降级页
        return 200 '您的浏览器版本过低,建议使用现代浏览器访问本功能';
    }

    proxy_pass http://127.0.0.1:9001;
}

这类逻辑不建议长期存在,但在「一小时内必须止血」的场景下,变量 + 简单判断会非常好用。


五、从变量到可观测性:让 nginx 不再是「黑盒」

如果把三篇「nginx 配置长跑」放在一起看,会发现一个演进路径:

  • 上篇是「路由规则」:请求到底会命中哪条 location
  • 中篇是「流量入口」:域名、TLS、转发、跨域、泛域名这些入口层职责;
  • 下篇就是「可观测性」:怎么知道这一层到底干了什么

内置变量是 nginx 给你的「观察窗口」:

  • 你可以用它们定制 access.log,让每条日志都带上关键上下文;
  • 可以在关键路径上输出必要的 debug 信息,快速缩小问题范围;
  • 可以用简单的条件判断,对不同来源、不同 UA 做轻量的路由差异化。

真正成熟的 nginx 使用方式,不是「背一堆指令」,

而是让它变成:
既能稳稳扛住流量,又能在出问题时,给你足够多的线索。


尾声:从会配 nginx,到敢把 nginx 当「工程设施」

回顾这三篇:

  • 你应该已经能比较自信地回答:某个路径最终会命中哪条 location,会不会被正则截胡;
  • 你大概也能设计一套比较干净的入口层:域名归一、HTTPS、反向代理、CORS、泛域名;
  • 最后,你有能力为 nginx 加上一层可观测性:变量、日志、简单的调试逻辑。

会写 nginx 配置,只是把系统「搭起来」;

敢把 nginx 当成工程设施来设计,才算是真正把它「用起来」。

当你下次看到一行 $http_origin$request_uri 时,

也许会意识到:

它不是一个随手抄来的变量,而是你和复杂系统对话的一只探针

相关推荐
+VX:Fegn08955 分钟前
计算机毕业设计|基于springboot + vue英语学习系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
步步为营DotNet7 分钟前
深度剖析ASP.NET Core Middleware:构建高效请求处理管道的关键
后端·asp.net
唐叔在学习30 分钟前
Pyinstaller进阶之构建管理大杀器-SPEC文件
后端·python·程序员
BIBI204930 分钟前
Nginx 反向代理完全入门:从一个真实场景开始
nginx·部署·配置·问题解决·服务器运维·web 开发·web 服务器
伯明翰java33 分钟前
【无标题】springboot项目yml中使用中文注释报错的解决方法
java·spring boot·后端
ihgry34 分钟前
java并发编程(juc理论篇)
后端
码界奇点43 分钟前
基于Spring Boot和Vue.js的视频点播管理系统设计与实现
java·vue.js·spring boot·后端·spring·毕业设计·源代码管理
程序员根根44 分钟前
MySQL 事务全解析:从 ACID 特性到实战落地(部门 - 员工场景)
数据库·后端
回家路上绕了弯1 小时前
分布式事务本地消息表详解:中小团队的低侵入落地方案
分布式·后端
武子康1 小时前
大数据-196 scikit-learn KNN 实战:KNeighborsClassifier、kneighbors 与学习曲线选最优 案例1红酒 案例2乳腺
大数据·后端·机器学习