Nginx 日志分析与 4xx/5xx/超时故障排查实战
来源:运维派 | 发布日期:2026-06-13
Nginx 是绝大多数 Web 业务的入口。日志字段非常规范,定位问题就是"按图索骥"——搞清楚日志字段含义、状态码语义、超时参数,80% 的故障在日志里直接可见。
一、access_log 字段详解
完整字段说明(17 个字段)
推荐生产日志格式:
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time" '
'req_id=$request_id '
'fwd="$http_x_forwarded_for" scheme="$scheme" '
'host="$host" uri="$request_uri"';
| 字段 | 含义 | 正常范围 | 异常判断 |
|---|---|---|---|
$remote_addr |
客户端 IP(反代后配合 $http_x_forwarded_for 看真实 IP) |
— | 突变的 IP 分布可能提示攻击 |
$remote_user |
HTTP Basic Auth 用户名 | — | 异常用户登录 |
$time_local |
服务器本地时间 [12/Jun/2026:10:00:00 +0800] |
— | 时区是否对齐 |
$request |
完整请求行(方法 + URI + 协议) | — | 异常方法/路径 |
$status |
HTTP 状态码 | 200/301/304 | 4xx/5xx 突增即问题 |
$body_bytes_sent |
响应体字节数(不含 header) | — | 0 字节可能有问题 |
$http_referer |
Referer 头 | — | 排查盗链/爬虫来源 |
$http_user_agent |
客户端 UA | — | 可疑 UA 识别爬虫/攻击 |
$request_time |
请求处理总耗时(秒),从 Nginx 收到首字节到发完末字节 | 0.001~0.500 | >1.0 为慢请求 |
$upstream_connect_time |
与上游建立 TCP 连接的时间(秒) | 0.000~0.005 | >0.050 怀疑网络问题 |
$upstream_header_time |
连接 + 收到上游首字节的时间 | ≤ upstream_response_time | 用于定位上游慢在哪(建立连接慢 vs 响应慢) |
$upstream_response_time |
与上游交互总时间 | 应 ≤ $request_time |
若 >> request_time 检查 Nginx 自身处理 |
$request_id |
Nginx 生成唯一 ID(1.11+) | 串联整个调用链 | 跨组件 trace 的关键 |
$http_x_forwarded_for |
客户端真实 IP(经过反代后) | — | 多级反代时逗号分隔 |
$scheme |
HTTP 或 HTTPS | — | 检查协议一致性 |
$host |
请求的 Host 头 | — | 排查虚拟主机路由 |
$request_uri |
完整 URL(含 query string) | — | 对比 $request 排查 rewrite |
核心排查三件套: $request_time、$upstream_connect_time、$upstream_response_time 三个字段一起看。若 $request_time ≈ $upstream_response_time,瓶颈在上游;若 $request_time >> $upstream_response_time,卡在 Nginx 自身或客户端侧。
error_log 关键字速查
| error_log 关键字 | 含义 | 定位方向 |
|---|---|---|
connect() failed |
连不上上游 | 上游进程/防火墙/端口/IP 配置 |
upstream timed out |
上游超时 | 上游响应慢/proxy_read_timeout 太短 |
no live upstreams |
所有上游不可用 | 全部被 max_fails 标记为 down |
worker connections are not enough |
连接数耗尽 | 调大 worker_connections |
accept() failed (24: Too many open files) |
文件描述符耗尽 | 调 worker_rlimit_nofile |
client sent invalid header line |
客户端非法 header | 检查客户端版本/行为 |
SSL_do_handshake() |
SSL 握手失败 | 证书/协议兼容性 |
limiting requests |
触发限流 | limit_req/limit_conn 配置 |
upstream sent too big header |
上游响应头超出 proxy_buffer_size |
调大缓冲区 |
cache lock |
缓存锁竞争 | 检查缓存配置 |
二、超时配置全景
Nginx 超时配置分布在 客户端、Nginx 自身、上游、FastCGI 四层:
客户端层
client_body_timeout 30s; # 客户端发送请求体的超时(空闲超时)
client_header_timeout 30s; # 客户端发送请求头的超时(空闲超时)
send_timeout 30s; # Nginx 向客户端发送响应的超时(两次写入之间)
keepalive_timeout 65s; # 长连接保持时间
keepalive_requests 100; # 长连接最多处理多少请求后断开
client_max_body_size 50m; # 客户端请求体最大
client_body_buffer_size 128k; # 请求体缓冲区
client_header_buffer_size 1k; # 请求头缓冲区
large_client_header_buffers 4 8k; # 大请求头缓冲区
上游层
upstream backend {
server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.2.11:8080 max_fails=3 fail_timeout=30s;
keepalive 64;
keepalive_requests 1000;
keepalive_timeout 60s;
}
location / {
proxy_connect_timeout 5s; # TCP 三次握手总时长(生产 1~5s)
proxy_send_timeout 60s; # 向上游发送请求的"两次写操作之间"超时
proxy_read_timeout 60s; # 等待上游响应的"两次读操作之间"超时
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 30s;
}
⚠️
proxy_next_upstream对非幂等接口(POST/PUT)默认不应启用重试,会放大故障。
FastCGI 层
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_connect_timeout 5s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
三、4xx 状态码排查(10 种)
| 状态码 | 含义 | 常见根因 | 排查命令 |
|---|---|---|---|
| 400 | Bad Request | 请求头格式错、body 超 client_max_body_size、URL 编码非法、HTTP/0.9 被拒绝 |
awk '$9 == 400 {print $7}' access.log \| sort \| uniq -c \| sort -rn \| head -20 |
| 401 | Unauthorized | 没带/格式错误的 Authorization 头、token 过期、Basic Auth 错 | awk '$9 == 401 {print $7}' access.log \| sort \| uniq -c |
| 403 | Forbidden | IP 黑名单 deny、文件权限、SELinux、目录列表关闭、限流触发 |
grep -i 'denied\|permission denied' error.log、grep 'avc denied' /var/log/audit/audit.log |
| 404 | Not Found | 路径打错、静态文件不存在、反代后端无此路径、try_files 配错、反代没传 $request_uri |
awk '$9 == 404 {print $7}' access.log \| sort \| uniq -c \| sort -rn \| head -20 |
| 405 | Method Not Allowed | POST 请求静态文件、反代上游只支持 GET、limit_except 限制 |
检查请求方法与 location 配置 |
| 408 | Request Timeout | 客户端慢请求、慢请求体、keepalive 客户端长时间不发请求 | grep -i 'client timed out' error.log |
| 413 | Payload Too Large | 请求体超过 client_max_body_size |
调大 client_max_body_size(文件上传业务常见) |
| 414 | URI Too Long | URL 超 large_client_header_buffers 总容量 |
调大缓冲区(注意调太大可被攻击) |
| 429 | Too Many Requests | limit_req/limit_conn 触发 |
grep -i 'limiting requests' error.log |
| 499 | Client Closed Request | Nginx 特有(非 HTTP 标准),客户端主动断开连接 | 结合 $request_time 看慢请求导致客户端不耐 |
499 突然飙升:通常是慢请求导致客户端超时主动断开,需排查上游延迟。
四、5xx 状态码排查
| 状态码 | 含义 | 常见根因 | 排查方向 |
|---|---|---|---|
| 500 | Internal Server Error | 上游应用抛异常、if 指令语法错误、模块加载失败 |
grep '\[error\]' error.log \| tail -50 |
| 502 | Bad Gateway | 上游未启动/防火墙挡/IP 端口错、accept 队列满、磁盘满、连接数打满 | grep -i 'connect() failed' error.log |
| 503 | Service Unavailable | 所有上游被 max_fails 标为 down、limit_req/limit_conn 触发 |
awk '$9 == 503 {print $7}' access.log |
| 504 | Gateway Timeout | 上游处理慢导致 proxy_read_timeout 超时 |
awk '$9 == 504 {print $14, $18}' access.log |
502 vs 504 区别
| 维度 | 502 | 504 |
|---|---|---|
| 阶段 | 握手阶段失败或上游主动断开 | 握手成功但上游迟迟不响应 |
$upstream_connect_time |
经常为 -(没连上) |
正常值(数毫秒) |
$upstream_response_time |
无(连不上所以没响应) | 等于 proxy_read_timeout 配置值 |
| error_log 关键字 | connect() failed、no live upstreams |
upstream timed out |
五、真实案例复盘
案例 1:上游 Tomcat 慢查询导致 504
现象:每分钟 200~300 个 504,业务反馈部分用户加载缓慢。
排查:
# 发现 504 的 request_time 都集中在 60 秒
awk '$9 == 504 {print $14}' access.log | sort -n | tail
# 输出:59.998, 60.001, 60.003... 等于 proxy_read_timeout
# 看上游响应时间
awk '$9 == 504 {print $18}' access.log | sort -n | tail
根因:Tomcat 端某个慢查询接口 P99 超过 60 秒,Nginx 配置 proxy_read_timeout 60s 直接切断。
修复:
- 短期:
proxy_read_timeout调到 120 秒,先恢复业务 - 中期:Tomcat 端优化慢 SQL,加索引、改查询逻辑
- 长期:上游引入熔断机制,慢请求主动 reject 返回 503 而不是拖到超时
案例 2:上游 accept 队列满导致 502
现象:高并发场景 502 集中在某个 Tomcat 节点。
排查:
# Nginx 上看 502 集中在哪个 upstream 地址
awk '$9 == 502 {print $1, $7, $NF}' access.log | sort | uniq -c | sort -rn | head
# Tomcat 侧检查 accept 队列
netstat -s | grep -i listen
# 大量 "SYNs to LISTEN sockets dropped"
根因:Tomcat 接收请求速度跟不上,连接被 Linux 内核丢弃。
修复:调大 Tomcat acceptCount + Linux somaxconn + tcp_max_syn_backlog。
案例 3:worker_connections 耗尽
现象:高峰期出现 502,error.log 大量 "worker connections are not enough"。
根因:Nginx worker 进程的最大连接数耗尽。Nginx 总连接数 = worker_processes * worker_connections / 2(每条连接在上游和客户端各算一个)。
修复:
worker_processes auto;
worker_rlimit_nofile 65535;
events {
worker_connections 40960;
use epoll;
multi_accept on;
}
案例 4:keep-alive 没复用导致上游连接池打满
现象:上游连接数爆表,Tomcat 报 "connection pool exhausted"。
根因:Nginx 默认每个请求和上游建立新连接,高并发下把上游连接池打满。
修复:
upstream backend {
keepalive 64;
keepalive_requests 1000;
keepalive_timeout 60s;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
案例 5:磁盘满导致 502
现象:突然所有接口 502,Nginx 进程还在,但所有请求无法正常返回。
排查:df -h 看到磁盘 100%。
根因:Nginx 写临时文件失败。
修复:
truncate -s 0 /var/log/nginx/access.log
# 配置 logrotate 和磁盘水位监控
案例 6:DNS 解析超时导致 502
现象:上游配置域名(proxy_pass http://backend.example.com:8080),重启后 502。
根因:Nginx 启动时解析一次域名缓存,之后不主动重新解析。域名 IP 变更后仍用旧 IP。
修复(Nginx 1.17+):
resolver 10.0.0.1 valid=30s;
案例 7:proxy_buffer 太小导致响应截断
现象:返回的 JSON/HTML 不完整,状态码 200 但客户端报错。
排查:
curl -v http://app.example.com/api/big-data # 看到响应被截断
grep -i 'upstream sent too big header' error.log
修复:调大 proxy_buffer_size 16k; proxy_buffers 4 64k;
六、日志切割与归档
# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
daily
rotate 30
missingok
notifempty
compress
delaycompress
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
关键:postrotate 用 kill -USR1 通知 Nginx 重新打开日志文件,不能 nginx -s reload(会断连接)。
# 归档与清理
xz /var/log/nginx/access.log-20260612
find /var/log/nginx/ -name "*.gz" -mtime +90 -delete
七、监控与告警
nginx-prometheus-exporter
server {
listen 127.0.0.1:80;
location /stub_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
nginx-prometheus-exporter -nginx.scrape-uri=http://localhost/stub_status
Prometheus 告警规则
groups:
- name: nginx
rules:
- alert: Nginx5xxHigh
expr: rate(nginx_http_responses_total{status=~"5.."}[5m]) > 10
for: 5m
labels: { severity: critical }
- alert: Nginx4xxHigh
expr: rate(nginx_http_responses_total{status=~"4.."}[5m]) > 100
for: 5m
labels: { severity: warning }
- alert: NginxUpstreamDown
expr: nginx_upstream_check_up{status="down"} == 1
for: 1m
labels: { severity: critical }
八、access_log 实战分析
# QPS 与带宽
awk '{print $4}' access.log | cut -d: -f1-3 | uniq -c
awk '{sum += $10} END {print sum/1024/1024, "MB"}' access.log
# 状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn
awk '$9 ~ /^4/ {c4++} END {print c4/NR*100"%"}'
# 慢 URL 排行
awk '{print $14, $7}' access.log | sort -rn | head -20
# Top IP / URL
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -20
# 找爬虫
awk -F'"' '$6 ~ /spider|bot|crawl/i {print $6}' access.log | sort | uniq -c | sort -rn | head
# P99 延迟按 URL 聚合
awk '{
url=$7; rt=$14
if (rt ~ /^[0-9.]+$/) {
sum[url] += rt; cnt[url]++; data[url, cnt[url]] = rt
}
} END {
for (url in cnt) {
n = cnt[url]
for (i = 1; i <= n; i++) arr[i] = data[url, i]
for (i = 1; i <= n; i++)
for (j = i+1; j <= n; j++)
if (arr[i] > arr[j]) { t = arr[i]; arr[i] = arr[j]; arr[j] = t }
p99 = arr[int(n*0.99)+1]
printf "%-40s p99=%.3fs n=%d\n", url, p99, n
}
}' access.log | sort -k3 -rn | head -20
九、日志分析工具链
| 工具 | 特点 | 适用场景 |
|---|---|---|
| goaccess | 终端实时分析,生成 HTML 报表 | 单机快速排查 |
| ELK (Elasticsearch+Logstash+Kibana) | 全文搜索、可视化 Dashboard | 企业级日志平台 |
| PLG (Promtail+Loki+Grafana) | 轻量级,与 Prometheus 生态集成 | 中小规模集群 |
goaccess -f /var/log/nginx/access.log -o /var/www/report.html
十、Nginx 安全配置
# 隐藏版本
server_tokens off;
# 限制方法
if ($request_method !~ ^(GET|POST|HEAD|OPTIONS)$) { return 405; }
# CC 防护
limit_req_zone $binary_remote_addr zone=cc:10m rate=10r/s;
location / { limit_req zone=cc burst=20 nodelay; }
# Slowloris 防护
client_body_timeout 10s;
client_header_timeout 10s;
send_timeout 10s;
keepalive_timeout 5s;
限制恶意 User-Agent
map $http_user_agent $bad_ua {
default 0;
~*sqlmap 1;
~*nmap 1;
}
if ($bad_ua) { return 403; }
限制 Referer 防盗链
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) { return 403; }
十一、Nginx 性能调优
系统级内核参数
# /etc/sysctl.d/99-nginx.conf
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 300000
net.ipv4.tcp_fin_timeout = 15
net.ipv4.ip_local_port_range = 1024 65535
Nginx 配置优化
# CPU 亲和性
worker_cpu_affinity auto;
# 文件描述符
worker_rlimit_nofile 65535;
# Gzip 压缩(CPU 敏感业务调低级别)
gzip on;
gzip_min_length 1k;
gzip_comp_level 5;
gzip_types text/plain text/css application/javascript application/json application/xml;
# 缓存文件描述符
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
十二、通用配置模板
通用反代模板
upstream backend {
least_conn;
server 10.0.2.10:8080 max_fails=3 fail_timeout=30s;
server 10.0.2.11:8080 max_fails=3 fail_timeout=30s;
keepalive 64;
}
server {
listen 80 backlog=2048 deferred reuseport;
server_name app.example.com;
access_log /var/log/nginx/app.access.log main;
error_log /var/log/nginx/app.error.log warn;
client_max_body_size 50m;
location / {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
proxy_next_upstream error timeout http_502 http_503 http_504;
}
}
HTTPS 反代模板
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/nginx/ssl/app.example.com.crt;
ssl_certificate_key /etc/nginx/ssl/app.example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
location / { proxy_pass http://backend; }
}
server { listen 80; server_name app.example.com; return 301 https://$server_name$request_uri; }
十三、完整排障清单
| 检查项 | 命令 |
|---|---|
| 日志在哪 | /var/log/nginx/access.log / error.log |
| Nginx 是否运行 | systemctl status nginx |
| 配置语法检查 | nginx -t |
| 端口监听 | ss -ltn \| grep :80 |
| 连接数 | ss -s、curl localhost/nginx_status |
| 最近的 5xx | awk '\$9 ~ /^5/ {print \$9, \$7, \$NF}' access.log \| tail |
| 上游是否存活 | curl http://10.0.2.10:8080/health |
| 磁盘使用率 | df -h |
| 文件描述符 | lsof -p \$(pgrep -f nginx) \| wc -l |
| 错误日志关键字 | grep -i 'connect() failed\|upstream timed out' error.log |
| 最近 5 分钟 5xx 错误率 | grep '5[0-9][0-9]' access.log \| wc -l / wc -l access.log |
十四、常见误区
- ❌ 调大 worker_connections 就能解决性能问题 — 还受 fd、CPU、磁盘、内存影响
- ❌ keepalive 64 越大越好 — 占内存,需按上游连接池容量调
- ❌ proxy_read_timeout 越大越安全 — 上游慢时 Nginx 一直挂着连接,拖累连接数
- ❌ 503 一定是上游挂了 — 也可能是
limit_req、limit_conn触发
关联页面
| 页面 | 关联点 |
|---|---|
| nginx-log-analysis-monitoring-guide | Nginx 日志分析与监控体系构建指南 |
| nginx-config-pitfalls | Nginx 典型配置错误复盘 |
| nginx-502-504-connection-reset-guide | Nginx 502/504/Connection Reset 深度排查 |
| nginx-production-performance-optimization | 生产级 Nginx 性能优化 |
| nginx-troubleshooting-methodology-8-steps | 一套可以在生产环境照搬执行的 Nginx 故障排查套路,覆盖 502/504/499/500/con |
| nginx-security-config-guide | Nginx 安全配置实战 |