技术指南

Nginx 403、404 与 502:沿请求路径定位故障

沿监听、虚拟主机、location、文件系统与上游逐层定位 Nginx 403、404、502 和重定向循环。

Nginx 返回 403、404 或 502 时,状态码只是请求走到哪一层的线索,不是根因。先固定域名、解析地址、协议、端口、Host、路径和发生时间,再沿监听端口、虚拟主机、location、文件系统或上游逐层取证;否则一次看似正确的本机请求可能落进完全不同的 server block。

把一次请求写成可复现证据

浏览器隐藏了 DNS、重定向和代理细节。先用 curl 记录实际目标与响应链;已知源站地址时,`--resolve` 会在不改公共 DNS 的情况下保留 HTTP Host 和 TLS SNI。源站若只允许 CDN 地址段,直连失败并不能单独证明应用故障。

替换为受控目标;输出可能包含内部地址、cookie 或认证头,分享前先脱敏
NAME=www.example.com
ORIGIN_IP=192.0.2.10
PATH_TO_TEST=/health

curl --silent --show-error --verbose --head \
  --connect-timeout 5 \
  --resolve "${NAME}:443:${ORIGIN_IP}" \
  "https://${NAME}${PATH_TO_TEST}" 2>&1 | tee nginx-request.txt
观察已经证明优先检查
连接失败或 TLS 失败尚未取得 HTTP 状态DNS、路由、防火墙、监听、证书与 SNI
404某个 HTTP 处理器认为资源不存在命中的 server/location、root/alias、try_files 或上游路由
403请求被策略拒绝或资源无法按当前上下文提供access/deny、目录 index、文件遍历权限、安全模块
502网关未得到可用的上游响应上游地址、监听、socket 权限、协议、超时与应用日志
重复 301/302/307/308多个跳转规则未收敛每一跳的 Location、scheme 与代理头信任边界

先核对监听者和有效配置

`nginx -t` 不只检查语法,也会尝试打开配置引用的文件;`nginx -T` 在此基础上输出展开后的配置。后者比只搜索 `/etc/nginx/sites-enabled` 更可靠,因为包布局、include 和启动参数可能不同。有效配置可能包含内部主机名或自定义头,保存和分享时要按敏感材料处理。

先记录版本、监听地址、服务启动参数和配置测试结果;妥善保管有效配置副本
nginx -v
systemctl status nginx --no-pager
sudo ss -lntp | grep nginx

sudo nginx -t
sudo nginx -T > nginx-effective.conf 2> nginx-config-test.log
systemctl cat nginx.service

确认请求命中了哪个 server 和 location

Nginx 先按目标地址与端口选择候选 server,再用 Host 匹配 `server_name`;没有匹配时会进入该监听端口的 default server。location 选择随后发生。直接请求 `127.0.0.1` 会改变 Host,因此不能代表真实域名路径。

分别验证本机 HTTP 与保留 TLS 名称的 HTTPS;不要用 -k 掩盖证书错误
curl --silent --show-error --head \
  -H 'Host: www.example.com' \
  http://127.0.0.1/health

curl --silent --show-error --head \
  --resolve www.example.com:443:127.0.0.1 \
  https://www.example.com/health

临时诊断时可以在目标 server 增加一个不含秘密的响应头或独立 access log 来证明命中路径,但不要把 `$document_root`、内部地址或文件路径暴露给公网客户端。验证后删除诊断标记。

把访问日志与错误日志对齐到同一秒

access log 说明请求、状态和耗时,error log 说明 Nginx 在相同时间附近遇到的具体失败。不要只看文件最后 100 行;高流量主机上应按 UTC 时间、Host、URI、状态和 request ID 缩小窗口,并同时查看 systemd journal 与应用日志。

按实际 log_format 调整字段;先确认日志路径来自有效配置
START='2026-08-15 21:00:00 UTC'
END='2026-08-15 21:05:00 UTC'

sudo journalctl -u nginx \
  --since "$START" --until "$END" --no-pager
sudo awk '$9 == 403 || $9 == 404 || $9 == 502 {print}' \
  /var/log/nginx/access.log | tail -n 50
sudo tail -n 100 /var/log/nginx/error.log

404:验证 URI 到文件或应用路由的映射

`root` 通常把完整 URI 追加到根目录;`alias` 用 location 匹配部分替换文件路径。`try_files` 会按 root/alias 构造候选路径,并在都不存在时内部跳转或返回指定状态。因此“文件存在”仍不足以证明当前请求会找到它。

  • 从 `nginx -T` 找到实际 listen、server_name 和匹配 location,不以文件名猜配置。
  • 把请求 URI 按 root 或 alias 语义映射到精确文件,检查路径中的大小写、尾斜杠和部署版本。
  • 用 `namei -l` 检查每一级目录,再以 worker 用户执行只读测试;不要先递归 chown。
  • SPA fallback 只应把前端路由送到真实 index;API、静态资源和拼写错误不应被无条件改写成 200。
  • 若 Nginx 把请求代理给应用,404 也可能来自上游;用 upstream status 和应用日志确认归属。
将 worker 用户和精确文件替换为有效配置中的值;退出码 0 才表示可读
sudo nginx -T 2>&1 | less
namei -l /srv/www/example/current/assets/app.js
sudo -u www-data test -r \
  /srv/www/example/current/assets/app.js
echo $?

403:先区分策略、目录索引和权限

403 可能来自 `deny`/认证策略,也可能是目录没有 index 且 autoindex 关闭,或 worker 无法遍历路径。error log 中的 `directory index ... is forbidden`、`permission denied` 与访问规则拒绝指向不同修复。不要把 403 统一处理成 `chmod 777`。

  • 若是目录请求,确认是否本应有 index 文件;不需要目录列表时保持 autoindex 关闭。
  • 检查 `allow`/`deny`、auth_request、limit_except 和包含文件,确认真实客户端地址是否经过可信代理还原。
  • 检查每一级目录的执行权限与目标文件读权限,只修复证据指向的 owner/group/mode。
  • SELinux 或 AppArmor 启用时查看对应审计日志与文件标签;不要全局关闭强制访问控制来验证猜测。
  • 修复后用目标 worker 身份重测,并确认没有把配置、备份或密钥目录变为可读。

502:从 Nginx 主机直连精确上游

先从有效配置取得 `proxy_pass`、`fastcgi_pass` 或 Unix socket,再在 Nginx 所在网络命名空间直接测试。`Connection refused` 说明该地址当时主动拒绝连接;超时、名称解析失败、TLS 验证失败和上游返回无效响应需要不同处理。先修可达性和协议,不要把超时调大当成通用修复。

按实际上游类型选择端口或 socket 检查;不要假设服务 unit 名
sudo ss -lntp | grep ':3000'
curl --fail --silent --show-error \
  --connect-timeout 2 http://127.0.0.1:3000/health

systemctl status example-app --no-pager
sudo journalctl -u example-app \
  --since '10 minutes ago' --no-pager
namei -l /run/example-app/app.sock

容器或独立 network namespace 中的 `127.0.0.1` 只指向该命名空间自身。若上游偶发慢响应,关联 `$upstream_connect_time`、`$upstream_header_time` 和应用指标,区分建连、首字节与应用处理阶段,再决定容量、超时或代码修复。

重定向循环:逐跳记录 Location

curl 达到上限会返回 47;保留每一跳的状态和 Location
curl --silent --show-error --head --location \
  --max-redirs 10 \
  --resolve www.example.com:443:192.0.2.10 \
  https://www.example.com/

常见循环来自边缘代理、Nginx 与应用对 HTTP/HTTPS 或规范域名的判断不一致。只信任来自明确代理地址的 forwarded headers,并让一个层负责规范跳转。不要为了停止循环而关闭 TLS 验证或永久绕过访问控制。

把变更做成可回滚事务

  • 记录故障样本与基线健康检查,备份将修改的精确文件并保留权限。
  • 只改证据指向的一层;执行 `nginx -t`,失败则不 reload。
  • 使用发行版服务管理方式 reload;Nginx 会尝试新配置,无法应用时继续使用旧配置。
  • 从本机和外部受控网络重复原失败请求、正常路径、TLS 与上游健康检查。
  • 观察错误率、延迟和日志;验收失败时恢复备份,再次 `nginx -t`、reload 和验证。
  • 删除诊断头、临时放行与测试文件,把命令、UTC 时间、结果和回滚写入事件记录。

本次隔离演练验证了什么

VPScope 使用 Ubuntu Nginx 1.24.0 在 `127.0.0.1:18080` 启动独立 prefix,未读取或修改生产配置。合成路由依次返回健康 200、缺失资源 404、无 index 目录 403、上游未监听时 502,以及自循环 302;curl 在三次跳转后以状态 47 停止。

随后只在 `127.0.0.1:18081` 启动合成上游,同一代理请求恢复为 200。access log 保留了逐次状态,error log 分别记录 `directory index ... is forbidden` 与 `connect() failed (111: Connection refused)`。演练结束后两个端口关闭,独立 Nginx 与上游进程停止;生产 80/443、systemd unit、证书和防火墙均未改变。

关闭事件前的验收清单

  • 失败请求的 scheme、Host、port、URI、解析地址、UTC 时间和响应链可复现。
  • 监听进程、Nginx 版本、`nginx -t` 与 `nginx -T` 的结论已记录。
  • 状态由正确 server/location 产生,并已区分 Nginx、文件系统与上游应用。
  • 修复只改变被证据指向的配置、权限或服务;没有递归放权、关闭 TLS 验证或全局停用安全模块。
  • reload 后原失败样本和关键正常路径均通过,错误率、延迟和日志无新异常。
  • 回滚经过验证,临时材料已清理,并安排发行版 Nginx 安全更新与下次演练。

返回知识库