网站出现 502 网关错误,对于使用 PHP 环境的站长来说,是服务器层面最常遇到的故障之一。这个错误本质上意味着 Nginx 或 Apache 作为网关,向上游的 PHP 进程(通常是 PHP-FPM)发送请求时,没有得到有效的回应。本教程将带你从零开始,系统性地排查并解决这个问题,即使你没有深厚的运维经验,也能按步骤操作。
### 前言介绍
502 Bad Gateway 错误页面通常表现为白屏或浏览器默认的错误提示。它的直接原因是 Web 服务器(如 Nginx)与后端 PHP 处理进程(PHP-FPM)之间的通信中断或失败。常见场景包括:PHP-FPM 服务意外停止、进程池耗尽、PHP 执行超时、内存不足或配置文件错误。本教程将针对最常见的 PHP 进程相关原因,提供一套从检查服务状态到调整核心配置的完整解决方案。所有操作均基于 Linux 服务器(CentOS/Ubuntu)环境,你需要拥有服务器的 SSH 登录权限。
### 前置准备
在开始排查之前,请确保你具备以下条件:
1. **服务器 SSH 访问权限**:你需要能够通过终端工具(如 PuTTY、Terminal、Xshell)登录到服务器,并且拥有 root 或 sudo 权限。
2. **基础命令知识**:了解 `systemctl`、`ps`、`grep`、`vim`、`tail` 等基本 Linux 命令的作用。
3. **确认网站环境**:明确你的 Web 服务器是 Nginx 还是 Apache,以及 PHP 版本(例如 PHP 7.4、8.1)。本教程以最常见的 Nginx + PHP-FPM 组合为例。
4. **准备好临时维护页面**(可选):如果网站是生产环境,建议先放一个简单的静态维护页面,避免用户反复刷新加重服务器负担。
### 分步操作步骤
#### 步骤 1:快速检查 PHP-FPM 服务是否在运行
这是最直接、最可能的原因。PHP-FPM 服务一旦停止,Nginx 无法转发请求,立刻返回 502。
1. **登录服务器**:使用 SSH 工具连接到你的服务器。
2. **执行检查命令**:在命令行输入以下命令,查看 PHP-FPM 的运行状态。
```bash
# 对于 systemd 管理的系统(CentOS 7+、Ubuntu 16+)
systemctl status php-fpm
# 注意:PHP 版本不同,服务名可能不同,例如 php7.4-fpm、php8.1-fpm
# 如果上述命令报错,尝试:
systemctl status php7.4-fpm
systemctl status php8.1-fpm
```
3. **分析输出结果**:
* **绿色 `active (running)`**:服务正在运行,问题可能出在其他地方,请继续下一步。
* **红色 `inactive (dead)` 或 `failed`**:服务已停止或启动失败。这是罪魁祸首。
* **修复方法**:如果服务停止了,立即启动它:
```bash
systemctl start php-fpm
# 或 systemctl start php7.4-fpm
```
启动后,再次执行 `systemctl status` 确认状态。如果启动失败,会显示错误日志,常见原因是端口被占用或配置文件语法错误。你可以使用 `journalctl -xe` 查看详细失败原因。
#### 步骤 2:检查 PHP-FPM 进程池是否已满(高并发场景)
即使服务在运行,如果同时处理的请求数量超过了 PHP-FPM 子进程的最大限制,新请求会被排队或直接拒绝,导致 502。
1. **查看当前进程数**:执行以下命令,统计当前 PHP-FPM 子进程的数量。
```bash
ps aux | grep -c php-fpm
# 这个命令会列出所有包含 php-fpm 的进程数量。注意,结果会包含 grep 命令本身,通常需要减去 1。
```
2. **检查进程池配置**:找到 PHP-FPM 的配置文件。通常位于 `/etc/php-fpm.d/www.conf` 或 `/etc/php/7.4/fpm/pool.d/www.conf`。
```bash
# 使用 vim 或 nano 打开配置文件
vim /etc/php-fpm.d/www.conf
```
3. **定位关键参数**:在文件中找到以下三个参数(通常在文件开头部分):
* `pm.max_children`:最大子进程数。这是最重要的限制。
* `pm.start_servers`:启动时的子进程数。
* `pm.max_spare_servers`:最大空闲进程数。
4. **诊断问题**:如果你的 `ps` 命令显示当前进程数接近或等于 `pm.max_children` 的值,说明进程池已经耗尽。服务器无法处理新请求。
5. **临时解决方案**:增加 `pm.max_children` 的值。例如,从 50 改为 100。同时,相应调整 `pm.start_servers` 和 `pm.max_spare_servers`。
```ini
pm.max_children = 100
pm.start_servers = 20
pm.min_spare_servers = 10
pm.max_spare_servers = 30
```
**注意**:不要盲目改大。每个子进程大约占用 20-50MB 内存。`max_children * 每个进程内存` 不能超过服务器总内存。例如,服务器有 4GB 内存,建议 `max_children` 不超过 80(4GB / 50MB)。
6. **重启 PHP-FPM 使配置生效**:
```bash
systemctl restart php-fpm
```
#### 步骤 3:检查 PHP 执行超时设置
如果某个 PHP 脚本执行时间过长(例如,处理大文件、调用外部慢速 API、数据库查询缓慢),PHP-FPM 会强制终止它,并返回 502。这种情况通常伴随 CPU 或数据库负载飙升。
1. **定位超时配置**:在 PHP-FPM 配置文件(`www.conf`)中,找到 `request_terminate_timeout` 参数。
```bash
vim /etc/php-fpm.d/www.conf
# 搜索 request_terminate_timeout
```
2. **查看当前值**:
* 如果该参数被注释(前面有 `;`),表示没有设置超时,脚本可以无限执行,这是不安全的。
* 如果设置为 `60s` 或 `300s`,表示脚本最多执行 60 秒或 300 秒。
3. **调整方案**:
* **临时解决**:如果网站是因为某个特定页面(如数据导出)超时,可以临时增大该值,例如 `request_terminate_timeout = 600s`。
* **根本解决**:检查服务器上的 PHP 错误日志(通常位于 `/var/log/php-fpm/` 或 `/var/log/php_errors.log`),找到具体是哪个脚本执行超时。优化该脚本的逻辑(例如,分页查询、添加索引、使用缓存)。
4. **同时检查 Nginx 超时**:Nginx 也有自己的超时设置。编辑 Nginx 配置文件(通常位于 `/etc/nginx/nginx.conf` 或网站配置文件中)。
```bash
vim /etc/nginx/nginx.conf
# 在 http 块中添加或修改
fastcgi_read_timeout 300s;
proxy_read_timeout 300s;
```
确保 Nginx 的超时时间大于或等于 PHP-FPM 的超时时间。
5. **重启 Nginx 和 PHP-FPM**:
```bash
systemctl restart nginx
systemctl restart php-fpm
```
#### 步骤 4:检查 PHP-FPM 监听地址和端口配置
Nginx 通过一个特定的地址(通常是 Unix Socket 或 TCP 端口)与 PHP-FPM 通信。如果两者配置不一致,通信会失败。
1. **查看 PHP-FPM 监听配置**:在 `www.conf` 文件中找到 `listen` 参数。
```bash
grep 'listen =' /etc/php-fpm.d/www.conf
```
常见输出有两种:
* `listen = /var/run/php-fpm/php-fpm.sock`(Unix Socket 方式,性能更好)
* `listen = 127.0.0.1:9000`(TCP 端口方式)
2. **查看 Nginx 配置**:找到 Nginx 网站配置文件(例如 `/etc/nginx/conf.d/default.conf` 或 `/etc/nginx/sites-enabled/your-site`),找到 `location ~ \.php$` 块。
```bash
vim /etc/nginx/conf.d/default.conf
```
查看 `fastcgi_pass` 指令的值:
```nginx
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; # 或 127.0.0.1:9000
# ... 其他配置
}
```
3. **比对一致性**:`fastcgi_pass` 的值必须与 `listen` 的值**完全一致**。例如,PHP-FPM 监听的是 `127.0.0.1:9000`,但 Nginx 配置的是 `unix:/tmp/php-fpm.sock`,就会导致 502。
4. **修复方法**:修改 Nginx 或 PHP-FPM 的配置,使两者一致。通常建议保持使用 Unix Socket,因为它比 TCP 端口更快。修改后,重启两个服务:
```bash
systemctl restart php-fpm
systemctl restart nginx
```
#### 步骤 5:检查 PHP-FPM 日志和系统资源
如果以上步骤都无法解决,需要深入查看日志和系统资源。
1. **查看 PHP-FPM 慢日志**:慢日志能记录执行过慢的脚本。在 `www.conf` 中启用它:
```ini
slowlog = /var/log/php-fpm/www-slow.log
request_slowlog_timeout = 10s
```
设置 `request_slowlog_timeout = 10s` 后,任何执行超过 10 秒的脚本都会被记录到 `www-slow.log`。查看该日志:
```bash
tail -f /var/log/php-fpm/www-slow.log
```
这会直接告诉你哪个 PHP 文件、哪一行代码执行缓慢。
2. **查看 PHP-FPM 错误日志**:
```bash
tail -100 /var/log/php-fpm/error.log
```
错误日志中可能包含 `WARNING: [pool www] seems busy` 或 `ERROR: unable to read what child say` 等关键信息。
3. **检查服务器资源**:
```bash
# 查看内存使用
free -m
# 查看 CPU 和内存占用最高的进程
top
# 查看磁盘 I/O 是否繁忙
iostat -x 1 5
```
* **内存不足**:如果 `free -m` 显示可用内存接近 0,且 `top` 中 PHP-FPM 进程占用大量内存,说明服务器内存耗尽。需要升级服务器或优化应用。
* **CPU 100%**:如果 CPU 被 PHP 进程占满,说明有脚本在死循环或进行大量计算。立即通过 `top` 找到 PID,然后使用 `strace -p PID` 追踪该进程正在执行什么操作。
### 常见问题
* **问题 1:重启 PHP-FPM 后,502 错误短暂消失,但几分钟后又出现。**
* **原因**:通常是进程池耗尽(`max_children` 太小)或存在内存泄漏的脚本。按照步骤 2 增加 `max_children`,并检查慢日志和错误日志定位泄漏脚本。
* **问题 2:修改了 `www.conf` 文件,但重启 PHP-FPM 报错。**
* **原因**:配置文件语法错误。执行 `php-fpm -t` 或 `php-fpm7.4 -t` 测试配置。系统会提示具体哪一行出错。根据提示修正即可。
* **问题 3:网站部分页面 502,部分页面正常。**
* **原因**:特定页面触发了 PHP 执行超时或内存限制。检查慢日志,找到那个特定的 URL 或脚本。可能是该页面代码效率低,或者包含一个死循环。
* **问题 4:检查发现 PHP-FPM 和 Nginx 配置都正确,服务也在运行,但依然 502。**
* **原因**:SELinux 或防火墙可能阻止了通信。尝试临时关闭 SELinux 测试:`setenforce 0`。如果问题解决,需要配置 SELinux 策略允许 HTTP 服务连接 PHP-FPM Socket。或者检查防火墙是否放行了 9000 端口(如果使用 TCP 方式)。
### 收尾总结
解决 502 网关错误的核心思路是“从外到内,逐层排查”。首先确认 PHP-FPM 服务是否存活,然后检查进程池是否耗尽,接着排查超时和通信配置,最后借助日志和系统监控工具定位深层问题。请记住,每次修改配置后,务必重启对应的服务(`systemctl restart php-fpm` 和 `systemctl restart nginx`)。建议在日常运维中,开启 PHP-FPM 的慢日志和错误日志,并定期查看,这是预防 502 错误的最佳手段。如果以上方法都无效,且服务器资源正常,可以考虑升级 PHP 版本或检查 PHP 扩展是否存在兼容性问题。