Skip to content

nginx 504 gateway timeout 排查:6 类根因决策树

1. 504 到底是什么

504 Gateway Timeout 是 HTTP 语义里定义的一个上游网关错误码(RFC 7231 §6.6.5)。字面翻译是"上游超时",但很多人把它理解成"nginx 卡了"——这是错的。nginx 本身几乎不产生 504,它只是"传话筒"。真正的语义是:nginx 已经把请求发给上游了,也在等上游回数据,但等到 proxy_read_timeout 到期了上游一个字都没回来,nginx 才把 504 甩给客户端。

所以 504 的核心特征就一句话:连接是通的,请求发出去了,等响应等到了超时上限,一个字没回来。这个"一个字"很关键,是它跟 502 分道扬镳的起点。

nginx 的具体行为是:worker 把请求写进 upstream socket,进入 read loop,epoll_wait 等上游回数据;每过一个事件循环检查是否超过 proxy_read_timeout;超了就关闭这条 upstream 连接,向客户端回 504,并在 error.log 里落一条 upstream timed out (110: Connection timed out) while reading response header from upstream。

2. 504 vs 502 的 5 个本质区别

先看一眼最容易搞混的地方。502 是"Bad Gateway",504 是"Gateway Timeout",中文都能翻译成"网关错误",但它们在协议时序上是两种不同的死法。

维度 502 Bad Gateway 504 Gateway Timeout
协议时序 连接建立后,nginx 收到了字节,但字节不合法(非法状态行、非法响应头、中途 RST) 连接建立后,nginx 发了请求,在 read 阶段一个字都没等到
连接状态 通常已经收到 RST 或 FIN,或者有 TCP 层错误 连接还活着,或者已经因为超时被 nginx 主动 close
error.log 关键字 upstream prematurely closed connection、upstream sent invalid header、upstream sent no valid HTTP/1.0 header upstream timed out (110: Connection timed out) while reading response header from upstream
access log 特征 upstream_response_time 通常 < 1s,或者字段本身出现异常;$status 是 502 upstream_response_time 精确等于 proxy_read_timeout(比如配 60s 就是 60.000),$status 是 504
典型根因 后端崩、端口没起、协议版本错、SSL 证书错、keepalive 死连接 后端慢、CPU 打满、连接池耗尽、DNS 慢、SSL 握手卡住

最容易误解的一点:很多人看到 upstream timed out 就以为是"上游连不上"。不是。"连不上"是 502,报 connect() failed。504 里的 "timed out" 指的是读超时,不是连接超时。proxy_connect_timeout 超时是 502,proxy_read_timeout 超时才是 504。这个区别在 nginx 源码里就是两个不同错误码:一个是 NGX_HTTP_BAD_GATEWAY,一个是 NGX_HTTP_GATEWAY_TIME_OUT。

还有一个隐藏区别:nginx 处理 502 和 504 的重试策略也不同。默认情况下 proxy_next_upstream error timeout 只会在下一次尝试时切到另一台上游——也就是说请求会重试一次到第二个 upstream,如果第二个也超时才会真正回 504。502 的重试触发更快,因为它通常瞬间发生;504 的重试要等满一个 proxy_read_timeout 才会切上游,所以 504 用户的感知延迟 = N × proxy_read_timeout(N 是 proxy_next_upstream_tries)。这一点对设计 API 超时预算至关重要:如果你前端超时是 30s,nginx 的 read_timeout 是 20s 且 tries=2,用户会等 40s 才拿到错误,前端早就超时了。必须保证 nginx 端所有超时之和 < 客户端超时,否则前端超时先触发,用户看到 499 而不是 504,你根本排查不到 nginx 的报错。

3. 6 类根因全景图

504 的 6 类根因不是并列关系,而是按发生顺序排列的:先解析 DNS、再建连接、握手 SSL、拿到连接发请求、后端处理、后端返回。任何一环超过 proxy_read_timeout 都会产生 504,但排查顺序应该按这个链路走。

        ┌────────────────────────────────────┐
        │   nginx worker 收到请求             │
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ [根因5] DNS 解析超时                │  resolver 卡、上游 DNS 慢
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ TCP 建连 + [根因4] SSL 握手超时     │  TLS 握手慢、证书链、SNI
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ 从 upstream 池取连接                │
        │ [根因3] 连接池耗尽,等不到可用连接   │
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ 发送 request header                │
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ [根因2] 后端 CPU 打满 / event loop │  进程活着但线程全忙
        │ [根因6] 后端慢查询 / 慢 IO         │  DB 慢、ES 慢、磁盘慢
        └────────────────┬───────────────────┘
                         ▼
        ┌────────────────────────────────────┐
        │ 等待 upstream_response             │
        │ [根因1] proxy_read_timeout 太短    │  时间窗口不够
        └────────────────┬───────────────────┘
                         ▼
                回 504 给客户端

一个粗估的比例:根因 1(timeout 配错)占 40%,根因 2(后端 CPU)占 25%,根因 6(慢查询)占 20%,根因 3(连接池)占 8%,根因 4(SSL 握手)占 5%,根因 5(DNS)占 2%。这是我在一线见过的比例,具体到你自己环境可能差得远,但排查优先级大致按这个来。

4. 根因 1:proxy_read_timeout 配置不当

这是最高频的 504,也是最容易误诊的。很多人一看到 504 就改 proxy_read_timeout 到 300 秒,然后问题"解决"了——其实你只是把 504 变成 P99 99 秒的假象,客户端一样在等,用户体验一样烂,只是错误率报表变好看了。

默认值:proxy_read_timeout 60s、proxy_send_timeout 60s、proxy_connect_timeout 60s。

常见错误配置:

# 错误示范 1:只调 read_timeout,忘了 send_timeout
location /api/ {
    proxy_pass http://backend;
    proxy_read_timeout 300s;      # 后端读超时
    # proxy_send_timeout 默认 60s —— 大 body 请求还是会被掐
}

# 错误示范 2:以为 timeout 是连接超时
location / {
    proxy_pass http://backend;
    proxy_read_timeout 5s;        # 后端正常需要 8s,永远 504
}

# 错误示范 3:在 location 里覆盖了 server 层的 timeout,但不确定谁优先
server {
    proxy_read_timeout 120s;      # 被 location 覆盖
    location / {
        proxy_pass http://backend;
        proxy_read_timeout 30s;   # 实际生效
    }
}

正确排查顺序:

# 1. 找出所有生效的 timeout 配置(nginx -T 会展开 include)
nginx -T 2>/dev/null | grep -E 'proxy_(read|send|connect|next_upstream)_timeout'

# 2. 从 access log 里看真实的响应时间分布
awk '$9==504 {print $NF}' /var/log/nginx/access.log | sort -n | uniq -c | tail
# 如果 upstream_response_time 全部精确等于 60.000,就是被 read_timeout 卡死
# 如果 60.001、60.002 这种,就是真的刚好卡边界

# 3. 从 P99 反推合理的 timeout
tail -100000 /var/log/nginx/access.log | awk '$9!=504 {print $NF}' \
  | sort -n | awk 'BEGIN{c=0} {a[c++]=$1} END{print a[int(c*0.99)]}'
# P99 是 8.3s,那 proxy_read_timeout 应该设 20s 左右(P99 × 2.5)

修复模板(假设 P99 = 8s):

# 放在 server {} 层,所有 location 继承
server {
    listen 443 ssl;
    server_name api.example.com;

    proxy_connect_timeout 5s;       # 建连慢说明后端有问题,5s 够了
    proxy_read_timeout    20s;      # P99 × 2.5,别调 300s
    proxy_send_timeout    10s;      # 通常比 read 短,大 body 场景再单独调
    proxy_next_upstream error timeout http_502 http_504;  # 超时重试
    proxy_next_upstream_tries 2;

    location / {
        proxy_pass http://backend;
    }
}

改完平滑重载:nginx -s reload。

一个反例:某次线上排查,客户把 proxy_read_timeout 从 60s 调到 300s 后 504 立刻消失,运维报告写"通过增大超时时间修复"。但一周后 P99 响应时间从 3s 涨到 45s,用户端开始频繁报"页面转圈不动"。真正的根因是后端某个 SQL 缺索引,被 timeout 屏蔽了报错。调大 timeout 是止痛药不是治本,改完一定要盯住 P99 曲线,如果同时恶化就是信号。

5. 根因 2:后端 CPU 打满导致响应慢

后端进程活着、端口通、TCP 握手成功,但请求发过去就是没反应——这是 CPU 打满的典型症状。

我在一个 Java 服务上遇到过一次:晚上 10 点开始连续 504,jstack 抓出来全是 RUNNABLE 状态在执行 JSON 序列化,top 显示单核飙到 100%,其他核 30%。GC 是正常的,堆内存正常——就是纯粹的计算密集型任务把 event loop 打爆了。

排查命令:

# 1. 后端进程 CPU 占用
top -p <pid> -H                    # -H 看每个线程的 CPU
# 或者
pidstat -u -p <pid> 1 5            # 每 1 秒刷新 5 次

# 2. 系统整体 CPU 状态
mpstat -P ALL 1 3                  # 看每个核的 %usr %sys %idle
# %usr 高 → 应用自己算得多
# %sys 高 → syscall 密集,可能是 IO 或网络
# %wa 高 → 在等 IO,去查根因 6

# 3. 抓 perf top 看具体热点
perf top -p <pid>
perf record -g -p <pid> -- sleep 30
perf report --stdio | head -50

# 4. Node.js 场景:event loop 延迟
node -e "setInterval(()=>console.log(process.cpuUsage()),1000)"
# 或者装 clinic.js:clinic doctor -- node app.js

# 5. Java 场景:直接看线程堆栈
jstack <pid> | grep -A 5 "RUNNABLE" | head -50

修复方向(按后端类型):

  • Node.js:加 PM2 cluster pm2 start app.js -i 0(跑满所有核);把 CPU 密集任务扔 worker_threads 或独立进程;限制单请求最大执行时间 server.requestTimeout = 10000
  • Java:查 GC(jstat -gc <pid> 1000)、查线程池、查 JSON 库是不是用了 Gson 而可以换 Jackson streaming
  • Go:GOMAXPROCS 别设成 1(容器里默认是宿主机核数,K8s 里可能因为 CPU limit 变成 1);pprof 抓 CPU profile curl localhost:6060/debug/pprof/profile?seconds=30 > cpu.prof
  • PHP-FPM:pm.max_children 是不是太小,pm = ondemand 冷启动慢;查 max_execution_time

6. 根因 3:upstream 连接池耗尽

这个跟 nginx-keepalive-502.md 那篇有部分重叠,但在 504 场景下表现形式不同:keepalive 池耗尽通常不直接报 502,而是让 nginx 等一条空闲连接等太久,最终撞上 proxy_read_timeout 报 504。

典型报错:

2026/10/26 02:15:33 [warn] 5040#0: *1234567 upstream timed out (110: Connection timed out)
while reading response header from upstream, client: 10.0.0.1, server: api.example.com,
request: "POST /api/search HTTP/1.1", upstream: "http://10.1.2.3:8080/api/search",
host: "api.example.com"

排查:

# 1. 看 nginx 到后端的 ESTAB 连接数
ss -tanp | grep :8080 | awk '{print $1}' | sort | uniq -c
# ESTAB 数 >> worker_processes × keepalive = 池满

# 2. 看 TIME_WAIT 是不是堆积(短连接导致)
ss -tan | grep TIME-WAIT | awk '{print $4}' | cut -d: -f2 | sort | uniq -c | sort -rn | head
# TIME_WAIT 多说明 keepalive 没生效,看 nginx-keepalive-502.md

# 3. 看后端 listen 队列
ss -ltn | grep :8080
# Recv-Q 接近 Send-Q 就是 listen backlog 快满了,新连接进不来

# 4. 看 fd 使用率
ls /proc/<nginx-pid>/fd | wc -l
cat /proc/<nginx-pid>/limits | grep 'open files'
# fd 数接近 max 就是 fd 泄漏或者配少了

修复:

upstream backend {
    server 10.1.2.3:8080;
    server 10.1.2.4:8080;
    server 10.1.2.5:8080;

    keepalive 64;                 # 每 worker 池大小
    keepalive_requests 1000;      # 单连接复用上限
    keepalive_timeout 65s;        # 比后端 60s 短一点
}

server {
    location /api/ {
        proxy_pass http://backend;
        proxy_http_version 1.1;                   # keepalive 必须 1.1
        proxy_set_header Connection "";            # 清掉 Connection: close
    }
}

7. 根因 4:upstream SSL handshake 超时

后端是 HTTPS 时,TLS 握手也会算进 proxy_read_timeout。TLS 1.3 握手是 1-RTT,TLS 1.2 是 2-RTT,跨机房或者跨境链路可能一次握手就花掉 300ms。加上 OCSP stapling、证书链验证、SNI 匹配,慢的时候一次握手 2-3 秒不罕见。

典型报错:

SSL_do_handshake() failed (SSL: error:14094410:SSL routines:
SSL3_READ_BYTES:sslv3 alert handshake failure) while SSL handshaking to upstream,
client: 10.0.0.1, upstream: "https://10.1.2.3:8443", host: "api.example.com"

排查:

# 1. openssl 直接测握手时间
openssl s_client -connect 10.1.2.3:8443 -servername api.example.com -brief \
  -cipher 'TLSv1.3' < /dev/null 2>&1 | grep -i "handshake\|verified"

# 2. 测 TLS 1.2 vs 1.3 耗时差异
for v in tls1 tls1_1 tls1_2 tls1_3; do
    echo -n "$v: "
    time openssl s_client -connect 10.1.2.3:8443 -$v < /dev/null > /dev/null 2>&1
done

# 3. 看证书链长度(链越长验证越慢)
openssl s_client -connect 10.1.2.3:8443 -showcerts 2>/dev/null | grep -c "BEGIN CERTIFICATE"

# 4. 看 OCSP 状态
openssl ocsp -CAfile chain.pem -issuer issuer.pem -cert leaf.pem \
  -url http://ocsp.example.com -verify

修复:

location /api/ {
    proxy_pass https://backend;
    proxy_ssl_server_name on;                    # SNI 开启
    proxy_ssl_name api.example.com;              # 匹配后端证书 CN/SAN
    proxy_ssl_protocols TLSv1.2 TLSv1.3;         # 明确协议
    proxy_ssl_ciphers HIGH:!aNULL:!MD5;
    proxy_ssl_verify off;                        # 内网场景可以关,公网必须开
    proxy_ssl_session_reuse on;                  # 会话复用,二次握手快
}

一点坦承:proxy_ssl_verify off 在内网是可以接受的,但我会强烈建议至少保留 proxy_ssl_verify_depth 2,防止中间人被替换。生产环境用证书校验的开销其实很小,别为了省那点 CPU 让链路不安全。

8. 根因 5:后端 DNS 解析慢

这个 504 比较冷门但排查起来最快。症状:只在新 upstream 连接时出现(比如冷启动、连接池清空后),持续 3-10 秒。

proxy_pass http://backend.example.com 里 backend.example.com 是域名的话,nginx 会用系统 DNS 解析。如果 /etc/resolv.conf 里配了远端 DNS 或者 systemd-resolved 转发有问题,解析就慢。

排查:

# 1. 直接测系统 DNS 延迟
time nslookup backend.example.com
time dig backend.example.com +short
time getent hosts backend.example.com

# 2. 看 nginx 是不是在用自己的 resolver
nginx -T 2>/dev/null | grep -E '^resolver'
# 没配的话就用系统 DNS,配了的话按 nginx 自己的走

# 3. 看 resolv.conf 配置
cat /etc/resolv.conf
# 有 options timeout:2 attempts:5 就是每次解析最多 2*5=10 秒

# 4. systemd-resolved 排查(Ubuntu/Debian)
systemd-resolve --status
resolvectl query backend.example.com
journalctl -u systemd-resolved | tail -30

修复:

# 全局层配 nginx 自己的 resolver
http {
    resolver 127.0.0.53:53 valid=10s ipv6=off;   # 127.0.0.53 是 systemd-resolved
    resolver_timeout 2s;
    resolver_ipv4_only on;

    server {
        location / {
            proxy_pass http://backend.example.com;   # 变量形式才能触发动态解析
        }
    }
}

变量形式是关键——写成 proxy_pass http://backend.example.com 会在 nginx 启动时解析一次,之后不重解析;写成 set $backend backend.example.com; proxy_pass http://$backend; 才会每次都用 nginx 自己的 resolver 动态解析(配合 upstream 的 zone 指令可以做 5 秒 TTL 的本地缓存)。

9. 根因 6:后端慢查询 / 慢 IO

这是"根因 2(CPU 打满)"的兄弟——CPU 不高但响应慢,通常卡在 IO 上。数据库慢查询、ES 慢聚合、磁盘 fsync 慢、NFS 抖动,全都是这里。

排查:

# 1. 系统层看 IO 压力
iostat -x 1 5
# %util 接近 100、await 高(>50ms)就是磁盘打满
# aqu-sz(等待队列)持续大于 1 就是排队严重

# 2. 看是谁在等 IO
iowait 高时:
pidstat -d 1 5                       # 每个进程的 IO
iotop -a                              # 交互式看 IO 大户

# 3. MySQL 慢查询
mysql -e "SHOW FULL PROCESSLIST;" | awk '$6 > 10 {print}'
tail -100 /var/log/mysql/slow.log
# 或者
pt-query-digest /var/log/mysql/slow.log

# 4. PostgreSQL
psql -c "SELECT now()-xact_start AS duration, query
         FROM pg_stat_activity WHERE state='active' ORDER BY duration DESC;"

# 5. ES 慢查询
curl -s 'localhost:9200/_cat/tasks?v&h=action,running_time,state' \
  | awk '$3 > "01:00:00"'

# 6. 应用层看是不是卡在锁
strace -p <pid> -e trace=futex -c     # futex 多 = 锁竞争

修复思路:

  • 数据库:加索引、拆 SQL、拆表、读写分离、加连接池(比如 HikariCP 的 maximumPoolSize)
  • ES:慢聚合改成预聚合、加 ILM 淘汰旧索引、扩容数据节点
  • 磁盘:换 NVMe、拆盘、用 tmpfs 放缓存
  • 应用:分页查、异步化、加缓存(Redis/Memcached)

10. 30 条 504 速查命令

一份可以从 504 报警直接开始扫的命令清单,按排查顺序组织。

# ===== 1-5:确认问题范围 =====
1)  awk '$9==504' /var/log/nginx/access.log | tail -20
2)  awk '$9==504 {print $NF}' /var/log/nginx/access.log | sort -n | uniq -c | tail
3)  tail -50 /var/log/nginx/error.log | grep -i "504\|timed out"
4)  grep "504" /var/log/nginx/error.log | awk -F'"' '{print $6}' | cut -d'?' -f1 | sort | uniq -c | sort -rn | head
5)  grep " 504 " /var/log/nginx/access.log | awk '{print $4}' | sort | uniq -c | sort -rn | head

# ===== 6-10:根因 1 timeout 配置 =====
6)  nginx -T 2>/dev/null | grep -E 'proxy_(read|send|connect|next_upstream)_timeout'
7)  nginx -T 2>/dev/null | grep -E '^(\s*)proxy_pass ' | head
8)  tail -100000 /var/log/nginx/access.log | awk '$9!=504 {print $NF}' | sort -n | awk '{a[NR]=$1} END{print "P50:",a[int(NR*0.5)],"P99:",a[int(NR*0.99)],"P999:",a[int(NR*0.999)]}'
9)  nginx -t 2>&1 | tail -5
10) grep -E "upstream timed out" /var/log/nginx/error.log | wc -l

# ===== 11-15:根因 2 CPU 打满 =====
11) top -bn1 | head -20
12) mpstat -P ALL 1 3
13) pidstat -u 1 3 | tail -20
14) perf top -p $(pgrep -f 'your_app' | head -1)
15) ps -eo pid,ppid,user,%cpu,%mem,cmd --sort=-%cpu | head -10

# ===== 16-20:根因 3 连接池 =====
16) ss -tanp | grep ESTAB | awk '{print $4}' | rev | cut -d: -f2- | rev | sort | uniq -c | sort -rn | head
17) ss -tan | grep TIME-WAIT | wc -l
18) ss -ltn | grep -E ':(80|443|8080|8443)\b'
19) cat /proc/$(pgrep -f "nginx: worker" | head -1)/limits | grep "open files"
20) ls /proc/$(pgrep -f "nginx: worker" | head -1)/fd | wc -l

# ===== 21-24:根因 4 SSL 握手 =====
21) openssl s_client -connect 10.1.2.3:8443 -servername api.example.com -brief < /dev/null 2>&1 | grep -iE "handshake|verified|protocol"
22) time openssl s_client -connect 10.1.2.3:8443 -tls1_3 < /dev/null > /dev/null 2>&1
23) openssl s_client -connect 10.1.2.3:8443 -showcerts 2>/dev/null | grep -c "BEGIN CERTIFICATE"
24) grep -i "ssl_do_handshake\|SSL_do_handshake" /var/log/nginx/error.log | tail -5

# ===== 25-27:根因 5 DNS =====
25) time getent hosts backend.example.com
26) resolvectl query backend.example.com
27) nginx -T 2>/dev/null | grep -E '^(\s*)resolver'

# ===== 28-30:根因 6 IO / 慢查询 =====
28) iostat -x 1 3
29) iotop -bn1 2>/dev/null | head -15
30) mysql -uroot -e "SHOW FULL PROCESSLIST;" 2>/dev/null | awk '$6 > 10 {print $1,$6,$10}' | head

11. FAQ

Q1: 504 和 502 到底啥区别?

一句话:502 是"后端死了或响应错了",504 是"后端没回话"。展开说,502 表示 nginx 已经建立了到后端的 TCP 连接,甚至可能收到了一些字节,但那些字节不合法——非法的状态行、非法的响应头、或者中途被 RST;504 表示 nginx 已经把请求发出去了,进入 read 阶段,在 proxy_read_timeout 到期前一个字都没等到。实操上最快的区分办法是看 access log 的 upstream_response_time 字段:如果这个值精确等于 proxy_read_timeout(比如配 60s 就是 60.000),基本可以断定是 504;如果是随机的小数值(比如 0.001、0.034),配合 $status = 502,就是后端崩了或者协议错了。

Q2: proxy_read_timeout 设多大才合理?

不要拍脑袋调大,也不要盲目调小。正确姿势是先看后端的真实响应时间分布:从 access log 里 grep 掉 504 的行(那些是被 timeout 卡死的假象),然后对 $request_time 或者 $upstream_response_time 做分位数统计,看 P50、P99、P999。proxy_read_timeout 应该设成 P99 的 2-3 倍,留够 buffer 应对偶发抖动。经验值:小 API 8-10s、复杂搜索 20-30s、报表类 60-120s。10 秒是万金油默认值,2 秒以下通常意味着后端真的在慢,先查后端再调 timeout。把 timeout 从 60s 调到 300s 只是把 504 换成了慢响应,用户体验并不会变好,监控看板还变好看了——是典型的自欺欺人。

Q3: 504 报错里 upstream timed out 是什么意思?

字面翻译是"上游超时",但要看上下文。nginx error.log 里常见的完整报错是 upstream timed out (110: Connection timed out) while reading response header from upstream,这里的 "timed out" 指的是读超时(read timeout),不是连接超时(connect timeout)。也就是说,nginx 已经连上后端了,把请求发出去了,正在等后端返回响应头,结果等到 proxy_read_timeout 到期还没等到。可能的原因有两类:一是后端真的处理慢(占 90%),二是中间链路丢包严重导致 TCP 层的 ACK 或者数据段一直没到 nginx(占 10%,多见于跨机房、跨境链路)。要区分这两者,最直接的办法是在 nginx 机器和后端机器同时 tcpdump:tcpdump -i any port 8080 -w /tmp/backend.pcap,然后看抓包里的 TCP 序列号是不是有大的空洞、重传是不是密集。

Q4: 改了 proxy_read_timeout 需要重启 nginx 吗?

不需要重启,用 nginx -s reload 平滑重载就行。reload 的原理是 master 进程收到信号后会启动一批新的 worker 进程,用新配置接管新连接;旧 worker 进程处理完手上存量请求后退出,不会强杀任何请求。整个过程零中断,客户端无感。前提是 nginx -t 语法检查通过——如果配置有语法错误,reload 会失败但旧的 worker 继续跑,业务不受影响。也可以用 HTTP API:curl -X POST http://127.0.0.1/reload(需要配 location = /reload { access_log off; allow 127.0.0.1; deny all; auth_basic "x"; return 200; } 之类的接口,或者用 stub_status + 独立脚本),生产环境更常用的是直接执行 systemctl reload nginx。

Q5: backend 是 Node.js 服务也常报 504 怎么办?

Node.js 单线程模型在两种情况下会集体翻车:一是 CPU 密集任务(JSON 序列化大对象、正则回溯、加密运算)把 event loop 阻塞住,二是 GC pause 太长。这两种都会导致 keep-alive 心跳响应延迟,nginx 侧看就是超时。排查先用 clinic.js(clinic doctor -- node app.js)看 event loop lag,或者直接 node -e "const t=process.cpuUsage(); setInterval(()=>console.log(process.cpuUsage(t)),100)" 打点。修复方向:一、启动时加 node --max-old-space-size=2048 app.js 限制堆大小,避免 V8 老年代堆积触发长 GC;二、用 PM2 集群模式 pm2 start app.js -i max 跑满所有 CPU 核;三、把 CPU 密集的任务扔到 worker_threads 或者独立的服务进程,主进程只做 I/O;四、给 server 加 requestTimeout(18 秒默认,可以按需调低)避免慢请求长期占用连接。