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

也许会意识到:

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

相关推荐
程序员小假44 分钟前
我们来说一说 Redis IO 多路复用模型
java·后端
okseekw1 小时前
一篇吃透函数式编程:Lambda表达式与方法引用
java·后端
Lear1 小时前
【SQL】联表查询全面指南:掌握JOIN的艺术与科学
后端
油丶酸萝卜别吃1 小时前
在springboot项目中怎么发送请求,设置参数,获取另外一个服务上的数据
java·spring boot·后端
7哥♡ۣۖᝰꫛꫀꪝۣℋ1 小时前
SpringBoot 配置⽂件
java·spring boot·后端
jiayong231 小时前
Spring Bean 生命周期详解
java·后端·spring
猎人everest1 小时前
Django Rest Framework (DRF) 核心知识体系梳理与深度讲解
后端·python·django
9号达人1 小时前
大家天天说的'银弹'到底是个啥?看完这篇你就明白了
前端·后端·程序员
无限进步_1 小时前
C语言文件操作函数解析
c语言·开发语言·数据库·c++·后端·visual studio