上傳大文件 (>2M) 失敗、頁面白屏、無錯誤信息,90% 是 **PHP/服務器多層限制疊加** 導致。本文提供從 `php.ini` 到 Nginx/Apache、前端、代碼邏輯的全鏈路配置清單。
| 層級 | 參數 | 默認值 | 建議大文件值 (如 100MB) | 說明 |
|---|---|---|---|---|
| PHP (php.ini) | upload_max_filesize | 2M | 100M | 單個上傳文件最大體積 |
post_max_size | 8M | 110M | **POST 總數據上限,必須 > upload_max_filesize** (含表單其他字段) | |
max_execution_time | 30s | 0 (無限制) 或 300 | 腳本最大執行時間,上傳+處理需足夠時間。CLI 模式無效 | |
| PHP (運行時) | max_input_time | 60s | 300 | 解析 POST/GET 數據最大時間,含文件上傳接收 |
memory_limit | 128M | 256M+ | 腳本內存上限,處理大圖/解析大 CSV 時需調大 | |
| Nginx | client_max_body_size | 1M | 110M | **Nginx 層面攔截大包**,配置在 `http`/`server`/`location` 塊 |
proxy_read_timeout | 60s | 300s | 反向代理模式下後端處理超時 | |
fastcgi_read_timeout | 60s | 300s | PHP-FPM 處理超時 | |
| Apache | LimitRequestBody | 0 (無限) | 115343360 (110M) | 目錄/虛擬主機級別限制 |
TimeOut | 300s | 300 | 請求接收/發送總超時 |
; 文件上傳
file_uploads = On
upload_max_filesize = 100M
post_max_size = 110M
; 執行時間 (0 表示無限制,生產建議具體值如 300)
max_execution_time = 300
max_input_time = 300
; 內存
memory_limit = 256M
; 臨時目錄權限確保可寫
; upload_tmp_dir = /tmp/php_uploads
# nginx.conf http 塊或 server 塊
client_max_body_size 110M;
# PHP-FPM 站點配置 location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# 超時設置 (防大文件處理中斷)
fastcgi_read_timeout 300s;
fastcgi_send_timeout 300s;
fastcgi_connect_timeout 300s;
# 緩衝區優化 (大文件上傳)
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
fastcgi_busy_buffers_size 32k;
}
<?php
function handleLargeUpload(array $file, string $saveDir, array $allowedMimes = []): array {
// 1. 錯誤碼檢查 (UPLOAD_ERR_INI_SIZE=1, UPLOAD_ERR_FORM_SIZE=2, UPLOAD_ERR_PARTIAL=3, UPLOAD_ERR_NO_FILE=4)
if ($file['error'] !== UPLOAD_ERR_OK) {
$msg = match($file['error']) {
UPLOAD_ERR_INI_SIZE => "超過 php.ini upload_max_filesize ({$file['size']} bytes)",
UPLOAD_ERR_FORM_SIZE => "超過 HTML 表單 MAX_FILE_SIZE",
UPLOAD_ERR_PARTIAL => "文件僅部分上傳 (網絡中斷/超時)",
UPLOAD_ERR_NO_FILE => "未選擇文件",
UPLOAD_ERR_NO_TMP_DIR => "缺少臨時目錄",
UPLOAD_ERR_CANT_WRITE => "寫入磁盤失敗",
UPLOAD_ERR_EXTENSION => "被 PHP 擴展攔截",
default => "未知上傳錯誤: {$file['error']}",
};
throw new Exception($msg);
}
// 2. MIME 類型驗證 (防偽裝) - 使用 finfo 而非 $_FILES['type']
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $file['tmp_name']);
finfo_close($finfo);
if ($allowedMimes && !in_array($mime, $allowedMimes)) {
@unlink($file['tmp_name']);
throw new Exception("禁止的文件類型: $mime");
}
// 3. 生成唯一文件名 (防覆蓋、防遍歷)
$ext = pathinfo($file['name'], PATHINFO_EXTENSION);
$newName = bin2hex(random_bytes(16)) . '.' . strtolower($ext);
$destPath = rtrim($saveDir, '/') . '/' . $newName;
// 4. 移動文件 (move_uploaded_file 自帶安全檢查)
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
throw new Exception("文件保存失敗: 目錄不可寫或磁盤滿");
}
@chmod($destPath, 0644);
return ['path' => $destPath, 'name' => $newName, 'mime' => $mime, 'size' => $file['size']];
}
// 表單端必須設置 enctype 和 MAX_FILE_SIZE (隱藏字段,客戶端可繞過,服務端以 php.ini 為準)
/*
*/
?>
單次 HTTP 上傳受限於瀏覽器/代理/超時,不可靠。採用 **前端分片 + 後端合併**:
**大文件上傳 = PHP.ini + Nginx/Apache + 代碼邏輯 三層同時放開**。常規文件 (<100MB) 調大上述參數即可;超大文件**必須分片上傳**,配合斷點續傳、秒傳 (Hash 校驗) 才是生產級方案。