php5.6-fpm环境下ftp_put()返回网络流读取失败问题咨询
问题根因
你遇到的问题是PHP5.6 FPM环境下FTP扩展对FTPS连接的被动模式支持存在BUG,你调用的ftp_pasv未实际生效,从FTP日志可以看到所有数据传输都使用了PORT主动模式,而非预期的PASV被动模式,主动模式下FTP服务端无法成功回连到NAT环境下的PHP服务器,最终抛出流读取错误。
解决方案
方案1:修复FTP扩展参数配置(推荐,无需更换上传方式)
在原有代码基础上新增ftp_set_option配置,并确保被动模式开启的位置正确,修改后的代码如下:
<?php $remotePath = '/httpdocs/2/3/9/6/7/8/8/'; $newFileName = time() . '.png'; $ftp = ftp_ssl_connect( 'example.com', 21 ); if (!$ftp) { die('连接FTP失败'); } $login_result = ftp_login( $ftp, 'username', 'password' ); if (!$login_result) { die('FTP登录失败'); } // 核心修复1:关闭被动模式地址校验,避免FTP服务器返回内网地址无法连接 ftp_set_option($ftp, FTP_USEPASVADDRESS, false); // 核心修复2:登录后显式开启被动模式,确保在所有数据操作前执行 ftp_pasv( $ftp, true ); $upload_result = ftp_put( $ftp, $remotePath . $newFileName, $_FILES['upload']['tmp_name'], FTP_BINARY ); if (!$upload_result) { die('上传失败: ' . error_get_last()['message']); } ftp_close( $ftp );
修改后重启php5.6-fpm服务即可验证,核心修复逻辑:
- 开启
FTP_USEPASVADDRESS = false:强制PHP使用FTP连接的目标地址作为被动模式传输地址,忽略FTP服务端返回的内网地址,避免NAT场景下的连接异常 - 确保被动模式开启在登录后、所有数据传输操作前执行,PHP5.6下连接状态变更会重置被动模式标记
方案2:调整php.ini配置
如果方案1未生效,检查php5.6-fpm的php.ini配置:
- 打开php5.6-fpm的配置文件,通常路径为
/etc/php/5.6/fpm/php.ini - 搜索
ftp.allow_passive配置项,确保值为On,如果不存在该配置项,新增一行ftp.allow_passive = On - 保存后执行
systemctl restart php5.6-fpm重启服务生效
方案3:更换为CURL实现FTPS上传(兼容性最佳)
如果上述两种方案都无效,可以使用兼容性更强的CURL扩展实现FTPS上传,完全避开PHP5.6 FTP扩展的BUG,代码示例如下:
<?php $remoteFile = 'ftp://example.com/httpdocs/2/3/9/6/7/8/8/' . time() . '.png'; $localFile = $_FILES['upload']['tmp_name']; $ch = curl_init(); $fp = fopen($localFile, 'r'); curl_setopt($ch, CURLOPT_URL, $remoteFile); curl_setopt($ch, CURLOPT_USERPWD, 'username:password'); curl_setopt($ch, CURLOPT_UPLOAD, 1); curl_setopt($ch, CURLOPT_INFILE, $fp); curl_setopt($ch, CURLOPT_INFILESIZE, filesize($localFile)); // FTPS配置 curl_setopt($ch, CURLOPT_FTP_SSL, CURLFTPSSL_ALL); curl_setopt($ch, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS); // 开启被动模式 curl_setopt($ch, CURLOPT_FTPPORT, '-'); // 自签名证书可开启以下配置,生产环境建议关闭并配置可信证书 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_exec($ch); $error = curl_error($ch); curl_close($ch); fclose($fp); if ($error) { die('上传失败: ' . $error); }
内容的提问来源于stack exchange,提问作者Andrea D.
相关产品推荐
相关产品推荐

