Nginx 状态码说明

本文简要总结 Nginx 常见的状态码(Status Code)及其产生原因与排查思路。

请求处理流程回顾

在分析状态码之前,先回顾一下典型的 HTTP 请求处理流程:

image.png

一个普通的 HTTP 请求处理流程如下:

  1. Client -> Nginx (A):客户端发起请求给 Nginx。
  2. Nginx -> Upstream (B):Nginx 处理后,将请求转发到上游服务器(如 uWSGI),并等待结果。
  3. Upstream -> Nginx (C):上游服务器处理完请求后,返回数据给 Nginx。
  4. Nginx -> Client (D):Nginx 将处理结果返回给客户端。

每个阶段都有预设的超时时间。由于网络波动、机器负载、代码异常等原因,如果某个阶段未在预期时间内正常返回,会导致请求异常,进而产生不同的状态码。

1. 504 Gateway Timeout

504 主要针对 B、C 阶段(Nginx 与上游服务器通信阶段)。

一般 Nginx 配置中会有如下超时设置:

location / {
    ...
    uwsgi_connect_timeout 6s;
    uwsgi_send_timeout 6s;
    uwsgi_read_timeout 10s;
    uwsgi_buffering on;
    uwsgi_buffers 80 16k;
    ...
}

这代表 Nginx 与上游服务器(uWSGI)通信的超时时间。如果在规定时间内,uWSGI 没有响应,Nginx 则认为请求超时,返回 504 状态码。

日志示例:

  • access_log:

    [16/May/2016:22:11:38 +0800] 10.4.31.56 201605162211280100040310561523 15231401463407888908 10.*.*.* 127.0.0.1:8500 "GET /api/media_article_list/?count=10&source_type=0&status=all&from_time=0&item_id=0&flag=2&_=1463407896337 HTTP/1.1" 504 **.***.com **.**.**.39, **.**.**.60 10.000 10.000 "Mozilla/5.0 ..." ...
  • error_log:

    2016/05/16 22:11:38 [error] 90674#0: *947302032 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 10.6.19.81, server: **.***.com, request: "GET /api/media_article_list/... HTTP/1.1", upstream: "http://127.0.0.1:8500/...", host: "mp.toutiao.com"

分析:
error_log 中出现 upstream timed out (110: Connection timed out) while reading response header from upstream,意味着在规定时间内,Nginx 没有从上游服务器的响应头中拿到数据,即 uWSGI 未返回任何数据。

2. 502 Bad Gateway

502 同样主要针对 B、C 阶段。产生 502 时,error_log 中通常会有以下几种典型内容:

  • recv() failed (104: Connection reset by peer) while reading response header from upstream
  • upstream prematurely closed connection while reading response header from upstream
  • connect() failed (111: Connection refused) while connecting to upstream

这些都代表在 Nginx 设置的超时时间内,上游 uWSGI 没有给出正确的响应(但有响应动作,否则会变成 504 超时),因此 Nginx 返回 502

日志示例:

  • access_log:

    [16/May/2016:16:39:49 +0800] 10.4.31.56 201605161639490100040310562612 2612221463387989972 10.6.19.81 127.0.0.1:88 "GET /articles/?source_type=0 HTTP/1.1" 503 **.***.com **.**.**.4, **.**.**.160 0.000 0.000 "Mozilla/5.0 ..."

    注意:access_log 中显示的是 503,原因见下文容灾机制说明。

  • error_log:

    2016/05/16 16:39:49 [error] 90693#0: *944980723 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 10.6.19.80, server: **.***.com, request: "GET /articles/ HTTP/1.1", upstream: "http://127.0.0.1:8500/...", host: "**.***.com"

关于 access_log 中出现 503 的说明

这是因为 Nginx upstream 的容灾机制。如果配置了 backup 服务器及失败判定策略:

upstream app_backup {
    server 127.0.0.1:8500 max_fails=3 fail_timeout=5s;
    server 127.0.0.1:88 backup;
}
  • max_fails=3:说明尝试 3 次后,会认为 127.0.0.1:8500 失效,于是进入 127.0.0.1:88 backup

Nginx upstream 默认判断失败节点状态以 connect refusetime out 为准。如果在 location 里加了以下配置:

proxy_next_upstream error http_502;
proxy_connect_timeout 1s; 
proxy_send_timeout 6s; 
proxy_read_timeout 10s;
proxy_set_header Host $host;
  • 该配置表示:对于 HTTP 状态是 502 的情况,也会触发 upstream 的容灾机制。
  • 逻辑概括:如果连续有 3 次 (max_fails=3) 状态为 502 的请求,则认为后端 127.0.0.1:8500 挂掉。在接下来的 5s (fail_timeout=5s) 内,请求会转发到 backup 服务器 127.0.0.1:88

查看 88 端口的配置:

server {
    listen 88;
    access_log /var/log/nginx/failover.log;
    expires 1m;
    error_page  500 502 503 504 /500.html;
    location / {
        return 503;
    }
    location = /500.html {
        root /**/**/**/nginx/5xx/;
    }
}

这意味着对于访问 88 端口的请求,Nginx 会直接返回 503 状态码,同时返回指定路径下的 500.html 文件。因此,在 access_log 中看到的是 503。

3. 499 Client Closed Request

客户端发送请求后,如果在规定时间内(假设超时时间为 500ms)没有拿到 Nginx 给的响应,则认为请求超时,客户端会主动结束连接。此时 Nginx 的 access_log 会打印 499 状态码。

  • 流程:A+B+C+D > 500ms。
  • 后果:此时 Server 端有可能还在处理请求,只不过 Client 断掉了连接,处理结果无法返回。
  • 风险:如果 499 较多,可能会引起服务雪崩。客户端因某些原因处理慢,未在规定时间内返回数据,认为请求失败中断连接,然后重新发起请求。不断重复会导致服务端请求堆积,负载变大,处理更慢,最终无法响应任何请求。

官方说明:
Nginx 返回 499 的情况是由于 client has closed connection(客户端主动关闭了连接)。

解决方案:

  1. 配置忽略客户端中断

    proxy_ignore_client_abort on;
  2. 优化后端程序
    还有一种原因是确实客户端关闭了连接或连接超时。主要是因为 PHP 进程数太少,或 PHP 进程占用资源不能很快释放,导致请求堆积。这种情况需要在程序上做优化。

4. 500 Internal Server Error

500 代表服务器内部错误,即服务器遇到意外情况而无法执行请求。

常见原因:

  • Web 脚本错误(如 PHP 语法错误、Lua 语法错误等)。
  • 访问量过大时,由于系统资源限制,无法打开过多的文件句柄(Too many open files)。

排查与解决:

  • 查看 Nginx 及 PHP 的错误日志。
  • 如果是 too many open files

    • 修改 Nginx 的 worker_rlimit_nofile 参数。
    • 使用 ulimit 查看系统打开文件限制。
    • 修改 /etc/security/limits.conf
  • 如果脚本存在问题:修复脚本错误并优化代码。
  • 如果各种优化后仍出现 too many open files:考虑做负载均衡,将流量分散到不同服务器。

5. 503 Service Unavailable & 常见状态码汇总

503 是服务不可用的返回状态。在 Nginx 配置中,如果设置了 limit_req 流量限制,可能导致许多请求返回 503。在限流条件下,为提高用户体验,有时希望返回正常 Code 200 且返回操作频繁的信息,但这需要具体业务配合。

以下是 HTTP 常见状态码分类汇总:

1xx - 信息提示

  • 100-199:用于指定客户端应相应的某些动作。

2xx - 成功

  • 200-299:用于表示请求成功。

    • 200 (成功):服务器已成功处理了请求。通常表示服务器提供了请求的网页。
    • 201 (已创建):请求成功并且服务器创建了新的资源。
    • 202 (已接受):服务器已接受请求,但尚未处理。
    • 203 (非授权信息):服务器已成功处理了请求,但返回的信息可能来自另一来源。
    • 204 (无内容):服务器成功处理了请求,但没有返回任何内容。
    • 205 (重置内容):服务器成功处理了请求,但没有返回任何内容。
    • 206 (部分内容):服务器成功处理了部分 GET 请求。

3xx - 重定向

  • 300-399:用于已经移动的文件并且常被包含在定位头信息中指定新的地址信息。

    • 300 (多种选择):针对请求,服务器可执行多种操作。
    • 301 (永久移动):请求的网页已永久移动到新位置。
    • 302 (临时移动):服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置。
    • 303 (查看其他位置):请求者应当对不同的位置使用单独的 GET 请求来检索响应。
    • 304 (未修改):自从上次请求后,请求的网页未修改过。
    • 305 (使用代理):请求者只能使用代理访问请求的网页。
    • 307 (临时重定向):服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置。

4xx - 客户端错误

  • 400-499:用于指出客户端的错误。

    • 400 (错误请求):服务器不理解请求的语法。
    • 401 (未授权):请求要求身份验证。
    • 403 (禁止):服务器拒绝请求。
    • 404 (未找到):服务器找不到请求的网页。
    • 405 (方法禁用):禁用请求中指定的方法。
    • 408 (请求超时):服务器等候请求时发生超时。
    • 413 (请求实体过大):请求实体过大,超出服务器的处理能力。
    • 414 (请求的 URI 过长):请求的 URI 过长,服务器无法处理。

5xx - 服务器错误

  • 500-599:用于支持服务器错误。

    • 500 (服务器内部错误):服务器遇到错误,无法完成请求。
    • 501 (尚未实施):服务器不具备完成请求的功能。
    • 502 (错误网关):服务器作为网关或代理,从上游服务器收到无效响应。
    • 503 (服务不可用):服务器目前无法使用(由于超载或停机维护)。
    • 504 (网关超时):服务器作为网关或代理,但是没有及时从上游服务器收到请求。
    • 505 (HTTP 版本不受支持):服务器不支持请求中所用的 HTTP 协议版本。

6. proxy_intercept_errors 配置

proxy_intercept_errors 指令用于当上游服务器响应头回来后,根据响应状态码的值进行拦截错误处理,常与 error_page 指令结合使用。主要用在访问上游服务器出现错误的情况下。

配置实例:

upstream mianshi1 {
    server 192.168.1.33:8080 max_fails=3 fail_timeout=10s;
}

server {
    listen 443;
    server_name zp.wangshibo.com;
    ssl on;
    
    ### SSL log files ###
    access_log logs/zrx_access.log;
    error_log logs/zrx_error.log;
    
    ### SSL cert files ###
    ssl_certificate ssl/wangshibo.cer;
    ssl_certificate_key ssl/wangshibo.key;
    ssl_session_timeout 5m;
    
    error_page 404 301 https://zp.wangshibo.com/zrx-web/;
    
    location /zrx-web/ {
        proxy_pass http://mianshi1;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
        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_redirect off;
        proxy_intercept_errors on;
    }
}

参考链接


说明:文中部分日志示例来源于 2016 年,配置参数与具体业务场景可能随版本更新有所变化,请以实际生产环境及官方文档为准。