Nginx作为反向代理和API网关使用时,路由规则的正确性直接决定了请求能否到达预期的后端服务。一旦出现404、请求被转发到错误的服务、或者路径前缀丢失等问题,access_log和error_log往往是第一手排查资料。这篇文章围绕Nginx日志与路由规则匹配的关系,从日志格式配置、location匹配优先级、proxy_pass路径拼接规则以及常见故障特征几个角度,系统地讲清楚如何通过日志定位网关路由问题。

一、先配置好能看懂路由信息的日志格式
默认的access_log格式非常简略,只有基础的访问信息,缺少路由排查需要的字段。要分析路由匹配,第一步是自定义log_format,把与路由相关的变量打进去。常用的变量包括$host(请求的主机名)、$server_name(匹配到的server块名称)、$uri(重写后的URI)、$request_uri(原始请求URI)、$args(查询参数)以及$upstream_addr(实际转发的后端地址)。
一个适合网关路由排查的日志格式配置如下:
http {
log_format routing '$remote_addr - [$time_local] '
'"$request" host=$host server=$server_name '
'uri=$uri request_uri=$request_uri '
'upstream=$upstream_addr '
'status=$status upstream_status=$upstream_status '
'match_location=$match_location';
# 需要配合set指令记录匹配到了哪个location
access_log /var/log/nginx/access.log routing;
}
其中$match_location是自定义变量,需要在每个location块里通过set指令赋值,这样日志里就能直接看到请求命中的是哪条路由规则。这个技巧在location数量较多的网关配置中特别实用,省去了反复推断的麻烦:
location /api/user/ {
set $match_location "user_service";
proxy_pass http://user_backend;
}
location /api/order/ {
set $match_location "order_service";
proxy_pass http://order_backend;
}
另外,error_log的级别建议调整为info或debug。error_log不只记录错误,rewrite阶段的重写信息、upstream选择过程在info级别下都会有所体现,对排查路由问题帮助很大。注意debug级别日志量极大,只在问题复现期间临时开启,排查完及时关闭。
二、理解location匹配优先级,读懂日志里的命中结果
很多路由不命中的问题,根源是对location匹配优先级理解不到位。Nginx的location匹配并不是简单的从上到下顺序匹配,而是有一套明确的优先级规则:精确匹配(=)优先级最高,其次是前缀匹配中的^~(命中后不再检查正则),然后是正则匹配(~和~*,按配置文件中的出现顺序生效),最后才是普通前缀匹配。普通前缀匹配中,最长前缀优先。
这意味着一个请求URI可能同时符合多个location的匹配条件,但最终命中的那个未必是你以为的那个。举个例子:
location /api/ {
set $match_location "generic_api";
proxy_pass http://default_backend;
}
location ~ ^/api/user/ {
set $match_location "user_regex";
proxy_pass http://user_backend;
}
请求/api/user/list时,虽然普通前缀/api/也能匹配,但正则location优先级更高,实际命中的是user_regex。如果日志中$match_location显示的结果和预期不符,首先要检查的就是匹配方式前缀符号,再检查是否有多条规则存在优先级覆盖。这也是前面强调在日志里记录命中location的原因——肉眼分析配置很容易出错,日志是最可靠的证据。
还有一个容易踩的坑:正则location与proxy_pass带URI的写法不兼容。当location使用正则匹配时,proxy_pass后面不能带路径,否则Nginx启动时会直接报错。遇到这类问题,error_log中会有明确的配置错误提示,启动失败时先看error_log而不是盲目修改。
三、proxy_pass路径拼接与rewrite对日志的影响
路由排查中最让人困惑的一类问题,是后端收到的路径和请求路径不一致。这涉及proxy_pass的路径拼接规则。当proxy_pass不带URI(只有http://backend这种形式)时,完整的原始请求URI会被原样传递给后端;当proxy_pass带有URI部分时,匹配location的前缀会被替换为proxy_pass中的路径。
对比下面两段配置的实际效果:
# 写法一:proxy_pass不带URI,后端收到 /api/user/list
location /api/ {
proxy_pass http://user_backend;
}
# 写法二:proxy_pass带URI,后端收到 /list
location /api/user/ {
proxy_pass http://user_backend/;
}
# 写法三:带路径映射,后端收到 /v2/user/list
location /api/user/ {
proxy_pass http://user_backend/v2/user/;
}
排查时,access_log中的$uri反映的是Nginx内部的最终URI,$request_uri是客户端原始请求。如果两者不一致,说明中间经历了rewrite。要确认后端实际收到的路径,可以在后端服务打印访问日志,或者在Nginx侧记录$upstream_uri相关变量做对照。路径丢失或多出前缀,绝大多数情况都出在proxy_pass末尾那个不起眼的斜杠上。
rewrite指令也常和路由规则产生交互。rewrite ... last会重新走一遍location匹配,可能被另一条规则接住;rewrite ... break则停留在当前location继续处理。如果日志显示请求被"抢走"转到了别的location,多半是last导致的重新匹配。必要时可以在rewrite前后分别通过set记录变量,观察URI的变化链路。
四、典型故障的日志特征与排查思路
第一类典型问题是请求落到了默认server。当请求的Host头找不到对应的server_name时,Nginx会使用默认server处理。日志特征是$host与$server_name明显对不上,或者server块上没有显式标记default_server却接住了不该处理的流量。解决办法是检查server_name配置,或故意设置一个返回444的default_server兜底:
server {
listen 80 default_server;
server_name _;
# 444状态码表示直接关闭连接,不返回任何响应
return 444;
}
第二类是404但后端服务正常。此时要看$upstream_addr是否为空:如果为空,说明请求根本没有进入proxy_pass阶段,是Nginx本地location没匹配到静态资源或规则;如果不为空且$upstream_status也是404,则是后端返回的,多半是路径拼接问题。
第三类是间歇性的路由错误,通常与upstream负载均衡有关。日志中$upstream_addr可能同时出现多个地址(用逗号分隔),表示请求在某台后端失败后进行了重试,这属于proxy_next_upstream机制。间歇性问题要结合$upstream_status和$upstream_response_time分析,判断是路由配置问题还是后端健康问题。
总结一下排查流程:先看日志确认命中的server和location,再对照proxy_pass的拼接规则确认转发路径,必要时借助自定义变量和info级别的error_log追踪rewrite过程。把日志格式配置好、把location优先级吃透,网关路由问题基本都能在日志里找到答案。