一、结论先给
状态码不是"背下来"的,是用来定位故障层的。看到一个 5xx,第一件事是判断它出在链路的哪一跳:
| 状态码 | 含义 | 故障在哪 |
|---|---|---|
| 500 | 上游代码抛异常 | 应用本身(看应用日志) |
| 502 Bad Gateway | 网关收到了无效响应 | 上游没起来 / 连接被拒 / 提前断开 |
| 503 Service Unavailable | 服务不可用 | 上游主动拒绝、限流、摘除、维护中 |
| 504 Gateway Timeout | 网关等超时了 | 上游太慢,超过网关超时时间 |
| 499(nginx 私有) | 客户端主动断开 | 用户等不及关了页面,或客户端超时更短 |
一句话区分:502 是"对方不接/乱说",503 是"对方说它不行",504 是"对方一直不出声"。
👉 想查某个码的完整定义,用 HTTP 状态码速查(在线);不确定是网络还是服务的问题,先用 网络延迟测试 打一发。
二、状态码全表(按排障价值排序)
2xx 成功
| 码 | 名称 | 说明 |
|---|---|---|
| 200 | OK | 正常响应 |
| 201 | Created | 创建成功,RESTful POST 应返回它 + Location 头 |
| 202 | Accepted | 已接收、异步处理中(下单排队场景) |
| 204 | No Content | 成功但无响应体,DELETE 常用 |
| 206 | Partial Content | 断点续传/分片下载,Content-Range 配合 |
3xx 重定向(重点看 301/302/307/308)
| 码 | 是否保持请求方法 | 是否可缓存 | 用途 |
|---|---|---|---|
| 301 | 可能变成 GET | 是 | 永久迁移,SEO 权重转移 |
| 302 | 可能变成 GET | 否 | 临时跳转 |
| 303 | 强制变 GET | 否 | POST 后跳到结果页 |
| 307 | 保持原方法 | 否 | 临时跳转且要保持 POST 体 |
| 308 | 保持原方法 | 是 | 永久跳转且要保持 POST 体 |
最常见的坑:把 POST 接口做了 301/302 跳转,浏览器会把方法降级成 GET 并丢掉请求体,表现为"接口莫名其妙收不到参数"。API 重定向必须用 307/308。
| 码 | 说明 |
|---|---|
| 304 | Not Modified,协商缓存命中,配合 ETag/Last-Modified |
4xx 客户端问题
| 码 | 名称 | 排障要点 |
|---|---|---|
| 400 | Bad Request | 参数格式错、JSON 解析失败 |
| 401 | Unauthorized | 没认证/认证失败,应有 WWW-Authenticate 头 |
| 403 | Forbidden | 认证了但没权限(401 和 403 别混用) |
| 404 | Not Found | 路径错 / 静态资源没发版 |
| 405 | Method Not Allowed | 路径对但方法不对,响应要带 Allow 头 |
| 408 | Request Timeout | 服务端等请求体超时 |
| 409 | Conflict | 资源冲突(重复创建、版本冲突) |
| 413 | Payload Too Large | 上传超限,nginx 对应 client_max_body_size |
| 415 | Unsupported Media Type | Content-Type 不对 |
| 422 | Unprocessable Entity | 语义校验失败(参数对但业务不通过) |
| 429 | Too Many Requests | 限流,应返回 Retry-After |
| 431 | Request Header Fields Too Large | Cookie 太大,nginx 调 large_client_header_buffers |
5xx 服务端问题
| 码 | 名称 | 说明 |
|---|---|---|
| 500 | Internal Server Error | 应用未捕获异常 |
| 501 | Not Implemented | 方法未实现 |
| 502 | Bad Gateway | 上游响应无效 |
| 503 | Service Unavailable | 不可用/维护/限流 |
| 504 | Gateway Timeout | 上游超时 |
| 505 | HTTP Version Not Supported | 协议版本不支持 |
nginx 私有码(只在 nginx 日志里能看到,客户端收不到)
| 码 | 含义 | 典型原因 |
|---|---|---|
| 499 | 客户端在 nginx 返回前断开 | 前端超时更短、用户关页面、移动端切后台 |
| 444 | nginx 主动断连不响应 | 常配在恶意扫描拦截上 |
| 495/496 | 客户端证书错误/未提供 | 双向 TLS 场景 |
| 497 | HTTP 请求发到了 HTTPS 端口 | 需要 error_page 497 跳转 |
499 是很重要但常被忽略的信号:如果你的接口 499 占比超过 1%,通常说明"用户实际感受到的响应时间"比你监控的服务端耗时长得多——要么是真的慢,要么是客户端超时设得太激进。
三、排障流程:从域名到应用逐层剥
DNS 解析 → TCP 连接 → TLS 握手 → 请求头/体上传 → 网关转发上游 → 上游处理 → 响应回传
对应工具和现象:
| 层 | 检查命令 | 失败现象 |
|---|---|---|
| DNS | dig +short example.com、nslookup |
解析到错 IP 或空 |
| TCP | curl -o /dev/null -w '%{time_connect}\n' https://x |
连接超时、Connection refused |
| TLS | curl -Iv https://x 2>&1 | grep -i tls、openssl s_client -connect x:443 |
证书过期、SNI 不匹配 |
| 网关 | curl -sS -D- -o /dev/null https://x |
502/504 |
| 上游 | 应用日志、ss -lntp | grep :8080 |
上游没监听、连接池满 |
| 回传 | curl -w '%{time_total}\n' |
499、慢 |
一个通用的耗时分解命令,一次看清时间花在哪:
curl -o /dev/null -sS -w 'DNS:%{time_namelookup}s 连接:%{time_connect}s TLS:%{time_appconnect}s 首包:%{time_starttransfer}s 总计:%{time_total}s 码:%{http_code}\n' https://example.com/
看哪一段突然变大就知道卡在哪:time_namelookup 大是 DNS,time_connect 大是网络/防火墙,time_appconnect 大是 TLS,time_starttransfer 大是服务端处理慢。
四、502 的定位与处理
502 的本质:nginx 连上游没连上,或连上了但上游没返回合法的 HTTP 响应就断了。
排查顺序:
# 1. 上游还活着吗
ss -lntp | grep 8080
systemctl status your-app
# 2. nginx 能不能直连上游(在 nginx 机器上测,别在自己电脑上测)
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/health
# 3. 看 nginx 错误日志,关键行是 connect() failed
tail -100 /var/log/nginx/error.log | grep -iE "connect|upstream|failed"
典型错误文本与含义:
| 日志关键字 | 根因 |
|---|---|
connect() failed (111: Connection refused) |
上游进程没监听 |
connect() failed (113: No route to host) |
防火墙/安全组拦了 |
upstream prematurely closed connection |
上游处理中崩了/被 OOM kill |
no live upstreams while connecting to upstream |
所有上游都被标记为不可用 |
upstream sent too big header |
上游响应头太大,调 proxy_buffers |
OOM 是"偶发 502"的高频原因,查一下:
dmesg -T | grep -i "out of memory" | tail -20
journalctl -k | grep -i oom | tail -20
五、504 的定位与处理
504 是等超时,意味着上游是活的,只是太慢。核心是超时参数链路上每一跳都要比前一跳长,否则前一跳先放弃,就变成 499 或无响应。
nginx 关键超时(放在 location 或 server 里):
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s; # 与上游建连超时,内网建议 3-5s
proxy_send_timeout 60s; # 发请求给上游的超时
proxy_read_timeout 60s; # 等上游响应的超时,慢接口调到 120s+
proxy_http_version 1.1; # 上游 keepalive 必需
proxy_set_header Connection "";
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 2; # 只重试 1 次,避免雪崩
}
PHP-FPM 场景还要多改几个:
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_read_timeout 120s;
fastcgi_send_timeout 120s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
同时 PHP 自己也有超时,三者必须对齐(php.ini 的 max_execution_time ≥ request_terminate_timeout ≥ nginx 的 read timeout):
max_execution_time = 120
request_terminate_timeout = 120
超时链必须单调递增:客户端(如 30s)> 网关(60s)> 上游(120s)?不对——正确顺序是外层的要更大,否则外层先断,用户看到的是无响应而不是明确错误。推荐:客户端 60s ≥ nginx 60s ≥ 应用自身 55s,让应用先优雅返回。
六、用日志统计状态码,快速发现异常
不要靠"感觉网站有问题",用数字说话。
# 状态码分布(nginx 默认日志格式,$status 是第 9 个字段)
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head
# 最近 5 分钟 5xx 数量
awk -v d="$(date -d '5 min ago' '+%d/%b/%Y:%H:%M:%S')" '$4 > "["d {if ($9>=500) c++} END {print c+0}' /var/log/nginx/access.log
# 5xx 里最慢的 10 个请求(日志加了 $request_time 时,$request_time 是最后一个字段)
awk '$9>=500 {print $NF, $7}' /var/log/nginx/access.log | sort -rn | head -10
# 502 集中在哪些 URL
awk '$9==502 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10
建议把 nginx 日志格式加上耗时字段,事后排障会轻松很多:
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time urt=$upstream_response_time uaddr=$upstream_addr';
access_log /var/log/nginx/access.log main_ext;
有了 upstream_response_time,一眼就能区分"是 nginx 慢还是上游慢":
# 上游耗时超过 3 秒的请求
awk -F'urt=' '{split($2,a," "); if (a[1]+0 > 3) print}' /var/log/nginx/access.log | head
👉 日志文件往往几千行起步,肉眼翻不动。把关键片段粘进 日志分析工具,它会自动统计级别分布、命中错误规则的行号、异常 Top 和错误高发时段,直接给结论。接口要复现可以用 在线 HTTP 请求工具 发一发。
七、常见误区
- 把 502 当 504 修:502 去查进程活着没,504 才去调超时。方向错了会白调半天参数。
- 重试次数开太大:
proxy_next_upstream_tries 3在高负载时会把流量放大 3 倍,直接压垮上游。重试 1 次足够。 - 只调 nginx 不调应用:nginx 超时调到 300s,应用 30s 就 self-kill,用户照样 502/504。
- 忽略 499:499 多说明用户体验已经差了,只是错误没进你的 5xx 监控。
- 401/403 混用:没登录返回 401,登录了没权限返回 403。写反了前端没法区分该跳登录页还是提示无权限。
- 把业务错误全返回 200:
{"code":500,"msg":"失败"}配 200 状态码,监控和网关全都失效,出问题查不到。
八、几条纪律
- 状态码是监控的第一数据源,按码分桶告警,别只看总量 QPS。
- 5xx 里单独盯 502/504,各自配不同告警和不同负责人。
- 超时链在设计阶段就定死并写进文档:客户端 > 网关 > 上游。
- 上线新接口第一件事是确认它有正确的 2xx/4xx 码,而不是一律 200。
- 日志必须带
request_time和upstream_response_time,否则事后无法复盘。
九、延伸阅读
👉 相关在线工具:HTTP 状态码速查 · 网络延迟测试 · 在线 HTTP 请求 · 日志分析,免登录、纯前端、数据不上传。