网站上传大文件总是失败?Nginx、PHP 与应用层限制逐层排查

小文件能上传,大文件却出现 413、超时或“没有选择文件”?本文面向 Nginx 与 PHP-FPM 网站,结合 WordPress 说明入口请求体限制、PHP 单文件与 POST 上限、临时目录和后处理超时的检查方法,并给出配置备份、生效核对及边界文件验证步骤,帮助按日志定位上传失败发生的环节。
网站上传大文件总是失败?Nginx、PHP 与应用层限制逐层排查

小图片能上传,几十兆的安装包却报错;后台已经把上传上限调大,浏览器仍然显示 413;文件进度走完了,又等来一个 504。网站上传故障可能发生在接收请求、解析表单、临时落盘和业务处理的不同阶段,改一个数值往往覆盖不了整条链路。

本文面向 Linux 上的 Nginx + PHP-FPM 网站,也包含 WordPress 的检查方法。示例以允许单个约 100 MiB 文件为目标,具体上限需要按业务、主机容量和入口限制调整。所有配置均为说明用途,未在读者的生产环境中执行。

用失败时间和日志确定卡在哪一层

把一次失败上传的时间、请求 URL、文件大小和响应码记下来,在浏览器开发者工具的 Network 面板查看失败请求。页面上的“HTTP 错误”提示通常缺少定位信息,实际请求的响应正文和服务端日志更有用。

现象或证据 优先检查
413,Nginx 错误日志包含 client intended to send too large body 命中请求的 Nginx client_max_body_size
413 或 403,但对应源站没有请求记录 CDN、WAF、负载均衡的请求体限制或安全规则
PHP 提示 POST Content-Length ... exceeds the limit,或表单、文件数组为空 post_max_size
文件错误码为 UPLOAD_ERR_INI_SIZE upload_max_filesize
408,日志显示读取客户端请求体超时 客户端上传中断或 Nginx 读请求体超时
502、504,或上传结束后长时间等待 PHP-FPM 退出、代理等待超时、应用后处理
文件错误码为 UPLOAD_ERR_NO_TMP_DIR、UPLOAD_ERR_CANT_WRITE PHP 临时目录、磁盘空间和写权限

这些现象提供排查方向,不能仅凭响应码认定故障组件。应用和 CDN 都可能返回相同状态码;没有源站日志也可能来自日志关闭、路径错误或采集延迟。要对照入口与源站同一时刻的记录。

在通常的部署中,请求经过 CDN 或 WAF、Nginx、PHP-FPM,再进入应用。任何一层拒绝请求,后面的上限都没有机会起作用。CDN 套餐的硬限制应查当前官方文档和控制台,源站配置无法提高这个上限。

Nginx 要修改命中上传地址的配置

先查看当前实例解析的配置,确认上传域名对应的 server 和最终匹配的 location:

sudo nginx -T 2>&1 | less

按 q 退出。输出可能包含内部域名和上游地址,公开求助时只摘取必要片段并脱敏。如果机器上有多个 Nginx 实例或运行在容器中,应使用实际服务的二进制、配置参数和容器环境;本机默认的 nginx -T 不一定对应正在接流量的实例。

Nginx 的 client_max_body_size 默认值为 1m,允许配置在 http、server 和 location 中。下级配置可以覆盖继承值,因此在全局调大之后,上传路径仍可能保留较小的限制。该限制计算整个客户端请求体,包含文件和表单封装。

例如,业务允许单文件约 100 MiB,可在已有站点配置中调整为:

server {
    # 保留原有 listen、server_name 和路由
    client_max_body_size 128m;

    # 保留原有 location、FastCGI 或反向代理配置
}

这只是片段,应合并到原有站点,不要用它覆盖完整配置。也不要为上传路径临时新建一个只有大小限制的 location,否则可能改变 PHP 路由。若已有上传专用 location,同时检查该处的覆盖项。

修改前备份实际文件,以下路径仅为示例:

sudo cp -a /etc/nginx/conf.d/site.conf /etc/nginx/conf.d/site.conf.bak-20261113
sudo nginx -t

备份文件使用不以 .conf 结尾的名称,避免被常见的 *.conf 通配规则再次加载;仍需核对自己的 include 规则。测试通过后,对使用 systemd 管理的服务执行:

sudo systemctl reload nginx

nginx -t 成功只能证明配置可解析。reload 后检查服务日志,并用相同域名和上传路径再次验证。失败时恢复备份,再测试和 reload。容器或面板部署应使用其管理入口,避免宿主机命令操作了另一套服务。

把限制设为 0 会关闭 Nginx 这一项请求体大小检查,公网网站通常应保留符合业务需要的上限。放大 client_body_buffer_size 也不能替代上传上限,它控制读取请求体的缓冲区,过量调整还会改变内存占用。

PHP 同时检查单文件和整个 POST 请求

PHP 的两个参数负责不同范围:

  • upload_max_filesize:单个上传文件的大小限制。
  • post_max_size:整个 POST 请求大小,包括文件、其他字段及 multipart 封装。

针对单文件约 100 MiB 的示例,可以设置:

upload_max_filesize = 100M
post_max_size = 128M

如果一次允许上传多个文件,post_max_size 需要容纳它们的总量和额外字段。Nginx 限制也应覆盖该请求体。PHP 官方文档建议 post_max_size 大于 upload_max_filesize;两者设成完全相同的数值容易在边界上传时被表单开销卡住。

超过 post_max_size 时,PHP 的 $_POST、$_FILES 会为空。应用如果只把空数组解释为“没有选择文件”,用户看到的错误就会偏离原因。应同时查看 PHP 日志中的 Content-Length 警告。

只超过 upload_max_filesize 时,常规文件上传可从 $_FILES 的 error 字段读到 UPLOAD_ERR_INI_SIZE。记录诊断日志时保留错误码和请求标识即可,避免记录文件内容、会话 Cookie 或完整用户数据。

CLI 的配置不代表网站正在使用的配置

php --ini

该命令读取命令行 PHP 的配置。Web 请求可能由另一个 PHP 版本、FPM 池或容器处理。修改前应核对网站绑定的 PHP-FPM 服务和对应配置目录,再检查池配置中的 php_value、php_admin_value 以及站点的 .user.ini 等覆盖来源。

WordPress 可以从“工具 → 站点健康 → 信息 → 服务器”查看 PHP 版本和相关限制。其他应用可使用仅限管理员或受控内网访问的诊断入口,读取 PHP_SAPI、php_ini_loaded_file() 及这两个参数的 ini_get() 值。诊断完成后移除临时入口,避免长期暴露完整 phpinfo() 页面。

upload_max_filesize 和 post_max_size 属于 INI_PERDIR 级别,不能指望在上传处理脚本内调用 ini_set() 来改变当前请求的解析限制。由 PHP-FPM 配置文件控制的修改,需要按部署方式重新加载相应服务;.user.ini 则可能受缓存时间影响。使用实际版本的配置检查命令确认语法,再通过站点管理工具或服务管理器平滑加载,最后回到 Web 侧核对生效值。

PHP 的 memory_limit 还关系到后续图片解码等业务内存开销。普通上传不要求整个文件始终驻留 PHP 内存,也没有通用的“文件大小乘固定倍数”公式。根据处理方式和并发设置预算,避免为了传大文件直接取消内存限制。

文件能接收,为什么最后仍然超时

上传包含接收文件和处理文件两个阶段。浏览器进度达到 100% 通常只能说明客户端发送完毕,服务器可能仍在校验、解压、生成缩略图或导入数据。

Nginx 的 client_body_timeout 默认是 60s,计算连续两次读取请求体之间的等待时间。它不把整个上传过程强制限制在 60 秒。持续有数据传输的慢速上传和中途长时间停顿,触发结果可能不同。

PHP 网站还可能涉及 max_input_time、max_execution_time 和 FPM 池中的 request_terminate_timeout,各自对应输入解析、脚本执行及 FPM 请求终止边界。具体耗时还受到 Nginx 请求体缓冲、平台和 SAPI 的影响。不要把这些参数统统改成相同的大数,再凭一次成功判断故障解决。

Nginx 直接连接 PHP-FPM 时,检查 fastcgi_read_timeout;Nginx 通过 HTTP 代理到应用时,检查 proxy_read_timeout。它们约束从上游连续两次读取之间的等待,并非统一的“文件上传总时长”。只有日志证明应用仍在正常工作、等待窗口确实不足时,才有依据提高对应超时。

如果 FPM 已因资源不足退出,调长 Nginx 等待时间没有帮助。长时间导入、视频转码和大批量缩略图生成,通常可以交给后台任务;上传接口返回任务 ID,再由页面查询处理结果,可减少连接长时间占用。

临时目录可能先于最终目录写满

文件最终要保存的位置有空间,并不代表上传过程的临时存储也够用。Nginx 的 client_body_temp_path、PHP 的 upload_tmp_dir 和应用最终目录可能处于不同的分区、容器或配额范围。

在确认实际路径后执行只读检查,下面使用 /tmp 演示:

df -h /tmp
df -i /tmp
namei -l /tmp

把 /tmp 替换为实际的临时目录和目标目录。df -h 看可用容量,df -i 看 inode,namei -l 帮助检查父级目录的遍历权限。容器应在相应命名空间内检查;主机还有空间时,容器可写层、tmpfs 或磁盘配额仍可能达到上限。

PHP 的 UPLOAD_ERR_NO_TMP_DIR 表示缺少临时目录,UPLOAD_ERR_CANT_WRITE 表示文件写入失败。核对实际运行用户的写权限,并检查 SELinux 或 AppArmor 日志。不要用递归 chmod 777 代替权限诊断,也不要在不了解用途时删除正在上传的临时文件。

Nginx 和 PHP 在不同阶段可能都要写入临时数据,容量规划应考虑并发上传和处理期间的副本。具体占用取决于缓冲方式和业务流程,不能只按一个文件的最终大小预留空间。

WordPress 后台显示的上限还受站点规则影响

WordPress 的 wp_max_upload_size() 会读取 PHP 的 upload_max_filesize 和 post_max_size,取较小值,再经过 upload_size_limit 过滤器。多站点网络设置、插件和上传类型校验也可能进一步收紧规则。

因此,插件把后台显示的数字改大,不能保证 PHP、Nginx 或 CDN 同时接受更大的请求。应按入口到应用的顺序核对生效值,再使用允许的文件类型验证。

上传 .zip、备份文件或视频时,还需区分媒体库和具体插件接口。备份插件可能使用分片上传,也可能有独立的单块限制、总量限制或导入超时。对分片接口,应检查每个分片请求大小以及合并阶段的空间,而不是直接把完整文件大小当作每次请求体大小。

用边界文件验证,再收紧到业务需要的范围

修复后准备一个较小文件、一个接近业务上限的文件,以及一个超过上限的文件。文件必须属于接口允许的类型;面向实际用户的上传接口还需要正常的登录状态和 CSRF 校验。

在测试环境、测试账号和已确认的上传地址上,可以用 curl 重放普通 multipart 文件请求。示例中的域名、路径和文件需要替换:

curl --show-error \
  --connect-timeout 10 --max-time 300 \
  -o /tmp/upload-response.txt \
  -w '\nHTTP %{http_code}, total %{time_total}s\n' \
  -F 'file=@./test-upload.zip' \
  'https://example.com/test-upload'

file 必须与应用字段名一致。示例没有登录凭证,直接用于受保护接口通常会得到登录页面或鉴权失败,不能据此判断上传上限。不要把真实 Cookie 或访问令牌贴入公开记录;浏览器中完成验证通常更省事。

curl 会实际提交文件。仅对已授权的测试接口执行,按 Ctrl+C 停止;--max-time 限制本次客户端等待时间,客户端退出后服务端任务未必立即停止。响应临时文件可能包含敏感信息,检查后按本机管理规则清理;测试上传产生的文件也应从应用内删除。

预期结果应同时满足:小文件和上限以内的文件成功、文件大小与完整性正确、超限请求返回明确提示。再对照 Nginx、PHP-FPM 和应用日志,确认没有新增错误。外部访问经过 CDN 时,应通过用户实际访问的完整链路验证,不能只用源站测试代替。

单个 100 MiB 文件使用 128 MiB 的请求体上限,只是留出表单余量的配置示例。业务如果只需要上传图片,应继续保留类型、尺寸和频率限制;数 GiB 的文件则更适合评估分片续传或对象存储直传,并把签名有效期、文件校验和访问权限纳入设计。

官方参考资料

资料核验日期:2026 年 11 月 13 日。

Linux运维

Linux 内存没有耗尽却被 OOM Killer 杀掉?cgroup 限制、Swap 与内存峰值定位

2026-11-12 11:11:27

Linux运维

Linux 服务重启后端口又消失?systemd Restart 与启动依赖排查

2026-11-13 10:14:34