Skip to main content

503 Service Unavailable 排障实战:从 CDN、网关到应用层逐层定位与恢复

September 27, 2026
给出 503 错误的分层排障路径:三分钟分流确定返回层,逐层排查 CDN/WAF、Nginx 限流与上游、PHP-FPM 与 Node 运行时、容器编排与数据库连接池,覆盖维护模式的正确姿势、日志关键词清单、重试风暴与预防措施。
503 Service Unavailable 排障实战:从 CDN、网关到应用层逐层定位与恢复

更新日期:2026-09-27

503(Service Unavailable)是生产环境里最"冤枉"的一类错误:它不代表代码写错了,而是服务在某个环节主动声明"我现在暂时无法处理请求"。正因为它是主动返回的,返回 503 的可能是 CDN、负载均衡、反向代理、应用容器中的任何一层——不搞清楚"是谁在返回 503",修复就无从下手。

本篇按照"先分流、再逐层"的顺序,给出一套可复用的 503 排障路径,覆盖 CDN/WAF、反向代理、应用运行时、容器编排四层,并给出维护模式下正确返回 503 的方式与预防措施。文中命令以 Linux 服务器为默认环境。

一、503 的语义:它和 500、502、504 有什么区别

排 503 之前,先把 5xx 家族的关系理清楚。四类错误经常被混为一谈,但排查入口完全不同:

状态码语义典型场景排查入口
500服务端内部错误代码异常、未捕获异常应用日志的异常堆栈
502 Bad Gateway网关收到了上游的无效响应上游进程崩溃、返回畸形响应网关与上游之间的连接
503 Service Unavailable服务暂时不可用过载、维护、上游全部不健康、限流返回 503 的那一层
504 Gateway Timeout网关等上游超时上游卡死、慢查询、超时配置过小上游耗时与超时配置

一句话概括:500 是"应用自己报错",502/504 是"网关对上游不满",而 503 是"链路中某一环明确说暂时不接客"。HTTP 语义上,503 还应该携带 Retry-After 响应头,告诉客户端多久后可以重试——这也是区分"有序维护"和"无序崩溃"的第一个线索。

另外要注意:如果你的站点套了 Cloudflare,浏览器里看到的 52x 错误页(520、521、522、523、525 等)虽然长得像 503,但它们是 Cloudflare 自定义的错误码,含义各不相同(例如 521 是源站拒绝连接、522 是源站连接超时)。见到 52x 时,排查重点在源站可达性,而不是应用本身。

二、三分钟分流:先确定是谁在返回 503

收到 503 报告后,先做四个快速检查,基本可以锁定问题层:

1. 看响应头。 用 curl 直接看原始响应:

curl -sI https://www.mf8.biz/some-page

关注三件事:Server 头(是 cloudflare 还是 nginx)、是否有 Retry-After、是否有 CF-Ray 之类的 CDN 追踪头。如果 Server: cloudflare 且错误页是 Cloudflare 样式,问题大概率在源站或 Cloudflare 配置;如果是 Nginx 默认错误页样式,问题在网关或上游。

2. 绕过 CDN 直连源站。 如果你有源站 IP,可以临时用 Host 头直连测试:

curl -sI -H "Host: www.mf8.biz" https://<源站IP>/some-page -k

直连正常、走 CDN 报错,问题在 CDN/WAF 配置;直连也报 503,问题在网关或应用层。

3. 确认影响面。 是全站所有路径都 503,还是只有某个接口?全站 503 通常指向网关层或进程级故障;单接口 503 更可能是该接口对应的 upstream、线程池或依赖服务的问题。

4. 看监控曲线的起点。 5xx 率是什么时候开始涨的?涨之前有没有发布、扩容、证书更换、上游配置变更?时间点对齐变更记录,往往一步就定位了。

分流时还要警惕重试风暴:503 一旦出现,客户端(浏览器、App、上游微服务)的重试会把流量放大数倍,让本来轻微的过载雪崩成全面不可用。判断方法是对比请求量的 QPS 曲线与 503 曲线是否同步跳涨。缓解手段是在客户端做指数退避(exponential backoff)加抖动(jitter),在服务端优先返回 429 + Retry-After 引导客户端按节奏重试——这也是把限流从 503 改成 429 的另一个理由:语义正确的状态码才能换来正确和退避行为。

三、CDN / WAF 层的 503

这一层的 503 有几个常见来源:

速率限制与防刷规则。 Cloudflare 的 Rate Limiting 规则触发时会返回 429 或自定义状态,但 WAF 托管规则或自定义规则也可能直接拒绝。检查 Cloudflare 控制台的 Security 事件流,按时间过滤就能看到被拦截的请求与命中的规则。

源站健康检查失败。 如果启用了 Cloudflare Load Balancer 或其他带健康检查的负载均衡,源站探测失败后流量会被摘除,此时返回 503 或自定义错误页。健康检查路径的选择很关键:如果探测路径指向一个会因缓存失效而变慢的动态页面,会造成"健康抖动"。探测路径应该是一个轻量、无依赖、无缓存的专用端点。

52x 家族错误的定位。 如前所述,521/522/523 指向源站网络层:源站防火墙把 Cloudflare IP 段挡了(521)、源站响应太慢(522)、DNS 指向了不存在的源站(523)。处理方式是核对源站防火墙/安全组是否放行了官方公布的 IP 段、源站负载是否饱和、DNS 记录是否正确。

回源协议不匹配。 SSL/TLS 模式设置为 Strict 而源站只有自签证书时会出现 526;源站 80 端口关闭而回源走 HTTP 时也会不可达。核对回源协议与源站监听端口是否一致。

这一层排查完成后仍无法解释 503 时,就进入网关层。

四、反向代理层:Nginx 的三类 503

Nginx 返回 503 主要有三个原因,各自的日志特征非常明确:

1. 上游全部不可用(no live upstreams)。 当 upstream 块里所有节点都被标记失败时,Nginx 返回 503,error.log 中会出现:

upstream server temporarily disabled while connecting to upstream
no live upstreams while connecting to upstream

处理方向:确认每个上游节点是否真的存活(逐个 curl 上游健康端点)、max_fails 与 fail_timeout 的组合是否过于敏感(默认 1 次失败即摘除 10 秒)、是否缺少 backup 节点。如果节点本身活着但健康检查路径返回慢,考虑给该路径加 proxy_connect_timeout 与更宽松的判定。

2. 限流模块触发(limit_req / limit_conn)。 一个容易踩的坑:limit_req 超限时的默认返回状态就是 503。这意味着"用户访问太快"这种本该是 429 的场景,会以 503 的面目出现在监控里,干扰排障判断。更语义化的做法是显式指定:

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;

server {
    location /api/ {
        limit_req zone=api burst=20;
        limit_req_status 429;   # 超限返回 429 而不是 503
    }
}

如果监控里 503 集中在某个高频接口,先查是不是限流配置在起作用——error.log 里会有 limiting requests, excess: ... by zone "api" 的记录。

3. 显式维护页。 有些团队用 return 503 加错误页模板做维护模式,这在排障时要先排除——确认一下这段时间有没有人执行过维护配置的上线。

五、应用运行时层的 503

PHP-FPM:进程池打满。 这是经典中的经典。当所有 FPM worker 都被慢请求占住时,Nginx 与 FPM 之间的排队超限,返回 503。PHP-FPM 日志里会出现明确证据:

server reached pm.max_children setting (5), consider raising it

处理步骤:先用 pm.status_path 打开状态页,观察 active processes 是否长期等于 pm.max_children;然后按内存估算上限——单进程平均内存乘以 pm.max_children 不应超过分配给 FPM 的内存总量,盲目调大只会把 503 变成 OOM;最后回到源头,找出为什么请求变慢(慢查询、外部 API 卡顿),worker 被占住只是症状。

Node.js:事件循环阻塞与健康检查失败。 单进程的 Node 服务一旦事件循环被同步计算或大量同步 IO 堵住,健康检查端点也会超时,编排系统或负载均衡随即摘除节点并返回 503。排查方向:进程内接入事件循环延迟监控(定期 setTimeout(0) 测量漂移)、检查是否有大 JSON 同步序列化、CPU 密集任务是否该挪到 worker 线程或独立服务。用 PM2 时注意 max_memory_restart 触发的频繁重启也会表现为间歇性 503。

优雅停机期的 503。 滚动发布时,旧实例进入关闭流程但仍有流量进来,如果网关没有做连接排水(connection draining),就会出现发布期间的 503 尖峰。修复方式是在负载均衡层配置健康检查与排水窗口,应用侧正确处理 SIGTERM:停止接收新连接、处理完存量请求再退出。

六、容器与编排层的 503

Docker Compose:healthcheck 与 depends_on。 depends_on 默认只保证启动顺序,不保证"依赖服务已就绪"。如果应用启动时依赖的数据库还没就绪,应用反复崩溃重启,网关侧看到的就是 503。给依赖加上 condition: service_healthy 与合理的 healthcheck。

Kubernetes:三类常见 503。 一是 liveness probe 配置过严,应用启动慢于探针窗口,Pod 被反复杀掉重建,Service 后端始终没有 Ready 端点——此时 Ingress 返回 503,修复方向是加 initialDelaySeconds 或改用 startup probe;二是 Pod 处于 OOMKilled 循环,kubectl describe pod 里能看到退出原因,需要调整内存 limit 或排查内存泄漏;三是 HPA 已扩到 maxReplicas 仍然不够,需要评估容量上限或优化单实例吞吐。

排查时一条很有用的命令:

kubectl get endpoints <service-name>

如果 endpoints 为空,说明没有 Ready 的 Pod,503 是必然结果——问题在 Pod 为什么不 Ready,而不是在 Ingress。

七、维护模式:正确地返回 503

503 不总是故障,维护窗口就应该用它。但要做得规范:

返回 503 的同时带上 Retry-After 头,告诉客户端和爬虫预计恢复时间;维护页本身要轻(静态 HTML,不要依赖数据库和应用,否则维护页自己也会挂);对搜索引擎而言,短时间的 503 不会被降权——爬虫会稍后重试,Retry-After 会被尊重,但如果维护持续数天,已收录页面可能被临时移出索引,所以长时间维护应使用 302 跳转到临时公告页而非长时间裸 503。

Nginx 下一个最小可用的维护模式配置:

server {
    listen 80;
    server_name www.mf8.biz;

    # 运维白名单,按需放开
    allow 203.0.113.0/24;
    deny all;

    error_page 503 /maintenance.html;
    location = /maintenance.html {
        root /usr/share/nginx/html;
        internal;
    }

    location / {
        return 503;
    }
}

八、日志排查清单

把各层日志位置和关键词整理成一张速查表,503 排障时按层对照:

层日志位置关键词
Nginxerror.logno live upstreams、limiting requests、upstream temporarily disabled
PHP-FPMFPM pool 日志reached pm.max_children、server busy
Node.js应用日志 / APMevent loop lag、ECONNREFUSED、health check timeout
systemd 服务journalctl -u <服务名>Failed、Start-request repeated too quickly
Dockerdocker logs <容器>restart 循环、健康检查失败输出
Kuberneteskubectl describe / eventsUnhealthy、OOMKilled、Readiness probe failed
Cloudflare控制台 Security/Analytics规则命中、52x 分布

一个实用的习惯:把 tail -f 各层日志的命令写进运维手册,事故时按顺序贴到多个终端窗口,时间戳一对照,请求在哪一层被拒一目了然。

九、恢复之后:验证与复盘

确认修复后,不要只看"页面能打开了":

用 curl -sI 确认状态码回到 200 且 Retry-After 已消失;观察监控上 5xx 率曲线归零,并确认没有从 503 变成 502/504(那是问题换了一层);如果是限流触发的 503,评估一下阈值是否合理——保护机制生效不算故障,但把它标成 429 才是正确姿势;如果是容量问题,把本次峰值流量、进程数、内存水位记入容量档案,作为下次扩容的依据。

复盘时回答三个问题:哪一层最先出问题?为什么监控没有在用户报障前告警(5xx 率告警阈值、健康检查覆盖)?同类问题能不能被自动化预案覆盖(自动摘除、自动扩容、维护页自动切换)?

十、预防:让 503 少发生的三件事

容量红线监控。 每个无状态服务都应有明确的容量指标——FPM 的 pm.max_children 使用率、Node 的事件循环延迟、连接池等待队列——在到达红线前告警,而不是等 503 出现才发现。

发布流量隔离。 滚动发布配合健康检查与连接排水,发布窗口的 503 尖峰应接近于零。发布脚本里加一步"发布后健康验证",失败自动回滚。

降级预案。 对非核心路径准备静态降级页(缓存命中优先,可结合 CDN 边缘缓存延长 TTL),核心依赖故障时宁可返回带 Retry-After 的缓存内容,也不把用户直接抛给 503。缓存层的治理思路可以进一步参考我们此前的 CDN 缓存命中率优化一文。

七、数据库与连接池:被忽视的 503 源头

应用进程还活着、健康检查也通过,但请求依然 503——很多时候是依赖的数据库连接池耗尽了。应用从池里拿不到连接,请求在队列里等待,等待超过应用设定的排队上限(或网关的超时)后,上游整体表现为不可用。

典型证据链:应用日志出现 connection pool exhausted、Timeout: Request timed out waiting for connection(不同语言框架措辞不同,关键词是 pool/exhausted/timeout 的组合);同时数据库侧的连接数逼近 max_connections。

处理顺序:先看有没有连接泄漏(借出连接未归还,常见于异常路径没有释放);再检查长事务(一个慢事务占住连接链式拖垮整个池);最后才是调大池上限——连接池上限和数据库的 max_connections、内存要联动计算,应用侧池子之和超过数据库上限,会把问题变成连接风暴。

八、传统运行时的线程池信号

PHP-FPM 和 Node 之外,两个常见运行时的对应信号也值得列入清单:

Java(Tomcat/Netty):Tomcat 的 maxThreads 打满后新请求进入 accept 队列,队列也满时拒绝连接。观察 server.tomcat.threads.busy 指标与线程 dump(jstack)里 BLOCKED 线程的占比;慢 SQL 和下游 HTTP 调用没有超时是最常见的两个根因。

Go:Go 的 net/http 没有传统线程池概念,但 goroutine 泄漏会让内存和调度延迟持续上涨,最终健康检查超时。pprof 的 goroutine profile 是定位泄漏点的标准工具,重点看数量随时间增长的同类调用栈。

这些运行时层的共同规律是:503 是症状,慢才是病。找到"什么变慢了",比调大任何池子都有效。

九、监控与告警:让 503 在用户报障之前被发现

等用户报障才知道 503,意味着监控缺了三块拼图:

分层探针。 对"客户端 → CDN → 网关 → 应用"的每一层各放一个探测点:公网拨测盯全链路,网关本机 curl 127.0.0.1 盯应用层。两条曲线一对比,503 出现在哪一层一目了然。

分级告警阈值。 5xx 率的告警建议分两级:超过 1% 持续 5 分钟告 warning,超过 5% 持续 2 分钟直接 page。阈值要结合基线校准——正常的 5xx 率不是零(爬虫扫漏洞也会产生 404/5xx),阈值定在基线之上而不是拍脑袋的零容忍。

变更时间线对齐。 告警消息里带上最近的发布与配置变更记录(或至少打通跳转),值班同学看到 5xx 告警的第一眼就能回答"是不是刚才那次发布",这是缩短 MTTR 最便宜的手段。

十、常见误区清单

最后把排障时反复出现的误区集中列出,对号入座可以少走弯路:

  1. 把 429 当 503 排:Nginx 限流默认返回 503,先确认是不是限流,再谈容量(第二节已给出 limit_req_status 429 的修正)。
  2. 只盯着应用代码:503 多数时候不是代码 bug。先分层定位,再决定要不要读代码。
  3. 盲目调大连接池/进程数:不找到"为什么慢",调大只是推迟崩溃,还可能把内存打爆,503 变 OOM。
  4. 健康检查路径用了重页面:探测路径本身拖垮应用,造成健康抖动,这是"明明没流量也 503"的经典原因。
  5. 忽略 Retry-After:无论故障还是维护,带上这个头都让客户端与爬虫的行为更可控,成本只是一行配置。
  6. 维护页依赖了正在维护的系统:维护页必须是完全静态的,不带数据库查询、不带外部字体和统计脚本。

官方资料与继续阅读