故障排查与最佳实践
2026/3/20大约 10 分钟
故障排查与最佳实践
常见错误码排查
400 Bad Request
原因分析:
- 请求头过大
- Cookie 过大
- 请求格式错误
- 客户端发送了无效请求
排查步骤:
# 查看错误日志
tail -f /var/log/nginx/error.log | grep 400
# 检查请求头大小配置
grep -E "client_header_buffer|large_client_header" /etc/nginx/nginx.conf
解决方案:
http {
# 增大请求头缓冲区
client_header_buffer_size 4k;
large_client_header_buffers 4 32k;
}
403 Forbidden
原因分析:
- 文件/目录权限不足
- deny 指令阻止访问
- SELinux 限制
- autoindex 未开启
排查步骤:
# 检查文件权限
ls -la /var/www/html/
# 检查 Nginx 运行用户
ps aux | grep nginx
# 检查 SELinux
getenforce
ls -Z /var/www/html/
# 检查配置中的 deny 指令
grep -r "deny" /etc/nginx/
解决方案:
# 修复权限
chown -R nginx:nginx /var/www/html/
chmod -R 755 /var/www/html/
# 修复 SELinux
chcon -R -t httpd_sys_content_t /var/www/html/
# 或关闭 SELinux
setenforce 0
404 Not Found
原因分析:
- 文件不存在
- root 或 alias 配置错误
- location 匹配不正确
- try_files 配置问题
排查步骤:
# 确认文件存在
ls -la /var/www/html/path/to/file
# 检查 root 配置
grep -E "root|alias" /etc/nginx/conf.d/*.conf
# 测试 location 匹配
nginx -T | grep -A 20 "location"
解决方案:
# 检查 root 路径是否正确
location / {
root /var/www/html; # 确保路径正确
try_files $uri $uri/ /index.html;
}
413 Request Entity Too Large
原因分析:
- 上传文件超过限制
- POST 请求体过大
排查步骤:
# 检查 client_max_body_size 配置
grep -r "client_max_body_size" /etc/nginx/
解决方案:
http {
# 全局设置
client_max_body_size 100m;
}
# 或在特定 location 设置
location /upload {
client_max_body_size 500m;
}
499 Client Closed Request
原因分析:
- 客户端在服务器响应前断开连接
- 后端处理时间过长
- 客户端超时设置过短
排查步骤:
# 分析 499 请求
awk '$9 == 499 {print $0}' /var/log/nginx/access.log | tail -20
# 检查响应时间
awk '$9 == 499 {print $NF}' /var/log/nginx/access.log | sort -n
解决方案:
# 调整后端超时
location / {
proxy_pass http://backend;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
500 Internal Server Error
原因分析:
- 后端应用错误
- FastCGI 配置错误
- 权限问题
- 配置语法错误
排查步骤:
# 查看 Nginx 错误日志
tail -f /var/log/nginx/error.log
# 查看后端应用日志
tail -f /var/log/app/error.log
# 测试后端服务
curl -v http://127.0.0.1:8080/health
502 Bad Gateway
原因分析:
- 后端服务未启动
- upstream 配置错误
- 后端服务崩溃
- 连接后端超时
排查步骤:
# 检查后端服务状态
systemctl status php-fpm
curl -v http://127.0.0.1:8080/
# 检查端口监听
ss -tlnp | grep 8080
# 查看错误日志
tail -f /var/log/nginx/error.log | grep 502
解决方案:
# 启动后端服务
systemctl start php-fpm
# 检查 upstream 配置
nginx -T | grep -A 10 "upstream"
503 Service Unavailable
原因分析:
- 后端服务器全部不可用
- 限流触发
- 维护模式
排查步骤:
# 检查 upstream 健康状态
curl http://localhost/upstream_status
# 检查限流配置
grep -E "limit_req|limit_conn" /etc/nginx/conf.d/*.conf
504 Gateway Timeout
原因分析:
- 后端响应超时
- proxy_read_timeout 过短
- 后端处理时间过长
排查步骤:
# 分析超时请求
awk '$9 == 504 {print $7, $NF}' /var/log/nginx/access.log
# 检查超时配置
grep -E "proxy_.*_timeout" /etc/nginx/conf.d/*.conf
解决方案:
location / {
proxy_pass http://backend;
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
proxy_send_timeout 60s;
}
常见配置错误
location 匹配不生效
# 错误示例:正则优先级问题
server {
# 这个正则会匹配所有 /api 开头的请求
location ~ ^/api {
return 200 "regex match";
}
# 这个永远不会被匹配到
location /api/v2 {
return 200 "prefix match";
}
}
# 正确做法:使用 ^~ 阻止正则匹配
server {
location ^~ /api/v2 {
return 200 "prefix match";
}
location ~ ^/api {
return 200 "regex match";
}
}
proxy_pass 斜杠问题
# 请求: /api/users
# 情况1:不带斜杠
location /api/ {
proxy_pass http://backend;
# 转发: http://backend/api/users
}
# 情况2:带斜杠
location /api/ {
proxy_pass http://backend/;
# 转发: http://backend/users (注意 /api/ 被去掉了)
}
# 情况3:带路径
location /api/ {
proxy_pass http://backend/v2/;
# 转发: http://backend/v2/users
}
# 常见错误:路径拼接错误
location /api/ {
proxy_pass http://backend/v2; # 缺少斜杠!
# 转发: http://backend/v2users (错误!)
}
try_files 使用陷阱
# 错误:最后一个参数是 URI 而不是文件
location / {
try_files $uri $uri/ /index.php$is_args$args;
}
# 注意:如果 index.php 不存在于 root 目录
# 会导致内部重定向循环
# 正确:确保最后的回退 URI 存在
location / {
root /var/www/html;
try_files $uri $uri/ /index.html;
}
# 或使用命名 location
location / {
try_files $uri $uri/ @backend;
}
location @backend {
proxy_pass http://127.0.0.1:8080;
}
if 指令的坑
# 错误:if 是邪恶的(在 location 中)
location / {
if ($request_uri ~* "\.php$") {
# 这里的指令可能不会如预期执行
proxy_pass http://php-backend;
}
}
# 正确:使用单独的 location
location ~ \.php$ {
proxy_pass http://php-backend;
}
# 或使用 map
map $request_uri $backend {
~*\.php$ php-backend;
default static-backend;
}
location / {
proxy_pass http://$backend;
}
upstream 连接复用问题
# 错误:没有启用 keepalive
upstream backend {
server 127.0.0.1:8080;
}
location / {
proxy_pass http://backend;
# 每个请求都会创建新连接
}
# 正确:启用连接复用
upstream backend {
server 127.0.0.1:8080;
keepalive 32;
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须!
proxy_set_header Connection ""; # 必须!
}
性能问题排查
连接数暴涨
# 查看连接状态
ss -s
# 查看 TIME_WAIT 连接
ss -ant | awk '/TIME-WAIT/ {count++} END {print count}'
# 查看连接来源
ss -ant | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head
# 解决方案
# 1. 调整内核参数
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_fin_timeout=15
# 2. 启用 upstream keepalive
响应时间变长
# 分析响应时间分布
awk -F'rt=' '{if($2)print $2}' access.log | awk '{
if($1<0.1) bucket["<100ms"]++
else if($1<0.5) bucket["100-500ms"]++
else if($1<1) bucket["500ms-1s"]++
else bucket[">1s"]++
}
END {for(b in bucket) print b, bucket[b]}' | sort
# 找出慢请求
awk -F'rt=' '$2>2 {print}' access.log | tail -20
# 检查后端响应时间
awk -F'urt=' '{print $2}' access.log | awk '{sum+=$1; count++} END {print sum/count}'
CPU 使用率过高
# 查看 Worker 进程 CPU 使用
top -Hp $(pgrep -f "nginx: worker" | head -1)
# 可能原因:
# 1. 正则表达式过于复杂
# 2. Gzip 压缩级别过高
# 3. SSL 握手过多
# 解决方案
# 降低 gzip 压缩级别
gzip_comp_level 4;
# 启用 SSL session 缓存
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
排查命令汇总
# 查看 Nginx 进程状态
ps aux | grep nginx
# 查看文件描述符使用
ls /proc/$(cat /run/nginx.pid)/fd | wc -l
# 查看网络连接
ss -tlnp | grep nginx
netstat -ant | grep :80 | wc -l
# 实时查看访问日志
tail -f /var/log/nginx/access.log
# 实时查看错误日志
tail -f /var/log/nginx/error.log
# 查看 Nginx 状态
curl http://localhost/nginx_status
# 测试配置
nginx -t
# 查看完整配置
nginx -T
# 使用 strace 跟踪系统调用
strace -p $(pgrep -f "nginx: worker" | head -1) -c
# 使用 curl 测试
curl -v -o /dev/null http://localhost/
curl -I http://localhost/
curl -w "@curl-format.txt" -o /dev/null -s http://localhost/
SSL/TLS 问题
证书过期
# 检查证书过期时间
openssl x509 -in /etc/nginx/ssl/cert.pem -noout -dates
# 检查远程证书
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
# 自动检查脚本
#!/bin/bash
CERT="/etc/nginx/ssl/cert.pem"
DAYS=30
EXPIRY=$(openssl x509 -in $CERT -noout -enddate | cut -d= -f2)
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $DAYS ]; then
echo "Certificate expires in $DAYS_LEFT days!"
fi
证书链不完整
# 检查证书链
openssl s_client -connect example.com:443 -showcerts
# 验证证书链
openssl verify -CAfile /etc/nginx/ssl/ca-bundle.crt /etc/nginx/ssl/cert.pem
# 修复:合并证书链
cat server.crt intermediate.crt root.crt > fullchain.pem
HTTPS 不生效
# 检查 SSL 配置
nginx -T | grep -E "ssl_|listen.*ssl"
# 检查端口监听
ss -tlnp | grep 443
# 测试 SSL 连接
openssl s_client -connect localhost:443
# 检查防火墙
iptables -L -n | grep 443
firewall-cmd --list-all
运维最佳实践
配置文件组织结构
/etc/nginx/
├── nginx.conf # 主配置文件
├── conf.d/ # 站点配置目录
│ ├── default.conf
│ ├── example.com.conf
│ └── api.example.com.conf
├── snippets/ # 配置片段
│ ├── ssl-params.conf
│ ├── proxy-params.conf
│ └── security-headers.conf
├── ssl/ # SSL 证书
│ ├── example.com.crt
│ └── example.com.key
└── sites-available/ # 可用站点(可选)
└── sites-enabled/ # 已启用站点(符号链接)
配置变更流程
#!/bin/bash
# 配置变更脚本
# 1. 测试配置
nginx -t || exit 1
# 2. 备份当前配置
BACKUP_DIR="/etc/nginx/backup/$(date +%Y%m%d%H%M%S)"
mkdir -p $BACKUP_DIR
cp -r /etc/nginx/conf.d/* $BACKUP_DIR/
# 3. 应用新配置
nginx -s reload
# 4. 验证
sleep 2
curl -f http://localhost/health || {
echo "Health check failed, rolling back..."
cp -r $BACKUP_DIR/* /etc/nginx/conf.d/
nginx -s reload
exit 1
}
echo "Configuration updated successfully"
安全加固清单
| 检查项 | 配置 | 说明 |
|---|---|---|
| 隐藏版本号 | server_tokens off; | 防止版本探测 |
| 禁止目录浏览 | autoindex off; | 防止目录遍历 |
| 限制请求方法 | limit_except GET POST | 只允许必要方法 |
| 防止点击劫持 | X-Frame-Options SAMEORIGIN | 防止 iframe 嵌套 |
| XSS 防护 | X-Content-Type-Options nosniff | 防止 MIME 嗅探 |
| HTTPS 强制 | HSTS header | 强制使用 HTTPS |
| 禁止敏感文件 | location ~ /\. { deny all; } | 禁止访问隐藏文件 |
| 限制上传大小 | client_max_body_size 10m; | 防止大文件攻击 |
| 超时设置 | client_body_timeout 10s; | 防止慢速攻击 |
Nginx 配置检查清单
| 检查项 | 状态 | 说明 |
|---|---|---|
| 配置语法测试 | □ | nginx -t 通过 |
| 日志路径正确 | □ | access_log 和 error_log 路径存在 |
| 证书有效期 | □ | SSL 证书未过期 |
| 证书链完整 | □ | 中间证书已配置 |
| 权限正确 | □ | 网站目录权限正确 |
| upstream 可达 | □ | 后端服务正常运行 |
| 健康检查配置 | □ | /health 端点可访问 |
| 安全头配置 | □ | 安全响应头已添加 |
| 限流配置 | □ | limit_req/limit_conn 已配置 |
| 错误页面 | □ | 自定义错误页面配置 |
| 日志轮转 | □ | logrotate 配置正确 |
| 监控配置 | □ | stub_status 已启用 |
常用运维脚本
Nginx 状态检查脚本
#!/bin/bash
# /usr/local/bin/nginx-check.sh
echo "=== Nginx 状态检查 ==="
echo ""
# 进程检查
echo "1. 进程状态:"
ps aux | grep -E "nginx: (master|worker)" | grep -v grep
# 端口检查
echo ""
echo "2. 监听端口:"
ss -tlnp | grep nginx
# 配置检查
echo ""
echo "3. 配置语法:"
nginx -t 2>&1
# 连接状态
echo ""
echo "4. 连接状态:"
if curl -s http://localhost/nginx_status > /dev/null 2>&1; then
curl -s http://localhost/nginx_status
else
echo "stub_status 未启用"
fi
# 错误日志最近10条
echo ""
echo "5. 最近错误日志:"
tail -10 /var/log/nginx/error.log
# 磁盘空间
echo ""
echo "6. 日志磁盘空间:"
du -sh /var/log/nginx/
日志分析脚本
#!/bin/bash
# /usr/local/bin/nginx-log-analyze.sh
LOG_FILE="${1:-/var/log/nginx/access.log}"
echo "=== Nginx 日志分析 ==="
echo "日志文件: $LOG_FILE"
echo ""
echo "1. 总请求数:"
wc -l < $LOG_FILE
echo ""
echo "2. HTTP 状态码分布:"
awk '{print $9}' $LOG_FILE | sort | uniq -c | sort -rn
echo ""
echo "3. Top 10 访问 IP:"
awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "4. Top 10 请求 URL:"
awk '{print $7}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "5. 每小时请求分布:"
awk '{print substr($4, 14, 2)}' $LOG_FILE | sort | uniq -c
echo ""
echo "6. 5xx 错误数量:"
awk '$9 ~ /^5/ {count++} END {print count+0}' $LOG_FILE
证书过期检查脚本
#!/bin/bash
# /usr/local/bin/ssl-check.sh
CERT_DIR="/etc/nginx/ssl"
WARN_DAYS=30
EMAIL="admin@example.com"
echo "=== SSL 证书检查 ==="
for cert in $CERT_DIR/*.crt $CERT_DIR/*.pem; do
if [ -f "$cert" ]; then
EXPIRY=$(openssl x509 -in "$cert" -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -n "$EXPIRY" ]; then
EXPIRY_EPOCH=$(date -d "$EXPIRY" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo ""
echo "证书: $cert"
echo "过期时间: $EXPIRY"
echo "剩余天数: $DAYS_LEFT"
if [ $DAYS_LEFT -lt $WARN_DAYS ]; then
echo "警告: 证书即将过期!"
# 发送告警邮件
# echo "证书 $cert 将在 $DAYS_LEFT 天后过期" | mail -s "SSL 证书过期告警" $EMAIL
fi
fi
fi
done
自动备份脚本
#!/bin/bash
# /usr/local/bin/nginx-backup.sh
BACKUP_DIR="/backup/nginx"
DATE=$(date +%Y%m%d)
KEEP_DAYS=30
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份配置文件
tar -czf $BACKUP_DIR/nginx_config_$DATE.tar.gz /etc/nginx/
# 备份 SSL 证书
tar -czf $BACKUP_DIR/nginx_ssl_$DATE.tar.gz /etc/nginx/ssl/
# 删除旧备份
find $BACKUP_DIR -name "*.tar.gz" -mtime +$KEEP_DAYS -delete
echo "备份完成: $BACKUP_DIR/nginx_config_$DATE.tar.gz"
ls -lh $BACKUP_DIR/*.tar.gz | tail -5
总结
本章介绍了 Nginx 故障排查与最佳实践:
- 常见错误码:400-504 错误的原因分析、排查步骤和解决方案
- 配置错误:location 匹配、proxy_pass 斜杠、try_files、if 陷阱
- 性能问题:连接数、响应时间、CPU 使用率的排查方法
- SSL/TLS 问题:证书过期、证书链、HTTPS 配置问题
- 运维最佳实践:配置组织、变更流程、安全加固
- 配置检查清单:上线前必检项目
- 运维脚本:状态检查、日志分析、证书检查、自动备份