NGINX · GATEWAY & LOAD BALANCER

Nginx
故障排查手册

THE OPS FIELD MANUAL
502 · 504 · upstream · SSL · perf
nginx -t · reload · error.log
502/504/499 upstream worker_processes reload SSL/TLS limit_req access.log

Nginx 是流量入口,故障了用户立刻感知。但 Nginx 自己很少崩——大部分"Nginx 故障"实际是 upstream(后端)的问题,Nginx 只是把它显化出来。诊断的核心:看 error.log 找 upstream 报错,而不是把锅都扣给 Nginx。

Nginx 请求处理流程

理解请求从进入到响应经过的每一关,排错时才能定位是哪一步出问题。每个错误码对应不同的失败点。

1
CLIENT
客户端发起请求
2
SERVER
匹配 server_name
3
LOCATION
匹配 URI 路径
4
UPSTREAM
转发到后端
5
BACKEND
后端处理
6
RESPONSE
回写客户端
01

502 Bad Gateway — 上游连不上

UPSTREAM CONNECTION FAILED
高频 必会
  • 页面返回 "502 Bad Gateway"
  • access.log 中状态码 502
  • error.log 出现 "connect() failed" 或 "no live upstreams"
  • 后端服务挂了(Tomcat/Java/Node 进程死了)
  • 后端在重启,端口暂时不监听
  • 后端进程在但 OOM、卡死,无法 accept 新连接
  • upstream 地址或端口配错
  • 后端是 HTTPS 但 Nginx 用 HTTP 转发(或反之)
  • Nginx 和后端之间防火墙/安全组拦截
# 1. 看 error.log 找具体报错(关键!)
tail -f /var/log/nginx/error.log

# 典型错误一:连接被拒绝(后端进程没启动)
# connect() failed (111: Connection refused) while connecting to upstream

# 典型错误二:无可用上游(所有后端都不健康)
# no live upstreams while connecting to upstream

# 典型错误三:upstream 提前关闭连接
# upstream prematurely closed connection while reading response header

# 2. 直连后端确认
curl -v http://<upstream-ip>:<port>/

# 3. 检查后端进程
ss -lntp | grep <port>
ps -ef | grep <service>

# 4. 检查 Nginx 上下游配置
nginx -T | grep -A 5 upstream
# 错误:后端是 HTTPS 但 Nginx 用 http 转发
location /api/ {
    proxy_pass http://backend;    # 后端实际是 https → 502
}

# 正确:后端 HTTPS 必须用 https,且配置证书验证策略
location /api/ {
    proxy_pass https://backend;
    proxy_ssl_server_name on;
    proxy_ssl_verify off;          # 内网自签证书可关闭验证
}
upstream prematurely closed · 这是后端在 Nginx 读完响应头之前就关了连接。常见原因:后端 OOM 被杀、后端响应超时主动 close、Keep-Alive 时间不匹配(Nginx 长连接给后端,但后端比 Nginx 先超时关闭)。

502 速查决策树

  1. 看 error.log:Connection refused → 后端进程死了 / 端口写错
  2. 看 error.log:no live upstreams → 健康检查把所有后端都标记 down
  3. 看 error.log:prematurely closed → 后端主动断开,看后端日志
  4. 直连后端 curl 通吗?不通 → 后端的锅 / 防火墙
  5. HTTPS 后端别用 http://,反之亦然
02

504 Gateway Timeout — 上游响应超时

BACKEND TOO SLOW OR TIMEOUT TOO SHORT
高频 必会
错误码意思类比
502连不上后端 / 后端返回了无效响应"打不通电话"
504连上了,但等响应太久超时"电话接通了但对方不说话"
503后端可达但拒绝服务(主动告知)"对方说'我现在忙,稍后再打'"
# error.log 典型报错
# upstream timed out (110: Connection timed out) while reading response header

# 看是哪个阶段超时
grep "timed out" /var/log/nginx/error.log | tail
location /api/ {
    proxy_pass http://backend;

    proxy_connect_timeout 5s;     # 与后端建立连接的超时(默认 60s)
    proxy_send_timeout 60s;       # 向后端发送请求的超时
    proxy_read_timeout 60s;       # 等后端响应的超时(默认 60s)
                                       # 长接口/大文件下载必调!
}
场景connectread说明
普通 API5s30s默认即可
报表/导出5s600s查询慢,要长等
WebSocket5s3600s长连接不要主动断
文件上传5s600s配合 client_max_body_size
超时不是越长越好 · 把 read_timeout 调到 10 分钟,确实没 504 了,但慢请求会堆积占用 worker,影响其他用户。正确做法是:优化后端慢查询 + 设置合理超时 + 慢接口用单独 location 配置。
# 1. 后端是否慢?直连后端测试时延
time curl http://backend:8080/slow-api

# 2. 看后端日志找慢查询(MySQL slow log / 应用 APM)

# 3. 看后端是否资源不足(CPU/内存/IO)
top
iostat -x 1
03

499 — 客户端主动断开

CLIENT CLOSED CONNECTION BEFORE RESPONSE
高频 进阶

客户端在 Nginx 还没回响应前就主动关闭了连接。责任通常不在 Nginx

  • 客户端有自己的超时,比后端响应快(常见!)
  • 用户点击"取消"或关闭浏览器标签
  • 移动端网络抖动断开
  • 客户端是上层代理/CDN,它的超时短于 Nginx 的
# 看 499 占比
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

# 看哪些接口 499 多
awk '$9==499 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

# 看 499 请求的响应时间分布
awk '$9==499 {print $NF}' /var/log/nginx/access.log | sort -n | tail
# 默认行为:客户端断了,Nginx 立即放弃后端请求
# 高并发场景下会导致后端请求大量取消,反而浪费资源

location /api/ {
    proxy_ignore_client_abort on;    # 客户端断开后,Nginx 继续等后端完成
    proxy_pass http://backend;
}
# 适用场景:幂等的写请求、需要完整执行的任务
# 不适用:GET 查询(浪费资源)
499 多 + 504 也多 → 后端慢是根因,客户端等不及自己超时了。解决方向:优化后端,而不是调 Nginx。
499 多 + 后端响应快 → 客户端自己有问题(超时短/网络差),Nginx 改不了什么。
04

upstream 负载均衡与健康检查

LOAD BALANCING STRATEGIES AND HEALTH
高频 必会
策略配置场景
轮询(默认)不写后端性能均等
加权轮询weight=N后端性能不一
最少连接least_conn长连接、长任务
IP Haship_hash会话保持(粘性)
一致性 Hashhash $request_uri缓存命中率优先
upstream backend {
    # 加权 + 健康检查
    server 10.0.0.1:8080 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.0.2:8080 weight=1;
    server 10.0.0.3:8080 backup;          # 主全挂了才启用
    server 10.0.0.4:8080 down;            # 暂时下线,不参与

    # 复用与后端的长连接(性能关键!)
    keepalive 32;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;             # keepalive 需要 HTTP/1.1
        proxy_set_header Connection "";       # 清除 close 头
    }
}
# max_fails=3 fail_timeout=30s 的含义:
# 在 30 秒内失败 3 次,把该后端标记为 down,持续 30 秒不再分配请求
# 30 秒后,Nginx 会尝试再次发送请求,成功就恢复

# 注意:这是被动健康检查,只在有请求时才检测
# 主动健康检查(定时探测)是商业版功能,开源版需要用 nginx_upstream_check_module
ip_hash 不是真粘性 · 客户端 IP 变了(切换网络、走代理)就会落到新后端,session 丢失。真要做会话保持,用 Redis 共享 session,而不是依赖 ip_hash。
keepalive 的威力 · 给 upstream 配 keepalive 后,Nginx 和后端之间复用 TCP 连接,QPS 能提升 30%-50%,后端 TIME_WAIT 也会大幅减少。生产环境必配。
05

worker 进程 CPU 单核打满

WORKER PROCESS PERFORMANCE
中频 进阶
  • top 看到某个 nginx worker 进程 100% CPU,其他闲着
  • 整体性能上不去,请求开始排队
  • 多核机器只用 1-2 核
  • worker_processes 设成 1 或者过少
  • 正则表达式回溯(rewrite/if 里的复杂正则)
  • SSL 握手负载集中(单核做加解密)
  • 限流/限连接全局锁竞争
# 主配置 /etc/nginx/nginx.conf

worker_processes auto;            # 自动等于 CPU 核数,推荐!
worker_cpu_affinity auto;         # 进程绑核,减少上下文切换

events {
    worker_connections 65535;     # 每个 worker 最大并发连接数
    use epoll;                      # Linux 必选
    multi_accept on;               # 一次 accept 多个连接
}

# 系统级 fd 限制必须配合调高
worker_rlimit_nofile 65535;

最大并发 = worker_processes × worker_connections
例如 8 核 + 65535 → 理论 52 万并发(实际受系统限制)

# 查看 worker 数量
ps -ef | grep "nginx: worker"

# 看每个 worker 的 CPU 使用
top -p $(pgrep -d, "nginx: worker")

# 看 worker 在做什么(找出问题 worker)
strace -p <worker-pid> -c
ssl_session_cache shared:SSL:50m;     # SSL 会话复用,大幅减少握手
ssl_session_timeout 1d;
ssl_session_tickets on;

# 优先使用快速的椭圆曲线
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
06

nginx -s reload 失败或不生效

CONFIGURATION RELOAD ISSUES
高频 入门
# 标准三步:测、重载、验证

# 1. 必须先测试配置语法
nginx -t
# 显示 syntax is ok 和 test is successful 才能继续

# 2. 平滑重载(不中断现有连接)
nginx -s reload
# 或
systemctl reload nginx

# 3. 验证新进程
ps -ef | grep nginx
# 应该看到 master 和新的 worker,旧 worker 在 "shutting down"

# 4. 看 error.log 是否有警告
tail -20 /var/log/nginx/error.log
# 场景一:语法错误
nginx: [emerg] unexpected "}" in /etc/nginx/conf.d/api.conf:23
# → 修配置,nginx -t 通过后再 reload

# 场景二:端口已被占用(只在 start 时出现,reload 不会)
bind() to 0.0.0.0:80 failed (98: Address already in use)

# 场景三:upstream 解析失败
host not found in upstream "api.internal"
# → DNS 不通或 hosts 没配,nginx -t 时就会报

# 场景四:include 的文件不存在
open() "/etc/nginx/conf.d/missing.conf" failed (2: No such file or directory)
# 1. 确认 nginx -t 加载的是哪个配置文件
nginx -t 2>&1 | grep file

# 2. 看完整生效配置(把所有 include 展开)
nginx -T | less

# 3. 确认 reload 真的执行了 — 看 master 进程时间
ps -eo pid,etime,cmd | grep "nginx: master"

# 4. 旧 worker 长期不退出?客户端长连接占着
ps -ef | grep "shutting down"
# 如果有,可用 worker_shutdown_timeout 强制超时
reload vs restart · reload 平滑,不会断开现有连接,生产首选。restart 会重启进程,瞬间所有连接断开,只在 master 进程异常或要改 worker_processes 等启动参数时才用。
修改前先备份 · 改配置前 cp nginx.conf nginx.conf.bak.$(date +%Y%m%d),改完 diff 看变更。线上事故多数是配置改错。
07

大文件上传失败 — 413 / 超时

REQUEST BODY SIZE AND TIMEOUT
高频 入门
  • 上传小文件正常,大文件返回 413 Request Entity Too Large
  • 或上传中途断开(超时)
  • error.log: "client intended to send too large body"
http {
    # 1. 请求体大小限制(默认 1M,常见坑!)
    client_max_body_size 100m;

    # 2. 客户端发送请求体的超时(默认 60s,大文件不够)
    client_body_timeout 600s;

    # 3. 请求体缓冲区(超过会写临时文件)
    client_body_buffer_size 1m;       # 内存缓冲
    client_body_temp_path /var/cache/nginx/client_temp;
}

server {
    location /upload {
        proxy_pass http://backend;

        # 4. 后端代理超时(上传后还要等后端处理)
        proxy_send_timeout 600s;
        proxy_read_timeout 600s;

        # 5. 大文件建议关闭缓冲,直接流式转发
        proxy_request_buffering off;
        proxy_buffering off;
    }
}
指令可在哪里写说明
client_max_body_sizehttp / server / locationlocation 级别可针对特定接口放宽
client_body_timeouthttp / server / location同上
proxy_*_timeouthttp / server / location反向代理才用
K8s Ingress 用 annotation · Nginx Ingress Controller 上对应配置:
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
08

SSL 证书问题排查

CERTIFICATE EXPIRED / MISMATCH / HANDSHAKE
高频 必会
# 1. 看证书过期时间
openssl x509 -in /etc/nginx/ssl/cert.pem -noout -dates

# 2. 看证书包含的域名(SAN)
openssl x509 -in cert.pem -noout -text | grep -A 1 "Subject Alternative Name"

# 3. 远程测试证书
openssl s_client -connect example.com:443 -servername example.com < /dev/null

# 4. 一行看到期时间
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# 5. 验证证书链完整性
openssl verify -CAfile chain.pem cert.pem
报错原因解决
NET::ERR_CERT_DATE_INVALID证书过期续签 / 替换证书
NET::ERR_CERT_COMMON_NAME_INVALID域名与证书不匹配用对应域名证书 / 申请通配符
NET::ERR_CERT_AUTHORITY_INVALIDCA 不被信任用受信 CA / 补全证书链
SSL_ERROR_NO_CYPHER_OVERLAP加密套件不匹配更新 cipher 列表
handshake failedSNI 缺失 / 协议版本启用 SNI / 升级 TLS
server {
    listen 443 ssl http2;
    server_name example.com;

    # 证书路径(cert.pem 必须包含完整证书链,不只是站点证书)
    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/private.key;

    # 协议与套件
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM;
    ssl_prefer_server_ciphers off;

    # 会话复用(性能)
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;

    # HSTS,强制 HTTPS
    add_header Strict-Transport-Security "max-age=63072000" always;
}

# HTTP 自动跳 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
证书链不全的坑 · 浏览器自带常见 CA 的根证书,但中间 CA 没有。如果只配置了站点证书没配中间证书,浏览器报"证书不受信任"。ssl_certificate 必须是 fullchain(站点证书 + 中间证书),不是单独的 cert.pem。
过期监控 · 用 cron 每天检查所有域名的证书过期时间,小于 30 天报警:
echo | openssl s_client -connect $domain:443 2>/dev/null | openssl x509 -noout -checkend 2592000 返回非 0 即将过期。
09

限流配置 — 防爬虫和保护后端

RATE LIMITING WITH LIMIT_REQ / LIMIT_CONN
中频 进阶
模块限制对象典型场景
limit_req请求速率(QPS)限爬虫、限暴力破解
limit_conn并发连接数限下载、防长连接占用
http {
    # 定义限流区域:按客户端 IP,10M 内存,平均 10 req/s
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    # 也可按其他变量限,如登录接口按用户名
    limit_req_zone $arg_username zone=login:10m rate=1r/s;
}

server {
    location /api/ {
        # 应用限流,burst 是突发容忍,超过 rate 但在 burst 内会排队
        limit_req zone=api_limit burst=20 nodelay;

        # nodelay 含义:burst 内的请求立即处理,不排队
        # 不加 nodelay:超过 rate 的请求会被强行延迟到匹配 rate

        # 自定义被限流时的响应码(默认 503)
        limit_req_status 429;
    }

    location /login {
        limit_req zone=login burst=5 nodelay;
        limit_req_status 429;
    }
}
http {
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;
    limit_conn_zone $server_name zone=conn_per_server:10m;
}

server {
    location /download/ {
        # 每个 IP 最多 3 个并发下载
        limit_conn conn_per_ip 3;

        # 每个 IP 限速 500K/s
        limit_rate 500k;
    }
}
场景限流参数
普通 APIrate=100r/s burst=200 nodelay
登录接口rate=5r/m burst=3
搜索接口rate=10r/s burst=20 nodelay
下载接口limit_conn=3 limit_rate=500k
CDN/反代后限流失效 · 用了 CDN 或前面有 SLB 后,$remote_addr 全是代理的 IP,会被限到同一桶里。必须用 $http_x_forwarded_forreal_ip 模块获取真实客户端 IP。
10

性能调优 checklist

PRODUCTION TUNING ESSENTIALS
高频 必会
# =========== 主配置层 ===========
worker_processes     auto;             # 等于 CPU 核数
worker_cpu_affinity  auto;             # 绑核
worker_rlimit_nofile 65535;            # 单 worker 最大 fd

events {
    worker_connections 65535;            # 单 worker 最大连接数
    use epoll;                            # Linux IO 多路复用
    multi_accept on;
}

http {
    # =========== TCP 优化 ===========
    sendfile on;                          # 零拷贝
    tcp_nopush on;                        # 配合 sendfile,合并小包
    tcp_nodelay on;                       # 长连接小数据立即发送

    # =========== 客户端长连接 ===========
    keepalive_timeout 65;
    keepalive_requests 10000;             # 单连接最大请求数

    # =========== Gzip 压缩 ===========
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;                 # 小于 1K 不压缩
    gzip_comp_level 5;                    # 1-9,5 是性价比最高
    gzip_types text/plain application/json application/javascript text/css;

    # =========== 缓冲区 ===========
    client_body_buffer_size    1m;
    client_header_buffer_size  4k;
    large_client_header_buffers 4 16k;    # 大 cookie / URL

    # =========== 隐藏版本 ===========
    server_tokens off;

    # =========== 日志 ===========
    access_log /var/log/nginx/access.log main buffer=32k flush=5s;
}
# /etc/sysctl.conf 必调

# 端口范围
net.ipv4.ip_local_port_range = 1024 65535

# TIME_WAIT 复用
net.ipv4.tcp_tw_reuse = 1

# SYN 队列与 backlog
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535

# 全连接队列
net.core.netdev_max_backlog = 65535

# keepalive 探测
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3

# 应用后 sysctl -p
# 1. QPS 实时统计
tail -f access.log | awk '{print $4}' | uniq -c

# 2. Top 10 慢接口
awk '{print $NF, $7}' access.log | sort -rn | head

# 3. 状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn

# 4. 访问最多的 IP(找异常爬虫)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head

# 5. 5xx 错误聚合
awk '$9 ~ /^5/ {print $7}' access.log | sort | uniq -c | sort -rn

上线前 Nginx 自查清单

  1. worker_processes 是否等于 CPU 核数?
  2. worker_connections 是否 ≥ 10000?
  3. 系统 ulimit -n 是否 ≥ 65535?
  4. upstream 是否配了 keepalive?
  5. gzip 是否开启,且涵盖主要类型?
  6. server_tokens 是否关闭?(安全)
  7. access.log 是否启用 buffer + flush?(性能)
  8. HTTPS 是否配置 ssl_session_cache?
  9. 所有 location 是否有合理的超时配置?
  10. 是否有限流保护核心接口?