上傳進度條在媒體庫或區塊編輯器(古騰堡)裡跑到接近滿格,畫面忽然跳出一行沒有檔名、沒有錯誤代碼、什麼都不解釋的「HTTP error」(區塊編輯器有時顯示成「Error while uploading」),圖片就停在半路上,沒有真的進到媒體庫。多數人第一個反應是去查 php.ini 裡的上傳大小限制,調完再試一次,同一行錯誤照樣跳出來,因為這其實是完全不同的兩種故障,找錯地方,當然怎麼調都沒用。
WordPress 核心對「檔案超過上傳限制」這類故障,其實會給出明確指名是哪個 PHP 設定值出問題的訊息;真正含糊的圖片 HTTP error,發生在檔案已經傳輸完成之後,WordPress 接著要幫這張圖產生多種尺寸的縮圖(sub-sizes)時,PHP 處理程序因為資源不足被系統強制中斷,才留下這行看不出所以然的提示。分不清這兩種故障,就會一直繞在調大小限制上,永遠碰不到真正出問題的地方。
先把兩種故障的訊息實際長相攤開來對照,才知道自己現在卡在哪一關。
跳出的「HTTP error」和超過上傳限制不是同一種故障
故障發生時,畫面上能看到的資訊少得可憐。上傳進度先跑得很順,常常是跑到八、九成就停住,接著跳出一行「HTTP error」,沒有檔名、沒有錯誤代碼,也沒有任何一句話說明卡在哪裡;區塊編輯器有時連字都懶得換,直接顯示通用的「Error while uploading」。這跟真正的「超過上傳大小限制」完全不是同一種訊息。
WordPress 核心處理「檔案超過上傳限制」這類故障時,走的是完全不同的路,wp_handle_upload() 這支函式內建了一組具名的錯誤字串,會明確指出卡在哪個設定值。檔案超過 upload_max_filesize 時顯示的是「The uploaded file exceeds the upload_max_filesize directive in php.ini.」,超過表單裡的 MAX_FILE_SIZE 則顯示另一句對應訊息;同一組陣列裡還各自列了「檔案只上傳了一部分」「沒有檔案被上傳」「找不到暫存資料夾」「寫入磁碟失敗」「副檔名被擋下」這幾種獨立情境的訊息。這一整組訊息只在檔案傳輸階段,也就是 PHP 接收 $_FILES 時附帶的上傳錯誤碼,才會被觸發。本文要處理的圖片 HTTP error,發生在檔案已經傳輸成功之後,走的是完全不同的程式路徑,自然拿不到這些訊息,這正是它看起來如此含糊的根本原因。
這個落差還會因為用哪種上傳介面而更明顯。WordPress 官方 Gutenberg 專案的 issue 證實,傳統媒體庫介面遇到同一種故障時,會把伺服器實際回傳的錯誤內容顯示出來;區塊編輯器當時卻只顯示一句通用的「Error while uploading」,看不到任何細節。也就是說,同一張圖片、同一個故障,用傳統媒體庫上傳可能看得到具體原因,換成古騰堡卻只剩一句沒頭沒尾的提示,這一點在後面排查時很有用,可以直接決定要不要換介面重測。
提示訊息空洞的原因,卡在產生縮圖而非上傳本身
圖片明明沒有超過上傳限制,錯誤訊息卻依然空洞到什麼線索都沒有,原因出在原始檔案其實早就上傳成功、已經好好躺在伺服器上了。真正被打斷的,是 WordPress 接下來要幫這張圖自動產生多種尺寸縮圖(sub-sizes)的後製階段,同一張原始圖通常要另外算出縮圖版、中等尺寸、大尺寸,甚至螢幕解析度較高裝置專用的版本(2x),每一個尺寸都要重新運算一次。當這段運算因為資源不夠被系統中斷,瀏覽器端收到的回應往往殘缺不全,前端程式解析不出具體內容,只能退回顯示一句通用字串。

WordPress 從版本 5.3 開始,其實已經為這個環節內建了一套重試機制,而這套機制存在的本身,就等於官方承認「後製階段容易在資源不足時中斷」是個常見的痛點。做法是替每一次圖片上傳請求附上一組獨一無二的上傳識別碼;如果原始請求因為逾時或記憶體用盡,最終以伺服器錯誤收場,瀏覽器端會帶著同一組識別碼再送一次請求,讓伺服器針對缺漏的部分補產生縮圖,這個動作可以重複好幾次;真的全部嘗試都失敗,才會顯示錯誤訊息,同時清掉半途產生的孤兒檔案。
WordPress 官方在對應的 Gutenberg 專案 issue 裡,直接建議了一個具體做法,如果原始檔案是照片或大尺寸圖片,就先把它縮到 2500 像素以下再重新上傳。這句建議本身透露了修法的方向,圖片的尺寸與運算量,才是真正該處理的變數,不是上傳大小限制那個數字。
常見成因鎖定在記憶體、逾時、引擎與檔名權限四個切點
撇開症狀不談,真正讓圖片後製中斷的成因,其實可以收斂成幾個切點。最常見的 2 個是記憶體不足與 PHP 執行逾時,這兩者都跟伺服器能撥給這次運算多少資源、多少時間直接相關;比較少見、但確實會發生的另外 2 個,一個是 WordPress 背後可切換的 2 套影像處理引擎行為不一致,另一個是檔名與資料夾權限這類跟資源無關的環境問題。

PHP 記憶體上限,WordPress 內建兩層預設值
WordPress 對 PHP 記憶體上限這件事,其實內建了不只一層預設值,很多人調了其中一層卻沒調到真正管用的那層。前台一般頁面用的是 WP_MEMORY_LIMIT,單站預設 40MB、多站台預設 64MB;後台管理畫面用的是另一個獨立常數 WP_MAX_MEMORY_LIMIT,預設值是 256MB 或主機 php.ini 原本的 memory_limit 兩者取高。這是兩個各自獨立的常數,不是同一組設定的兩種寫法。
圖片處理走的是另一條路,實際呼叫的是 wp_raise_memory_limit('image') 這支函式,套用的預設值跟後台那組一樣,是 WP_MAX_MEMORY_LIMIT 或原始 memory_limit 兩者取高,也可以透過 image_memory_limit 這個過濾器單獨調整。這支函式的邏輯只會嘗試把限制往上調,不會往下調,而且一開始就先檢查主機的 memory_limit 這個 php.ini 值是不是可以被改變,如果主機把它鎖死,函式會直接放棄調整、回傳失敗。也就是說,已經在 wp-config.php 裡調過 WP_MEMORY_LIMIT 卻還是踩到同一種故障,很可能不是設定寫錯,而是主機那層本身就鎖死不讓改。
PHP 逾時限制讓大圖來不及產完所有尺寸
記憶體不是唯一的時間變數,PHP 執行時間的上限一樣會讓這個後製過程停擺。max_execution_time 管的是一支 PHP 腳本被系統強制中斷前,最多可以跑幾秒,預設值是 30 秒(命令列環境下預設是 0,也就是不限制,但網頁上傳走的是一般的 SAPI,不是命令列,所以還是受這個秒數限制)。一張大圖要依序算出縮圖版、中尺寸、大尺寸等好幾種版本,運算本身就需要時間,一旦這段時間超過 30 秒,PHP 同樣會被系統強制中斷,效果跟記憶體不足一樣是後製階段被打斷,只是原因不同、修法也不同,調記憶體救不了逾時的狀況。
WordPress 其實也內建了一層防呆,big_image_size_threshold 這個過濾器,只要原始圖片的寬或高超過設定的門檻,就會先把圖片縮小,再拿縮小後的版本當作可用的最大尺寸,預設門檻是 2560 像素。這代表 WordPress 官方本身也認定原始圖片尺寸是造成後製失敗的關鍵變數之一。不過就算有這層自動縮小保護,縮小這個動作本身也要耗運算資源,原始圖檔如果夠大、色彩夠複雜,光是這一步的運算就可能撞上逾時,並不是有這道防呆就完全不會出事。
Imagick 和 GD 耗用資源的方式並不相同
WordPress 背後其實有 2 套可以互相替換的影像處理引擎,預設嘗試順序是先試 Imagick、不支援才退回 GD,這個順序可以用過濾器覆寫。這 2 套引擎耗用系統資源的方式並不一樣,Imagick 把影像資料放在它自己獨立的記憶體管理機制裡,不是 PHP 的 emalloc,所以 PHP 端量到的記憶體用量,會遠低於系統實際消耗掉的記憶體。這代表主機後台監控看到「PHP 記憶體用量正常」,不等於「系統資源真的足夠」。
WordPress 核心原始碼裡也留了一句耐人尋味的註解,在載入圖片、準備呼叫 wp_raise_memory_limit('image') 之前,官方特別寫著即使 Imagick 用的 PHP 記憶體比 GD 少,仍要幫 php.ini 設定值偏低的使用者拉高限制。這句註解等於是 WordPress 官方自己承認,這 2 套引擎的資源行為並不一致,需要分別因應,不能套用同一套判斷標準。
更麻煩的是,ImageMagick(Imagick 底層依賴的程式庫)本身還有一層完全獨立於 PHP memory_limit 之外的資源限制機制,可以透過 policy.xml 安全政策、或是 MAGICK_MEMORY_LIMIT、MAGICK_DISK_LIMIT、MAGICK_AREA_LIMIT、MAGICK_THREAD_LIMIT 這類環境變數設定。多數主機商為了避免單一使用者拖垮共用資源,會把這層限制鎖得比 PHP 端更緊。也就是說,就算把 PHP 的 memory_limit 調得再高,Imagick 仍然可能因為自己這層限制而處理失敗,這種情況下換一套引擎,有時候比死命調高記憶體數字更有效。
特殊檔名與資料夾權限也可能讓上傳失敗
前面幾種都跟資源多寡有關,還有另一類比較少見、但真的會發生的成因,是檔名與資料夾權限。檔名如果帶了特殊符號,像是單引號或非 ASCII 字元,伺服器在搬移或重新命名檔案時,有機率因為這些符號而出錯,尤其是後製階段要把算好的縮圖檔案寫進特定資料夾的那一刻。
wp-content/uploads 目錄或當月的子目錄,如果權限設定不對,一樣會讓新產生的縮圖檔案寫不進去。WordPress 官方預設的權限值是目錄 0755、檔案 0644,對應到 FS_CHMOD_DIR 與 FS_CHMOD_FILE 這兩個常數;權限比預設更嚴格,就可能讓寫入動作失敗。另外,PHP 接收上傳檔案時,會先把它暫存到伺服器的暫存目錄,路徑由 upload_tmp_dir 這個 php.ini 設定值指定,這個暫存目錄本身如果空間不足或權限不對,故障甚至會提前發生在檔案暫存這一步。
另外要注意的是,WordPress 核心其實對「暫存資料夾不存在」「寫入磁碟失敗」這類問題,本來會給出具體的具名訊息,但那組訊息只在檔案上傳最初的傳輸階段才會觸發。如果失敗發生在後製階段寫入縮圖檔案的當下,同樣會因為權限或磁碟空間問題被中斷,卻拿不到那組具名訊息,一樣只會顯示成含糊的圖片 HTTP error。
先分辨故障卡在資源,還是卡在設定
找到成因前,排查的順序比修法清單更重要。先用最快、最不花力氣的方式縮小範圍,確定問題出在哪個方向,再決定要不要去動設定檔或找主機客服,而不是把上面幾種成因逐一套用修法試一遍。
接下來要做的兩個判斷其實不複雜,先確認是不是所有圖片都會失敗,再確認是不是只有區塊編輯器才會失敗,這兩件事就能大幅縮小要往哪個方向修的範圍,再決定要調記憶體、換引擎,還是該去找主機。

換一張小圖片上傳,先排除資源耗盡的可能
先準備一張確定檔案很小的圖片重新測試,例如 100KB 以內、長寬都在 1000 像素以內。如果這張小圖能順利上傳,原本那張大圖卻不行,代表故障點確實出在資源或處理耗時,不是外掛衝突或權限設錯這類會讓所有圖片都失敗的全站性問題。
反過來,如果連這張很小的圖片都一樣跳出圖片 HTTP error,代表問題根本不在圖片本身的尺寸,這時候該往檔名、權限、外掛衝突的方向找,而不是繼續在記憶體或逾時的設定上打轉。這一步幾乎不花時間,卻能立刻幫接下來的排查定出大方向,值得當成第一步優先做。
改用傳統媒體庫上傳,能看到更明確的錯誤訊息
第二個便宜的測試,是跳出文章編輯器,直接進入媒體庫頁面,或使用舊版上傳工具,上傳同一張圖片。前面提過,WordPress 官方的 issue 已經證實,傳統媒體庫介面遇到同一種故障時,會把伺服器實際回傳的錯誤內容顯示出來,不像區塊編輯器容易退回顯示通用字串。
換個介面測試同一張圖,拿到的線索可能完全不一樣,判斷是記憶體不足、逾時,還是別的原因,會比只在區塊編輯器裡反覆重試準確得多。這一步同樣不需要改任何設定,只是換個入口而已,順手就能做。
開啟除錯紀錄,比對 PHP 中斷的確切位置
如果前兩個測試還是無法判斷方向,可以透過 WP_DEBUG 與 WP_DEBUG_LOG 這兩個常數,把 PHP 的錯誤紀錄寫進 wp-content/debug.log 這支檔案,再重現一次上傳失敗,回頭比對紀錄檔在同一時間點寫了什麼。出現跟記憶體相關的字樣,對應的就是前面記憶體那個成因;出現跟執行時間上限相關的字樣,則對應逾時那個成因,兩種對應到完全不同的修法,靠猜測很容易白工。
要注意的是,WP_DEBUG_LOG 要設成 true 才會真的把錯誤寫進檔案,只開 WP_DEBUG 只會把訊息顯示在畫面上;查完之後記得把 WP_DEBUG_DISPLAY 關掉,避免除錯訊息曝露給一般訪客看到。這支紀錄檔是整個排查過程裡最直接的證據,比憑經驗猜測準確得多。
跟主機核對 PHP 設定的實際值,不是後台顯示的數字
WordPress 後台「網站健康」工具或某些外掛顯示的 PHP 資訊,反映的是 WordPress 自己嘗試呼叫 wp_raise_memory_limit() 之後、目前生效中的結果,不等於主機真正允許的上限。共享主機常見的情況是在 php.ini 或伺服器層再蓋一層更緊的限制,這種情況下不管在 wp-config.php 裡怎麼調整常數都不會生效,呼應前面提過函式偵測到主機值鎖死就直接放棄調整的那個邏輯。
真正要確認的,是直接找主機客服問清楚目前的 memory_limit、max_execution_time、upload_tmp_dir 這幾個實際數值,以及是否可以調整。有些主機商能協助個別網站調高這幾個值,有些共享方案則完全鎖死,只能靠後面「上傳前把圖片壓到 2500 像素上下」這個不依賴主機設定的做法繞過去。
記憶體和逾時,兩個最先動手的修法
排查完鎖定成因之後,接下來是動手修。修法要對應到排查出來的成因,不是每一種都無腦全部套用一遍,確定是記憶體問題就先動記憶體,確定是逾時就先看逾時,亂槍打鳥反而浪費時間。
wp-config.php 拉高兩個記憶體常數的上限
確定問題出在記憶體之後,具體做法是在 wp-config.php 裡加 2 行常數,位置要放在檔案裡 /* That's all, stop editing! */ 這行提示之前:define( 'WP_MEMORY_LIMIT', '256M' ); 影響的是一般前台頁面的記憶體上限,define( 'WP_MAX_MEMORY_LIMIT', '256M' ); 影響的則是後台管理畫面與圖片處理共用的那組上限,這兩個常數都要放在 wp-settings.php 載入之前才會生效。
WordPress 會自動比較這裡設定的值跟目前實際生效中的值,不會平白把限制調低,也不會超過主機本身的硬限制。換句話說,這兩行常數能不能真的生效,前提是主機的 php.ini 本身允許調到這個範圍,不是寫了數字就一定拉得上去。呼應前面排查那一節的結論,如果主機把 memory_limit 鎖死,這兩行常數再怎麼調都不會有反應,得先跟主機客服確認實際能調到多少。
換掉圖片處理引擎,讓伺服器改用 GD
如果排查結果指向 Imagick 那層獨立的資源限制,而不是 PHP 本身的記憶體,換一套引擎有時候比死命調高記憶體數字更有效。做法是透過程式碼,掛在佈景主題的 functions.php 或站台專屬外掛裡,改變 WordPress 預設的引擎嘗試順序,強制只用 GD、跳過 Imagick。預設順序是 Imagick 優先、GD 其次,這個順序可以透過過濾器回傳自訂陣列來覆寫,也可以只保留單一引擎。
反過來,如果是 GD 在處理大圖時比較容易撞上 PHP 的 memory_limit,也可以強制優先用 Imagick。要留意的是,改引擎之前最好先確認主機兩套函式庫都真的有安裝,否則 WordPress 會自動退回到有安裝的那一套,改了程式碼也不會生效。這是需要動一段程式碼的技術調整,改完務必拿原本會失敗的那張圖重新測試,才知道有沒有真的解決問題。
上傳前把圖片壓到 2500 像素上下
前面幾種修法都要動設定檔或找主機,還有一個更根本、完全不依賴主機權限的做法,就是上傳前先把圖片本身處理過。WordPress 官方在錯誤訊息裡建議的門檻,就是把原始檔案的長邊控制在 2500 像素上下,這個數字跟 WordPress 內建的自動縮小門檻 2560 像素幾乎一致,不是憑空建議出來的。
具體做法是避免直接上傳相機或手機直出的原始尺寸大圖,上傳前先把長邊壓到 2500 像素上下,同時把檔案大小控制在合理範圍。這個做法對記憶體不足、逾時、Imagick 資源限制這 3 種成因都有效,因為三者的根源都指向同一件事,也就是這張圖要處理的運算量太大。對沒有主機管理權限的讀者(像很多共享主機使用者)來說,這是最快能繞開整個問題的方式。
改完設定後,要用原本失敗的那張圖再驗一次
設定調整完,驗證這一步不能隨便拿一張新圖片交差,而是要用原本那張確定會失敗的原始檔案重新上傳一次。如果換成別張本來就會過的小圖,測出來的結果什麼都證明不了。
上傳成功後,進附件詳細資訊確認縮圖是不是真的產齊了,像是縮圖版、中尺寸、大尺寸這些版本都要看得到,而不是只剩下原始尺寸那一份,那種情況代表後製其實還是卡在半路,只是剛好沒跳出錯誤訊息。如果前面有開除錯紀錄,也該回頭比對同一個時間點的紀錄檔,確認記憶體或執行逾時相關的字樣不再出現。
如果最後選擇壓縮圖片這個治本做法,日後編輯上傳習慣也要跟著調整,不然哪天換了一支拍照解析度更高的手機,或用了一台新相機,拍出的原始檔案又超過同一個門檻,同樣的圖片 HTTP error 還是會再跳出來一次。記憶體、逾時、引擎限制,其實都是同一套資源與時間問題,在不同主機環境下呈現的不同樣貌,先分清楚自己卡在哪一種,修法才不會白費工夫。
