PHP-FPM与Nginx通信报 502 Bad Gateway或504 Gateway Timeout终极解决方案(适用于PHP执行耗时任务情况下的报错)

文章讲述了在PHP脚本执行时间过长导致的502/504错误,分析了原因并给出了相应的解决方案,包括在php-fpm.conf中增加request_terminate_timeout配置以及在nginx配置中调整fastcgi连接、读取和发送超时时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前置条件:

适用于常规请求都没问题,但是执行某些php脚本需要超过一分钟的情况下的502/504,并不是任何请求都502/504的情况(这说明php-fpm或者nginx配置错误)。

出现502/504的原因

502

执行脚本时间太长,期间php没有返回任何的数据。php-fpm超时,nginx没超时。nginx认为php-fpm罢工了,然后抛出了异常。

504

执行脚本时间太长,期间php没有返回任何的数据。php-fpm没超时,nginx超时。nginx认为php-fpm响应太慢,nginx没憋住抛出了异常。

不生效的解决方案(防止各位师傅踩坑):

代码

set_time_limit(0);
ignore_user_abort(true);
ini_set('max_execution_time', 600);

不生效原理剖析

以上代码的作用设置了php代码本身可以更长的时间处理任务并且不报致命错误,但不代表程序一定无限制的可以执行这么久。因为Nginx与PHP进程通信方式是检测到.php的文件交给php-fpm进程处理,php-fpm是一个fastcgi进程管理器,php-fpm一旦超时,php-fpm会强制终结掉这个进程,这就是报502的原因。
这段代码又无法控制nginx fastcgi的一些机制,所以报504的原因。
意味着仅代码层配置还不够,服务器也得配置。

官方文档对这2个函数和1个配置的解释:

##### set_time_limit:
Set the number of seconds a script is allowed to run. If this is reached, the script returns a fatal error. The default limit is 30 seconds or, if it exists, the max_execution_time value defined in the php.ini.

##### ignore_user_abort:
Set whether a client disconnect should abort script execution.When running PHP as a command line script, and the script's tty goes away without the script being terminated then the script will die the next time it tries to write anything, unless enable is set to true.

##### max_execution_time:
This sets the maximum time in seconds a script is allowed to run before it is terminated by the parser. This helps prevent poorly written scripts from tying up the server. The default setting is 30. When running PHP from the command line the default setting is 0.
On non Windows systems, the maximum execution time is not affected by system calls, stream operations etc. Please see the set_time_limit() function for more details.
**Your web server can have other timeout configurations that may also interrupt PHP execution..Apache has a Timeout directive and IIS has a CGI timeout function. Both default to 300 seconds. See your web server documentation for specific details.**
加粗字体意思是说:Web 服务器可以有其它超时配置,这些配置也可能会中断 PHP 执行。Apache 有一个 Timeout 指令,IIS 有一个 CGI 超时功能。 两者都默认为 300 秒。(nginx也有一个fastcgi超时配置,默认60秒)。

502解决方案

再php-fpm.conf中添加request_terminate_timeout = 600即可,如下:

#编辑php-fpm配置
vim /usr/local/php/etc/php-fpm.conf
#添加此配置,单位默认为秒,多少秒请根据情况自行设定
request_terminate_timeout = 600
#保存后重启
service php-fpm restart

504解决方案

再nginx配置中添加 fastcgi_connect_timeout 600; fastcgi_read_timeout 600; fastcgi_send_timeout 600; 即可,如下:

#编辑nginx某个站点的配置
vim /usr/local/nginx/conf/vhost/test.conf
#再location中添加以下配置,单位默认为秒,多少秒请根据情况自行设定,完整代码块如下:
location ~ \.php$ {
	fastcgi_pass   127.0.0.1:9000;
	fastcgi_index  index.php;
	fastcgi_param  SCRIPT_FILENAME $document_root/$fastcgi_script_name; 
	include        /usr/local/nginx/conf/fastcgi_params;
	fastcgi_connect_timeout 600; 
	fastcgi_read_timeout 600; 
	fastcgi_send_timeout 600; 
}

#保存后测试配置是否有问题,如果有问题,请修改好后再次尝试。
../../sbin/nginx -t
#确认配置正常,重启
service nginx restart

配置含义官方说明:

PHP-FPM:
request_terminate_timeout
The timeout for serving a single request after which the worker process will be killed. This option should be used when the 'max_execution_time' ini option does not stop script execution for some reason. A value of '0' means 'Off'. Available units: s(econds)(default), m(inutes), h(ours), or d(ays). Default value: 0.


Nginx:
fastcgi_connect_timeout 60s;
Defines a timeout for establishing a connection with a FastCGI server. It should be noted that this timeout cannot usually exceed 75 seconds.

fastcgi_read_timeout 60s;
Defines a timeout for reading a response from the FastCGI server. The timeout is set only between two successive read operations, not for the transmission of the whole response. If the FastCGI server does not transmit anything within this time, the connection is closed.

fastcgi_send_timeout 60s;
Sets a timeout for transmitting a request to the FastCGI server. The timeout is set only between two successive write operations, not for the transmission of the whole request. If the FastCGI server does not receive anything within this time, the connection is closed.
/** * @notes 正向配送费货品明细导入() * @return Json * @author 胡军 * @date 2025/06/20 */ public function importGoodsDetail(): Json { set_time_limit(0); ini_set('memory_limit', '2048M'); $params = $this->request->param(); if (empty($params["file"])) { return $this->fail('请上传要导入的文件',[],0,1); } $titleArr = [ "物流履约单号" => "logistics_order_no", "货品名称" => "product_name", "货品ID" => "product_id", "货品件数" => "quantity", "货品重量(克)" => "weight", "货品长度(毫米)" => "length", "货品宽度(毫米)" => "width", "货品高度(毫米)" => "height", "货品体积(立方毫米)" => "volume", ]; try { // 使用生成器获取数据流 $dataGenerator = $this->importExeclGenerator($params['file']); //TODO:此处每次批数据量不要太大 不然saveAll()会产生性能问题 经过测试30最后 有待优化... $batchSize = 30; // 每批处理30条,可根据服务器性能调整 $model = new GoodsDetailModel(); $total = 0; $success = 0; $failedBatches = []; // 记录失败的批次 // 直接处理数据,不要使用事务 $batchData = []; foreach ($dataGenerator as $rowData) { $total++; // 转换数据格式 $item = []; foreach ($rowData as $k => $val) { if ($k && $val && isset($titleArr[$k])) { $item[$titleArr[$k]] = trim($val); } } if (!empty($item)) { $batchData[] = $item; } // 达到批次大小处理完所有数据时插入数据库 if (count($batchData) >= $batchSize) { if (!empty($batchData)) { try { //TODO:模型的批量插入存在性能问题 还可以优化提高导入速度 待优化... $model->saveAll($batchData); $success += count($batchData); } catch (\Exception $e) { // 记录失败批次(可选) $failedBatches[] = [ 'startRow' => $total - count($batchData) + 1, 'endRow' => $total, 'error' => $e->getMessage() ]; } } $batchData = []; // 清空批次数据 } } // 处理剩余数据 if (!empty($batchData)) { try { //TODO:有优化的空间 速度可以大大提升 赶进度暂且这样... $model->saveAll($batchData); $success += count($batchData); } catch (\Exception $e) { $failedBatches[] = [ 'startRow' => $total - count($batchData) + 1, 'endRow' => $total, 'error' => $e->getMessage() ]; } } // 构建返回结果 $resultMsg = "导入成功,共{$total}条数据,成功导入{$success}条"; if (!empty($failedBatches)) { $resultMsg .= ",失败 ".$total - $success." 条,具体报错信息:".$e->getMessage(); return $this->fail('导入失败',['remark' => $resultMsg],0,1); } return $this->success('导入成功',['remark' => $resultMsg],1,1); } catch (\Exception $e) { return $this->fail('导入失败',['remark' => $e->getMessage()],0,1); } } /** * @notes Excel导入方法(生成器模式,逐行返回数据) * @notes common.php代码不规范导致本地git有差异直接还原 直接写在控制器里算了! * @author 胡军 * @date 2025/06/20 */ private function importExeclGenerator(string $file_name): \Generator { try { $path = app()->getRootPath() . "public/" . $file_name; $reader = IOFactory::createReaderForFile($path); $reader->setReadDataOnly(true); $spreadsheet = $reader->load($path); // 获取第一个工作表 $sheet = $spreadsheet->getSheet(0); // 获取表头 $title = $sheet->rangeToArray('A1:' . $sheet->getHighestColumn() . '1', null, true, true, true)[1]; if (empty($title)) { throw new \Exception('Excel表头为空'); } $highestRow = $sheet->getHighestRow(); // 逐行生成数据,不一次性加载到内存 for ($row = 2; $row <= $highestRow; $row++) { $rowData = $sheet->rangeToArray( 'A' . $row . ':' . $sheet->getHighestColumn() . $row, null, true, true, true )[$row]; $item = []; foreach ($rowData as $colIndex => $value) { $colName = $title[$colIndex]; if ($colName) { //$item[$colName] = trim($value); $item[$colName] = $value; } } // 使用yield关键字逐行返回数据 yield $item; } } catch (\Exception $e) { throw $e; } } 我在进行100万excel表格的导入操作,上面这是我的后端php导入接口,但是总是提示nginx网关502错误 另外我的nginx的配置和php.ini的配置增加了如下配置: 大文件上传 nginx的配置要增加: client_max_body_size 100M; client_body_buffer_size 1024k; php.ini的配置要增加: post_max_size = 100M upload_max_filesize = 50M memory_limit = 1024M 大文件下载 nginx的配置要增加: # 代理相关超时配置(适用于反向代理场景) proxy_connect_timeout 3600; # 上游服务器的连接超时 proxy_read_timeout 3600; # 从上游服务器读取响应的超时 proxy_send_timeout 3600; # 发送请求到上游服务器的超时 # 者使用fastcgi相关超时配置(如果直接连接PHP-FPM) fastcgi_connect_timeout 3600; fastcgi_read_timeout 3600; fastcgi_send_timeout 3600; 请帮我分析为什么总是报错502呢?是后端接口的问题还是nginx的问题呢?
07-07
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小松聊PHP进阶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值