同一段 PHP 程式碼,在沒有 OPcache 的伺服器上,每一次請求都要重新被讀取、解析、編譯成位元組碼,然後才開始執行。一個裝了二、三十支外掛的 WordPress 站,光是一次後台請求就可能載入好幾百個 PHP 檔,這些「重新編譯」的成本被乘以每天成千上萬次請求,全部壓在 CPU 上。
WordPress OPcache 設定要解決的就是這件事:讓編譯只發生一次,之後的請求直接拿共用記憶體裡編好的位元組碼來跑。設定對了,繁忙站台的 TTFB(首位元組時間)通常能省下數百毫秒,流量尖峰時的 CPU 用量也明顯下降;設定錯了,或乾脆用預設值,大半好處就留在桌上沒拿。
這篇會講清楚 OPcache 在 WordPress 上到底做什麼、哪幾個參數真正影響效能、該填什麼值、JIT 要不要開、怎麼確認快取真的在運作,以及最容易踩雷的「更新後程式碼沒生效」要怎麼處理。對象是自己能改 php.ini 的自架使用者,以及用虛擬主機面板但想看懂這些數字的站長。
OPcache 在 WordPress 的快取堆疊裡扮演什麼角色
OPcache 是 PHP 內建的位元組碼快取,位置在所有快取層的最底下。頁面快取(page cache)省掉的是整頁 HTML 的重複產生,物件快取(object cache)省掉的是資料庫查詢的重複;OPcache 比這兩層更低一階,它省掉的是 PHP 直譯器把原始碼解析、編譯成 Zend opcode 這段工作。
這個位置決定了它的價值。任何沒被頁面快取擋下來的請求,每一次登入後台的操作、每一次 REST API 呼叫、每一次 WP-CLI 指令,都還是要實際執行 PHP。OPcache 決定這些 PHP 是每次都從原始碼重新編譯,還是直接從共用記憶體拿預編譯好的位元組碼。對 WooCommerce 這類購物車、結帳、會員中心頁面天生繞過整頁快取的站,OPcache 往往是少數還能幫上忙的快取層。
要先講清楚它不做什麼,才不會誤會它的效果。OPcache 只快取一樣東西,就是編譯後的 PHP 位元組碼。它不快取 HTML、不快取資料庫查詢結果、也不快取任何應用層資料。所以一支慢的資料庫查詢、一個同步等待的外部 API 呼叫,OPcache 一點都幫不上忙。它是地板不是天花板,把編譯成本降到趨近於零,但其他瓶頸還在的話,頁面一樣慢。
還有一個版本變化值得記著。從 PHP 8.5 起,OPcache 已經內建編譯進 PHP,不再是可選的擴充套件,新環境不會再不小心漏掉它。PHP 8.4 以前它技術上仍是可選的,用 Docker 映像檔或自行編譯時有機會編出一個沒載入 OPcache 的 PHP,所以動手調校之前,先確認它真的存在。
怎麼確認 OPcache 已經載入並正在運作
動任何設定之前,先確認擴充套件有載入、並量一下目前的狀態。最直接的方式是在伺服器上跑指令查看 OPcache 區塊:
php -i | grep -i "opcache"
正常會看到一段包含 Opcode Caching => Up and Running 的輸出,後面列著每個 OPcache 參數的現值。如果看到的是 Disabled,或根本沒有 OPcache 區塊,那就是擴充套件沒載入,這時候調什麼參數都沒用,得先安裝 php-opcache 套件並重新載入 PHP-FPM。
這裡有一個極常見的誤判要提醒。php -i 跑的是 CLI(命令列)的 PHP,CLI 跟 PHP-FPM 是兩套各自獨立的 OPcache,由 opcache.enable_cli 另外控制,預設關閉。也就是說,你在命令列看到的數字,不等於網站訪客實際走的那套快取。要看 PHP-FPM 真正在用的狀態,得從一個經由 PHP-FPM 執行的網頁請求去讀。
實務上的做法是在網站根目錄放一支受保護的小腳本,用 opcache_get_status() 把即時狀態以 JSON 印出來,看完立刻刪掉:
<?php
// 放在 docroot,看完務必刪除,不要留在正式環境
if (!isset($_GET['token']) || !hash_equals('改成很長的隨機字串', $_GET['token'])) {
http_response_code(403);
exit;
}
header('Content-Type: application/json');
echo json_encode(opcache_get_status(false), JSON_PRETTY_PRINT);
opcache_get_status() 的輸出若公開暴露算是資訊洩漏,用完一定要刪。裡頭真正要看的欄位是記憶體使用量、interned strings 使用量、已快取腳本數、快取命中率,以及 oom_restarts 這幾項,後面調校會反覆回頭看它們。
如果你不想碰命令列,較新的 WordPress 在後台「工具 → 網站健康 → 資訊 → 伺服器」區塊也會顯示 OPcache 是否啟用。但要清楚它只是「有沒有開」的通過或失敗檢查,不是調校用的診斷工具,看狀態的本體還是 opcache_get_status()。
WordPress 該調的幾個 OPcache 參數與建議值
OPcache 有數十個 ini 參數,但在 WordPress 上,真正決定它是有效運作還是白佔記憶體的,是下面這幾個。預設值是 PHP 為了保守用資源而設的,不是為正式 WordPress 站設的,照搬預設正是大半人沒拿到效果的原因。
opcache.memory_consumption:配給 OPcache 的共用記憶體總量,單位 MB,預設 128。這是最常見的設定不足。一個裝了二、三十支外掛的站很容易把 128 MB 填滿,填滿後 OPcache 開始把舊項目逐出(evict)以騰空間,命中率隨之崩跌,每一次逐出都代表下一個請求得重新編譯。一般 WordPress 站從 256 MB 起跳較安全;WooCommerce、多站台、或外掛超過四十支的,從 384 MB 或 512 MB 開始。警訊是 oom_restarts(記憶體不足重啟)這個計數器隨時間持續上升,只要它非零且一直長,就代表記憶體配太小。
opcache.interned_strings_buffer:給 interned strings(跨腳本去重複的相同字串)用的記憶體,單位 MB,預設 8。WordPress 的程式碼裡有大量重複的字串常數,例如 hook 名稱、選項鍵、翻譯字串,這個緩衝區就是讓這些字串不會在快取裡被重複存好幾份。8 MB 在大型站很快就用光,症狀跟記憶體不足一樣是逐出與重新編譯。正式站建議至少 16 MB,外掛多或 WooCommerce 的給 32 MB。要注意這塊是從 memory_consumption 裡切出來的,設 256 加上 32,實際給位元組碼的就剩 224 MB。
opcache.max_accelerated_files:OPcache 最多快取幾個腳本,預設 10000。這個值內部會被進位到一組質數裡的下一個,常見的可選值是 3907、7963、16229、32531、65521。一個最精簡的 WordPress 約三千個 PHP 檔,裝了二十支外掛加一套主題大概在八千到一萬五千檔之間,光 WooCommerce 自己就多好幾千。檔案數超過上限後,OPcache 就算還有記憶體也會停止快取新檔。先數一下實際檔案數:
find /var/www/你的站台 -name "*.php" -type f | wc -l
數出來後挑質數清單裡比它大一截的值。多數正式站用 32531 很夠;WooCommerce 或外掛很多的多站台用 65521。警訊是狀態裡的已快取鍵數逼近上限,或出現 hash_restarts。記得 Composer 的 vendor 目錄、mu-plugins 也都算進去。
opcache.validate_timestamps:是否每次檢查磁碟上的檔案有沒有比快取新,預設 1(檢查)。這個值決定了「更新外掛後新程式碼會不會生效」。對大多數自架站,維持 1。把它設成 0 是效能最高的選項,因為 PHP-FPM 會省掉每個被引入檔案的一次 stat() 系統呼叫,但代價是快取裡的位元組碼會比磁碟上的原始檔活得更久,除非重新載入 PHP-FPM 或明確呼叫 opcache_reset(),否則會一直送舊程式碼。只有在你有一條會於每次部署時自動執行重置的部署流程時,才設成 0;少了那一步,外掛更新甚至安全性修補會看似部署成功,實際上完全沒生效。
opcache.revalidate_freq:當 validate_timestamps=1 時,每隔幾秒檢查一次檔案時間戳,預設 2。正式站把它設成 60 是個好選擇。預設的 2 秒意味著幾乎每個請求都在檢查可能上千個檔案的修改時間,在繁忙站台很浪費;60 秒代表一個快取腳本最多舊一分鐘,對任何非即時開發的 WordPress 流程都夠用。設成 0 會強制每個請求都檢查,那只適合本機開發環境。
opcache.save_comments:是否在快取的位元組碼裡保留 PHP docblock 註解,預設 1。維持 1。設成 0 雖然能讓位元組碼小一點,但會弄壞任何在執行期讀取 docblock 註解的程式,PHP 官方手冊明確警告這會破壞依賴註解解析的框架。WordPress 生態裡有數支外掛靠 docblock 解析來辨識 hook、REST API 路由或 CLI 指令,省下的那幾個百分比記憶體,不值得換來難以診斷的外掛失靈。
一份可直接套用的正式環境設定範本
把研究出來的值整理成一份自帶說明的設定檔,丟進你發行版的 OPcache ini 片段裡。Debian、Ubuntu 路徑通常是 /etc/php/8.3/mods-available/opcache.ini;Red Hat 系是 /etc/php.d/10-opcache.ini。每一行都寫成明確覆寫,即使跟預設相同也列出來,方便日後維護的人一眼看懂。
; WordPress 正式環境 OPcache 設定
; 針對 25 至 40 支外掛、WooCommerce、PHP-FPM 的站台
opcache.enable=1
opcache.enable_cli=0 ; CLI 是另一套快取,非必要不開
; 記憶體配置
opcache.memory_consumption=256 ; 共用記憶體總量 MB
opcache.interned_strings_buffer=32 ; 從上面那筆切出來
opcache.max_accelerated_files=32531 ; 內部進位到質數
; 新鮮度政策
opcache.validate_timestamps=1 ; 只在部署時會自動重置才改 0
opcache.revalidate_freq=60 ; 每檔每分鐘檢查一次
opcache.file_update_protection=2 ; 不快取近 2 秒內被改過的檔
; 安全性
opcache.save_comments=1 ; 註解導向的程式需要
opcache.max_wasted_percentage=10 ; 容許 10% 碎片後才重啟
; JIT(見下一節)
opcache.jit=disable
opcache.jit_buffer_size=0
存檔後重新載入 PHP-FPM,指令例如 sudo systemctl reload php8.3-fpm,讓站台跑一段真實流量再看狀態。WooCommerce 或多站台這種重型站,把記憶體那兩行改成 memory_consumption=512 與 max_accelerated_files=65521 即可。
用虛擬主機面板而沒有 SSH 的人,這些值一樣有意義,只是改的位置不同。cPanel 走「MultiPHP INI Editor」,寶塔在「軟體商店」的 PHP 設定裡有 OPcache 分頁,Cloudways、Kinsta 這類代管平台則由主機商管理、靠它的部署流程重置快取,通常不開放改 ini。改不了的時候,把上面這幾個建議值整理成具體數字,直接開單請主機商幫你調,比含糊地說「網站很慢」有效得多。
PHP JIT 對 WordPress 該不該開
先給結論:標準 WordPress 站把 JIT 關著就好,除非你有量測過、確定有特定吃 CPU 的工作負載受惠。
PHP 8.0 在 OPcache 之上加了 JIT 編譯器。對影像處理、科學計算、純 PHP 跑分這類吃 CPU 的工作,JIT 能帶來真實的加速。但 WordPress 是 I/O 密集型的,一次請求的時間絕大部分花在等資料庫查詢、等 Redis、等外部 HTTP 呼叫、等檔案系統讀取,真正在跑 PHP 程式碼的時間只佔一小部分。JIT 只加速 PHP 程式碼的執行,所以在 WordPress 上的跑分通常只看到 1 到 5 percent 的改善,還得換來額外的 JIT 緩衝記憶體與偶發的相容性問題。
版本預設值有個轉折要記著。PHP 8.3 以前,opcache.jit 預設是 tracing、opcache.jit_buffer_size 預設 0;到了 PHP 8.4,預設改成 opcache.jit=disable、opcache.jit_buffer_size=64M。不論哪個版本,想明確關掉就把這兩行都寫死成 disable 與 0。真的想試的話,用 opcache.jit=tracing 搭配 64 至 128M 的緩衝,然後在同一段真實流量下比較啟用前後的 TTFB 與 CPU,量不出差異就關回去。
怎麼判斷你的 OPcache 是健康的
重新載入 PHP-FPM 後,讓站台在真實流量下跑至少三十分鐘,再回頭讀 opcache_get_status()。一個調得好的 WordPress OPcache,幾個關鍵指標應該長這樣:
| 指標 | 健康的值 | 不對時代表什麼 |
|—|—|—|
| opcache_enabled | true | 擴充套件沒載入或 enable=0 |
| cache_full | false | memory_consumption 太小 |
| opcache_hit_rate(命中率) | 98% 以上 | 有東西在逼它重新編譯 |
| oom_restarts | 0 | 記憶體不足、快取被迫逐出 |
| hash_restarts | 0 | max_accelerated_files 太小 |
| num_cached_keys(已快取鍵數) | 遠低於上限 | 還有空間可快取更多腳本 |
| interned_strings free_memory | 非零 | interned 緩衝區還沒用光 |
命中率拉到 98% 以上是調得好的目標,掉到 95% 以下就代表 OPcache 做的工比應該做的少。診斷的訣竅是「看症狀,不要用猜的」,每次只動一個參數,改完讓正式流量跑滿一個週期再判斷:
- 命中率持續偏低(70 至 80%):多半是快取剛被重置或 worker 剛啟動還沒暖機,先讓它跑滿三十分鐘真實流量再下判斷;還是上不去就回頭看
oom_restarts與hash_restarts。 oom_restarts一直增加,或cache_full變 true:記憶體配太小,把memory_consumption加倍後重新載入。- 已快取鍵數逼近上限,或
hash_restarts非零:檔案數超過max_accelerated_files,重數一次 PHP 檔再往上調一級質數。 - 命中率明明 99% 但 TTFB 還是高:OPcache 已經盡責了,瓶頸在別處,去查慢查詢、同步外部呼叫、PHP worker 被吃滿這些方向。
如果你習慣待在 wp-admin 而沒有 shell,OPcache Manager、WP OPcache 這類外掛能在後台直接顯示命中率、記憶體與重啟原因;想看趨勢圖的話,amnuts 的 OPcache GUI、CacheTool 這類獨立工具提供更細的單檔列表與遠端重置。指標看的還是同一組:命中率約 99% 為健康、已用對未用記憶體、已快取鍵數對上限,以及兩個重啟計數器。
部署與更新後,怎麼讓新程式碼確實生效
OPcache 以「腳本路徑加上時間戳」當索引鍵來快取位元組碼。每次你改了程式碼上線,新程式碼要生效,下面三條路必須走其中一條。
第一條,validate_timestamps=1 的情況。OPcache 會在 revalidate_freq 秒內(上面範本是 60 秒)注意到新的時間戳並自動重新編譯,你什麼都不用做。這是預設,也是大多數從 wp-admin 直接更新外掛的 WordPress 站該走的路。如果你發現每次更新外掛後網站會壞掉約一分鐘,通常就是 revalidate_freq 設太長或時間戳驗證被關了,把它調回 60 以下即可。
第二條,validate_timestamps=0 且你剛部署完。OPcache 根本不知道磁碟上的檔案換了,你得明確讓它失效。最簡單而且永遠有效的是重新載入 PHP-FPM,例如 sudo systemctl reload php8.3-fpm,它會清掉每個 worker 的快取並重啟。若部署帳號不能 sudo,可以放一支受密碼保護的單行腳本在 docroot,部署腳本同步完檔案後用 curl 打它觸發 opcache_reset():
<?php
// /wp-content/opcache-reset.php,用密碼保護
if (!isset($_GET['token']) || !hash_equals('改成很長的隨機字串', $_GET['token'])) {
http_response_code(403);
exit;
}
echo opcache_reset() ? 'reset' : 'failed';
這裡藏著最容易踩的雷:opcache_reset() 是分 SAPI 的。從命令列跑 php -r "opcache_reset();" 只重置了 CLI 那套快取,不會清到 PHP-FPM 的快取。要清 PHP-FPM 的快取,這支重置一定得透過 PHP-FPM 執行的網頁請求去呼叫。很多人部署腳本裡寫了 CLI 重置,結果新程式碼遲遲不生效,原因就在這。
第三條,乾脆重新載入 PHP-FPM。這比 opcache_reset() 重一些,但保證乾淨的全新狀態,是最不會出錯的選擇。實務上,把「驗證時間戳設 1、revalidate_freq 設 60」當預設就能應付絕大多數情境;只有當你想榨出最後那點效能、且願意在部署流程裡認真維護重置步驟時,才走 validate_timestamps=0 那條路。
從確認載入到看懂指標,OPcache 該怎麼一步步調起來
OPcache 是 WordPress 伺服器上投資報酬率最高的單一效能設定之一,因為它打中的是每一個沒被快取擋下來的動態請求,包括後台、REST API、以及 WooCommerce 那些天生繞過頁面快取的頁面。但它的好處幾乎全被預設值鎖住了,照搬 128 MB 與 10000 檔的預設,等於只拿到一小部分。
把節奏抓成這樣就不會亂:先確認擴充套件真的載入、且看的是 PHP-FPM 而非 CLI 那套;數一下實際 PHP 檔案數,記憶體從 256 MB、檔案上限從 32531 起跳;JIT 維持關閉,除非量測證明有 CPU 瓶頸;設好後讓真實流量跑滿三十分鐘,再用 opcache_get_status() 對著命中率、oom_restarts、hash_restarts 三個數字調整,每次只動一項。最後別忘了把快取重置寫進部署流程,並且確認那支重置是經由 PHP-FPM 觸發的,否則更新會看似成功卻沒生效。把這條路走完一遍,你的 PHP 編譯成本就被壓到趨近於零,剩下的效能工夫,就能專心投在資料庫與其他真正的瓶頸上。