如何诊断和解决 Nginx 502 Bad Gateway 错误

通过检查错误日志、上游服务健康状态、套接字权限、代理设置、超时配置和防火墙来修复 Nginx 502 错误。

如何诊断和解决 Nginx 502 Bad Gateway 错误

Nginx 是一个强大且流行的 Web 服务器和反向代理,常用于提供静态内容、负载均衡流量以及将请求转发到各种上游应用服务器,如 PHP-FPM、Node.js、Python Gunicorn 或 Apache Tomcat。当 Nginx 在与这些上游服务器通信时遇到问题,通常会返回“502 Bad Gateway”错误。

从 Nginx 错误日志开始,然后验证上游进程是否正在运行、可访问并允许响应。

理解 Nginx 502 Bad Gateway 错误

502 Bad Gateway 错误表示 Nginx 作为反向代理,从上游服务器收到了无效响应。这意味着 Nginx 成功连接到了上游服务器,但收到了无响应、不完整的响应或无法理解的响应。关键在于,问题不在于 Nginx 本身,而在于 Nginx 试图与之通信的服务。

常见的上游服务器包括:

  • PHP-FPM:用于 PHP 应用程序(例如 WordPress、Laravel)。
  • Gunicorn/uWSGI:用于 Python 应用程序(例如 Django、Flask)。
  • Node.js:用于 JavaScript 应用程序。
  • Apache Tomcat:用于 Java 应用程序。
  • 其他 Web 服务器:例如提供特定内容的 Apache HTTP 服务器。

502 错误是一个关键指标,表明您的应用程序后端未正常运行或 Nginx 无法访问。

逐步诊断

解决 502 错误的关键是系统性的诊断。从最可能的原因开始,逐步深入调查。

1. 首先检查 Nginx 错误日志

您的 Nginx 错误日志是主要信息来源。它们通常包含关于 Nginx 为何无法与上游服务器通信的具体细节。

  • 位置:通常位于 /var/log/nginx/error.log
  • 命令:使用 tail -f 实时监控日志,同时尝试重现错误。
tail -f /var/log/nginx/error.log

需要关注的内容

  • connect() failed (111: Connection refused):表示上游服务器未在指定的地址/端口上监听,或者防火墙阻止了连接。
  • upstream timed out:上游服务器响应时间过长。
  • upstream prematurely closed connection:上游服务器在发送完整响应之前关闭了连接。
  • no live upstreams while connecting to upstream:Nginx 找不到任何配置的可用上游服务器。

2. 验证上游服务器状态

一旦从 Nginx 错误日志中获得线索,请检查上游应用服务器的状态。

  • 对于 PHP-FPM

    sudo systemctl status php8.2-fpm
    
  • 对于 Node.js/Python/其他自定义应用: 检查进程是否正在运行。

    ps aux | grep node
    ps aux | grep gunicorn
    

    如果使用进程管理器如 PM2(Node.js)或 Supervisor(通用),请检查其状态。

    pm2 status
    sudo supervisorctl status
    

如果服务未运行,请尝试启动它并检查其自身的日志以查找错误。

sudo systemctl start php8.2-fpm

3. 检查与上游服务器的网络连接

确保 Nginx 能够通过配置的端口或套接字路径访问上游服务器。

  • 对于 TCP/IP 连接(例如 127.0.0.1:8000: 使用 telnetnc(netcat)从 Nginx 服务器测试端口连通性。

    telnet 127.0.0.1 8000
    nc -vz 127.0.0.1 8000
    

    成功连接应显示 Connected to 127.0.0.1.succeeded!。如果挂起或显示 Connection refused,则上游服务未监听或防火墙阻止了连接。

  • 对于 Unix 套接字(例如 unix:/run/php/phpX.X-fpm.sock: 验证套接字文件是否存在且具有正确的权限。

    ls -l /run/php/phpX.X-fpm.sock
    

    Nginx 应对此套接字文件具有读写权限。Nginx 用户(例如 www-data)需要属于拥有该套接字的组(例如 www-dataphp-fpm)。

常见原因及解决方案

根据您的诊断步骤,以下是 502 错误的最常见原因及解决方法。

1. 上游服务器未运行或崩溃

原因:Nginx 尝试代理到的应用程序(例如 PHP-FPM、Gunicorn、Node.js 应用)未运行或已崩溃。

解决方案:启动或重启上游服务。

# 示例:PHP-FPM
sudo systemctl start php8.2-fpm
# 如果它正在运行但怀疑崩溃,请重启:
sudo systemctl restart php8.2-fpm

# 对于自定义应用程序,使用其特定的启动/重启命令

提示:确保您的上游服务配置为在系统启动时自动启动。对于 systemd 服务,使用 systemctl enable phpX.X-fpm

2. 上游服务器过载 / 资源耗尽

原因:上游服务器负载过高,内存或 CPU 耗尽,或达到进程限制,导致停止响应或拒绝新连接。

症状:Nginx 错误日志可能间歇性显示 connection refusedupstream timed out,尤其是在负载下。系统监控工具(tophtopfree -h)显示高资源使用率。

解决方案

  • 对于 PHP-FPM:在其配置文件(例如 /etc/php/X.X/fpm/pool.d/www.conf)中调整 PHP-FPM 池设置。

    • pm.max_children:同时存活的最大子进程数。
    • pm.start_servers:启动时创建的子进程数。
    • pm.min_spare_serverspm.max_spare_servers:控制保留的空闲子进程数。
    ; 动态进程管理示例
    pm = dynamic
    pm.max_children = 50
    pm.start_servers = 10
    pm.min_spare_servers = 5
    pm.max_spare_servers = 20
    
    • 如果脚本耗尽内存,请增加 php.ini 中的 memory_limit
  • 对于其他应用程序:增加工作进程数、线程数或尽可能分配更多内存。监控您应用程序的特定指标。

  • Nginx 超时:在 Nginx 配置中增加 proxy_connect_timeoutproxy_send_timeoutproxy_read_timeout 指令,但请理解,如果后端确实存在问题,这只会延迟错误的发生。

    http {
        ...
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        ...
    }
    

3. Nginx 中上游配置错误

原因:Nginx 配置为连接到错误的上游服务器 IP 地址、端口或 Unix 套接字路径。

症状:Nginx 错误日志在请求后立即显示 connect() failed (111: Connection refused)

解决方案:仔细检查您的 Nginx 服务器块配置(/etc/nginx/sites-available/your_site.conf)。

  • 对于 HTTP/HTTPS 上游

    location /app {
        proxy_pass http://127.0.0.1:8000; # 确保 IP 和端口正确
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    
  • 对于通过 Unix 套接字的 PHP-FPM

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/phpX.X-fpm.sock; # 验证此路径与 PHP-FPM 配置完全匹配
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    
  • 对于通过 TCP/IP 的 PHP-FPM

    location ~ \.php$ {
        fastcgi_pass 127.0.0.1:9000; # 验证 IP 和端口
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    

更改后,始终测试您的 Nginx 配置并重新加载/重启 Nginx:

nginx -t
systemctl reload nginx # 或者如果 -t 指示需要,则重启

4. PHP-FPM request_terminate_timeout 超时

原因:PHP 脚本执行时间超过了 PHP-FPM 中的 request_terminate_timeout 设置。Nginx 等待响应,但 PHP-FPM 终止了脚本,导致 Nginx 收到不完整的响应。

症状:Nginx 错误日志可能显示 upstream timed outscript timed out。PHP-FPM 日志可能显示 child XX exited on signal 9 (SIGKILL)

解决方案

  • 增加 request_terminate_timeout:在您的 PHP-FPM 池配置(www.conf)中,找到并调整此指令。将其设置为 0 可禁用超时,但通常不推荐,因为长时间运行的脚本可能会挂起资源。

    request_terminate_timeout = 300 # 增加到 5 分钟(300 秒)
    
  • 增加 Nginx 中的 fastcgi_read_timeout:此 Nginx 超时应等于或大于 request_terminate_timeout

    location ~ \.php$ {
        ...
        fastcgi_read_timeout 300s; # 必须 >= PHP-FPM 的 request_terminate_timeout
        ...
    }
    

警告:虽然增加超时可以解决 502 错误,但可能掩盖了潜在的性能问题。最好的长期解决方案是优化慢速的 PHP 脚本。

5. 防火墙问题

原因:防火墙(在 Nginx 服务器或上游服务器上,如果它们分开)阻止了到上游端口或套接字的连接。

解决方案

  • 检查防火墙状态

    sudo ufw status # 对于 UFW(Ubuntu/Debian)
    sudo firewall-cmd --list-all # 对于 firewalld(CentOS/RHEL)
    sudo iptables -L # 对于 iptables
    
  • 打开必要的端口:确保 Nginx 用于连接上游的端口(例如,通过 TCP/IP 的 PHP-FPM 的 9000 端口)已打开。

    sudo ufw allow from 127.0.0.1 to any port 9000 # 允许本地主机连接到 9000
    sudo firewall-cmd --permanent --add-port=9000/tcp # 对于 firewalld
    sudo firewall-cmd --reload
    
  • 仅在受控环境中临时禁用防火墙进行测试,然后重新启用并正确配置。

6. SELinux 或 AppArmor 干扰

原因:安全增强功能如 SELinux(在 RHEL/CentOS 上)或 AppArmor(在 Ubuntu/Debian 上)可能阻止 Nginx 访问上游套接字或进行网络连接,即使文件权限和防火墙配置正确。

症状:日志可能显示 permission denied 或类似消息,尤其是在 /var/log/audit/audit.log(对于 SELinux)中。

解决方案

  • 检查 audit.log

    sudo grep nginx /var/log/audit/audit.log
    
  • 临时将 SELinux 设置为宽容模式sudo setenforce 0。如果错误解决,则 SELinux 是罪魁祸首。然后您需要生成并应用适当的 SELinux 策略(例如 audit2allow)。记得将其设置回强制模式(sudo setenforce 1)。

  • 检查 AppArmor 状态sudo aa-status。如果 AppArmor 处于活动状态,您可能需要调整 Nginx 配置文件。

7. 大型请求/响应体(代理缓冲)

原因:Nginx 的默认代理缓冲设置对于非常大的请求或响应体可能太小,导致连接过早关闭。

症状:Nginx 错误日志可能显示 upstream prematurely closed connection while reading response header from upstreamupstream prematurely closed connection while reading response body from upstream

解决方案:在您的 httpserverlocation 块中调整 Nginx 代理缓冲指令。

http {
    ...
    proxy_buffer_size   128k; # 用于响应第一部分的缓冲区大小
    proxy_buffers   4 256k; # 用于响应其余部分的缓冲区数量和大小
    proxy_busy_buffers_size   256k; # 忙碌缓冲区的最大大小
    proxy_temp_file_write_size 256k; # 如果缓冲溢出,写入临时文件的大小
    ...
}

注意:这些设置会消耗更多内存。根据服务器的资源和应用程序响应的典型大小谨慎调整。

一般故障排除技巧

  • 查看所有相关日志:除了 Nginx 错误日志,还要检查 Nginx 访问日志、上游应用程序日志(PHP-FPM、Gunicorn、Node.js 应用日志)和系统日志(/var/log/syslogdmesg)。
  • 重启 Nginx:在任何配置更改后,始终重启 Nginx 以确保生效:systemctl restart nginx
  • 测试 Nginx 配置:在重启之前,验证您的 Nginx 配置语法:nginx -t
  • 隔离问题:尝试绕过 Nginx 直接访问上游应用程序。例如,如果您的 Node.js 应用在 localhost:3000 上,请从服务器的命令行使用 curl http://localhost:3000。如果这也失败,则问题肯定出在您的应用程序,而不是 Nginx。
  • 检查磁盘空间:磁盘已满可能会阻止应用程序写入临时文件或日志,导致崩溃或失败。使用 df -h 检查磁盘使用情况。

要点

/var/log/nginx/error.log 开始,然后验证上游是否在 Nginx 主机上运行并可访问。一旦您知道失败是连接被拒绝、超时、权限被拒绝还是过早关闭,修复方法通常在于上游服务、套接字权限、超时设置或防火墙规则。