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 时,

也许会意识到:

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

相关推荐
㳺三才人子2 小时前
初探 Flask
后端·python·flask·html
星栈独行2 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Peace2 小时前
【Nginx】
linux·运维·nginx
Java爱好狂.2 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易2 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶3 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl3 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel4 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记5 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ5 小时前
【无标题】
nginx