同一个 opcache三个信号都指向有问题。php.ini 里它被注释着。CLI 说它是 Disabled。php -m里它出现两次。三个都是误读。真实结论是opcache 从头到尾都在正常工作。上一篇讲的是「1Panel 部署 ThinkPHP8 踩坑实录」。这一篇讲反面。有些东西看起来像坏了其实一直是好的。我复验 PHP 环境的时候记录里挂着一条红色阻塞写的是 opcache 未加载。判据有三条每条看着都很硬。第一条php.ini 里zend_extensionopcache是注释掉的。第二条php -i说Opcode Caching是 Disabled。第三条php -m | grep opcache输出了两行。机器还是上一篇那台。1Panel v2PHP 8.4.25 跑在容器里基础镜像 Debian 13。Web 是 OpenResty 容器FastCGI 转发到127.0.0.1:9000。框架是 ThinkPHP 8.0app/下 104 个类。正因为类多opcache 才有价值。命令里用变量代替容器名先在终端执行一次# 换成你在 1Panel 里给 PHP 运行环境起的名字 容器名exportPHP_CTNphp84回显来源说明一下。标「服务器实测」的来自这次部署2026-09-17。标「本机复现」的是我在同一版本 PHP 8.4.25 的 CLI 上做的对照实验。后者是必要的。下面要证明的几个机制光靠服务器上一张快照说不清楚必须做对照。php.ini 里它是注释掉的1Panel 生成的 php.ini 在宿主机上。直接读它# 在宿主机执行不要套 docker exec原因见本节末尾grep-nzend_extension/opt/1panel/runtime/php/$PHP_CTN/conf/php.ini命中的只有一行;zend_extensionopcache分号开头。再往下翻[opcache]整段也都是注释。一个扩展的加载开关被注释掉了那它当然没加载。这是第一条判据的由来。实际是php.ini 不是唯一的配置源。官方 PHP 镜像以及基于它的各类 fpm 镜像有一套 conf.d 机制。每启用一个扩展就往/usr/local/etc/php/conf.d/里放一个独立的 ini 文件。php.ini 只管基础指令。扩展的加载声明在 conf.d 里。php --ini会把配置来源全部打印出来dockerexec-i$PHP_CTNphp--ini回显2026-09-17 那天服务器实测Configuration File (php.ini) Path: /usr/local/etc/php Loaded Configuration File: /usr/local/etc/php/php.ini Scan for additional .ini files in: /usr/local/etc/php/conf.d Additional .ini files parsed: /usr/local/etc/php/conf.d/docker-php-ext-bcmath.ini, /usr/local/etc/php/conf.d/docker-php-ext-ftp.ini, /usr/local/etc/php/conf.d/docker-php-ext-gd.ini, /usr/local/etc/php/conf.d/docker-php-ext-gettext.ini, /usr/local/etc/php/conf.d/docker-php-ext-intl.ini, /usr/local/etc/php/conf.d/docker-php-ext-mysqli.ini, /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini, /usr/local/etc/php/conf.d/docker-php-ext-pcntl.ini, /usr/local/etc/php/conf.d/docker-php-ext-pdo_mysql.ini, /usr/local/etc/php/conf.d/docker-php-ext-redis.ini, /usr/local/etc/php/conf.d/docker-php-ext-shmop.ini, /usr/local/etc/php/conf.d/docker-php-ext-soap.ini, /usr/local/etc/php/conf.d/docker-php-ext-sockets.ini, /usr/local/etc/php/conf.d/docker-php-ext-sysvsem.ini, /usr/local/etc/php/conf.d/docker-php-ext-xmlrpc.ini, /usr/local/etc/php/conf.d/docker-php-ext-zip.ini, /usr/local/etc/php/conf.d/xx-php-ext-memcached.ini17 个扩展 ini。opcache 的加载声明就在第 7 行那个文件里。看一眼它的内容顺便确认模块真的在# 打印 opcache 的 ini 内容 确认模块已加载dockerexec-i$PHP_CTNbash-ccat /usr/local/etc/php/conf.d/docker-php-ext-opcache.ini; php -m | grep -i ^Zend OPcache$zend_extensionopcache Zend OPcache Zend OPcache第一行就是那个文件的全部内容。只有zend_extensionopcache一行没有任何opcache.*参数。判据php.ini 里注释掉不等于没启用。任何 PHP 配置结论先把php --ini跑一遍。确认配置有几个来源再去读对应的文件。php --ini的输出记后三行就够。Loaded Configuration File主配置。Debian 的php.ini-production模板约 900 行绝大部分是注释说明。Scan for additional .ini files in附加配置目录。Additional .ini files parsed实际生效的附加文件清单。扩展在这里。顺带一个坑。/opt/1panel/runtime/php/运行环境名/conf/是宿主机路径容器内不存在。读它要直接在宿主机 cat。写成docker exec里去 cat/opt/1panel/...。只会得到No such file or directory。容器内的对应路径是/usr/local/etc/php/php.ini。CLI 说它 Disableddockerexec-i$PHP_CTNphp-i|grepOpcode Caching这条命令会回你Opcode Caching Disabled非常像没开。于是第二条判据成立。实际是Disabled只说明 CLI 这个 SAPI 不启用 opcache 缓存。它不等于模块没加载更不等于 FPM 没开。原因是一个默认值。opcache.enable_cli 0。而docker exec里跑的 php永远是 CLI SAPI。我在同版本 PHP 8.4.25 上做了对照实验两组只差这一个开关。本机复现。实验 A 是默认值opcache.enable_cli为 Off。回显里Opcode Caching是 Disabledopcache.enable却是 On。实验 B 显式开启 enable_cli。Opcode Caching变成 Up and Runningopcache.enable还是 On。实验 A 的完整回显本机复现Opcode Caching Disabled Startup Failed Opcode Caching is disabled for CLI opcache.enable On On opcache.enable_cli Off Off实验 B本机复现Opcode Caching Up and Running opcache.enable On On opcache.enable_cli On On请盯住实验 A 的第三行。opcache.enable On On。同一个模块同一个进程全局开关明明白白是 On。上面那句Disabled描述的是 CLI 不缓存被当成了 opcache 没开。只 grepOpcode Caching一行就会得出完全相反的结论。正确的验证方式用探针脚本从 Web 层问它。放到站点根目录浏览器访问一次。看完立刻删除。?php// opcache-probe.php —— 放到站点根目录浏览器访问一次看完立即删除header(Content-Type: text/plain; charsetutf-8);// 模块都没加载的话后面全白搭先短路if(!function_exists(opcache_get_status)){exit(opcache 模块未加载 —— 先解决模块问题\n);}$statusopcache_get_status(false);printf(当前 SAPI : %s\n,PHP_SAPI);printf(opcache.enable : %s\n,ini_get(opcache.enable));printf(opcache.enable_cli : %s\n,ini_get(opcache.enable_cli));printf(缓存是否真正启用 : %s\n,!empty($status[opcache_enabled])?yes:no);printf(已缓存脚本数 : %d\n,count($status[scripts]??[]));printf(内存用量 : %.1f MB\n,($status[memory_usage][used_memory]??0)/1048576);printf(命中率 : %.2f%%\n,$status[opcache_statistics][opcache_hit_rate]??0);同一个脚本在 CLI 下跑本机复现enable_cli 设成 1当前 SAPI : cli opcache.enable : 1 opcache.enable_cli : 1 缓存是否真正启用 : yes 已缓存脚本数 : 0 内存用量 : 8.7 MB 命中率 : 0.00%再把opcache.enable_cli改回默认的 0脚本一字不动地重跑。缓存是否真正启用立刻变成 no。opcache.enable依旧是 On。同一份 PHP、同一个模块、同一个脚本结论却相反。差别只在这次是谁在跑。要盯住输出里的当前 SAPI。它是 cli 的那一刻这份结果关于 opcache 的结论就只能代表 CLI。要判断 FPM让脚本从 Web 层跑。那时 SAPI 会变成fpm-fcgi那一行缓存是否真正启用才是你要的答案。这次部署的记录里留的也是对的口径。要真正确认走 Web 层放一个临时phpinfo.php用完务必删除。探针没跑就别把 CLI 的Disabled当成 FPM 的结论。这是两条判据混用导致的误判。php -m 打印两行dockerexec-i$PHP_CTNphp-m|grep-i^Zend OPcache$回显2026-09-17 那天服务器实测Zend OPcache Zend OPcache两行。看着像加载了两次。实际是php -m的输出分两段。[PHP Modules]和[Zend Modules]。opcache 是 Zend 扩展于是它在两个段里各出现一次。同一个模块被列在两处不是加载两次。本机复现加上行号就一目了然33-xmlwriter 34:Zend OPcache ← 第一处位于 [PHP Modules] 段的字母序 Z 位置 35-zip ... 38-[Zend Modules] 39:Zend OPcache ← 第二处位于 [Zend Modules] 段两个不同的行号两个不同的段。grep -n的行号在这里又一次直接给出了答案。那怎么判断真的重复加载别数php -m的行数。去看 conf.d 里有几个 opcache ini。上面那份php --ini清单里docker-php-ext-opcache.ini只出现了一次。这就够了。要自己复核# 复核conf.d 里有几个 opcache 的 ini应为 1 个dockerexec-i$PHP_CTNbash-cls -1 /usr/local/etc/php/conf.d/ | grep -i opcache真正会踩到重复加载的恰恰是照旧教程修这个动作。网上大量教程写着在 php.ini 里加zend_extensionopcache。包括我自己这套手册的早期版本。但在 conf.d 已经加载过 opcache 的镜像里再加一次就是声明两次。本机复现# 同一条 zend_extension 声明两次会怎样php-dzend_extensionphp_opcache.dll-dzend_extensionphp_opcache.dll-mCannot load Zend OPcache - it was already loaded这才是真正的重复加载而且它会打印告警。看到这句就是重复了。看不到就不是。所以在面板勾选即生效的镜像里要调 opcache 参数就只追加opcache.*。不要重复写zend_extension。三条判据各自的下一步php.ini 里zend_extension是注释行。别急着说没加载。跑php --ini看Additional .ini files parsed里有没有它。php -i显示Opcode Caching Disabled。别急着说 FPM 没开。同一份输出里找opcache.enable。要终局结论就用 Web 层探针。php -m里 opcache 出现两行。别急着说重复加载。看 conf.d 里有几个 opcache ini。真重复会打印...already loaded。正确的调参姿势。1Panel 里的位置是运行环境下的 PHP 配置修改页在 php.ini 末尾追加; 注意不要再写 zend_extensionopcache —— conf.d 已经加载过了 opcache.enable1 opcache.memory_consumption192 opcache.interned_strings_buffer16 opcache.max_accelerated_files20000 ; 保持 1/2 最稳妥改完代码约 2 秒自动生效无需重启容器 opcache.validate_timestamps1 opcache.revalidate_freq2 opcache.save_comments1 opcache.fast_shutdown1还有两行值得单说。ThinkPHP 这种框架一次请求要编译上百个 PHP 文件。opcache 的收益来自编译一次、复用多次。而validate_timestamps1加revalidate_freq2意味着一件事。rsync更新代码后最多 2 秒新代码就生效不用重启 PHP 容器。代价是每次请求多一次文件 mtime 检查。换来的是部署流程少一步。对中小流量站点这笔账很划算。若把validate_timestamps改成 0性能最好。但每次rsync之后必须重启 PHP 容器否则旧字节码会一直跑。改之前先想清楚部署流程。三个假象底下是同一件事我们习惯用单点回显下结论。而 PHP 的配置是一个多来源、多 SAPI 的系统。配置来源至少两处。php.ini 加 conf.d 下的那些 ini。SAPI 至少两种。cli 和 fpm-fcgi。docker exec里跑的 php 永远是前者。模块清单其实有两张表。[PHP Modules]和[Zend Modules]。把三条判据串成一条判定路径。遇到 opcache 好像不生效就照这个走有没有OnOff怀疑 opcache 没生效php --ini看 Additional .ini files parsedconf.d 里有opcache 的 ini 吗模块已加载假象一排除确实没加载面板勾选 opcache 后重启同一份 php -i 里opcache.enable 是什么全局已开假象二排除确实没开追加 opcache.enable1Web 层探针SAPI fpm-fcgi缓存是否真正启用 yes结论opcache 正常所以下次在容器里看到异常先问三句。这个结论是哪个来源给的哪个 SAPI 给的哪张表给的我一开始也把 CLI 的Disabled当成 FPM 的结论了。它长得太像没开。