编程 EL9 不再提供 mod_php:Apache 2.4 + PHP 8.4 迁到 php-fpm 的配置与失效点

2026-11-05 00:03:27

EL9 不再提供 mod_php:Apache 2.4 + PHP 8.4 迁到 php-fpm 的配置与失效点

症状

在 EL9(RHEL 9、Rocky 9)上按旧文档安装 mod_php 或 libapache2-mod-php,软件包直接无法匹配。把 EL8 的 vhost 原样搬过来后,可能出现三种现象:

  • Apache 启动日志中出现 PHP Module not compiled to be thread safe,并拒绝加载 mod_php;
  • .php 文件被当作纯文本输出,或直接弹出下载;
  • EL8 上还能安装 mod_php,但必须把 Apache 切换到 prefork 才能启动。

原因

mod_php 是 apache2handler SAPI,它会把 PHP 解释器嵌入每个 httpd 工作进程。除非编译 PHP 时启用 --enable-zts(Zend 线程安全),否则 mod_php 要求 prefork MPM,这会把并发能力限制在进程模型的水平上。发行版打包的 mod_php 通常没有启用 ZTS,所以基本等同于要求 prefork。

Apache 2.4 起,event MPM 已是默认;线程化 MPM 与 mod_php 不兼容,加载时就会报上面那行错。而 PHP 8.4.0 的 apache2handler 至少要求 Apache 2.4,EOL 的 2.0/2.2 已不再支持。

发行版层面更直接:EL8 默认使用 php-fpm,mod_php 仍提供,但需要把 Apache 切到 prefork;EL9 官方仓库不再提供 mod_php;Plesk 也已弃用。php.net 手册写明不要在新安装中使用 mod_php,保留该页仅为历史参考和少数确实需要把 PHP 嵌入 httpd 进程的场景,现代部署用 PHP-FPM + mod_proxy_fcgi。

具体配置

Apache 侧

确认 mod_proxy 与 mod_proxy_fcgi 已加载:

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so

早期只能靠 ProxyPassMatch 按 URL 匹配,不管文件是否存在:

ProxyPassMatch ^/(.*\.php(/.*)?)$ unix:/run/php-fpm/www.sock|fcgi://localhost/var/www/html/$1

这条规则的缺点是:只要 URL 以 .php 结尾,请求就会被转给 FPM,哪怕磁盘上没有这个文件,用户拿到的是 FPM 返回的 File not found。

Apache 2.4.10 起支持把 SetHandler 指向 proxy,配合 `` 只匹配真实存在的文件:


SetHandler "proxy:unix:/run/php-fpm/www.sock|fcgi://localhost"

EL8 自带 httpd 2.4.37、EL9 是 2.4.53,都高于 2.4.10,新部署直接用 SetHandler 写法。

php-fpm pool

/etc/php-fpm.d/www.conf 里至少要定住运行用户、socket 与权限:

user = apache
group = apache
listen = /run/php-fpm/www.sock
listen.owner = apache
listen.group = apache
listen.mode = 0660

php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_value[upload_max_filesize] = 64M
php_admin_value[open_basedir] = /var/www/html:/tmp

失效点排查

按迁移后实际遇到的顺序:

php_value / php_admin_value 不再生效。 写在 vhost 或 .htaccess 里的这类指令没有模块接收,FPM 不是 httpd 模块。挪到 pool 配置(php_admin_value[...] / php_value[...]),或写 .user.ini——后者只对 PHP_INI_PERDIR / PHP_INI_ALL 级别的项有效,PHP_INI_SYSTEM 级别的项改不动。

脚本运行用户变了。 不再统一以 www-data 跑,而是 pool 的 user/group。缓存、上传、session 目录的属主和权限要跟着改。反过来也解决了 mod_php 下的老问题:PHP 生成的文件属主是 www-data,用户删不掉,配额也不算在自己头上。

REDIRECT_ 前缀。 Apache 作为代理转发时会往环境变量加前缀,依赖 getenv('XXX') 的脚本可能读不到值,需要改成 getenv('REDIRECT_XXX'),或直接从 $_SERVER 取。

opcache 变成每池独立。 池一多,内存占用翻倍。opcache.validate_timestamps 与 revalidate_freq 要按部署方式调:开发机打开;生产环境如果是发版即重启 fpm,可以关掉时间戳校验。

502 与 503 要分开看。 502 Bad Gateway 多数是 socket 连不上:路径写错、listen.owner / listen.mode 不对、SELinux 拦住了连接。503 多数是池满:pm.max_children 打满、listen.backlog 溢出,结合 fpm status 和 slowlog 看。

文件系统不共享。 FPM 在另一台机器或容器里时,必须同步或共享代码目录,否则 FPM 报 Primary script unknown。

共享主机上的差异

mod_php 下所有站点都以 www-data 跑,CMS 要写文件就得 666/777,一个用户能读到别人的代码。切到每站点独立 FPM 池、不同 uid 之后,隔离和文件归属一起解决;event MPM 下,静态内容也不会再被卡住的 PHP 子进程拖住。

仍应保留 mod_php 的场景与判据

判据就三条:应用强依赖 .htaccess 里的 php_value / php_admin_value 且改不动;单机、小流量、进程数上不去;嵌入式或无法拆出独立进程的环境。

满足这三条再谈保留,前提是 ZTS 编译 PHP,配 worker 或 event MPM,并把 MaxConnectionsPerChild 设为大于 0 定期回收子进程,同时接受随之而来的内存占用与安全代价:

./configure --enable-zts --with-apxs2=/usr/bin/apxs
make && make install

这个前提做不到,就走 PHP-FPM + mod_proxy_fcgi。

复制全文 生成海报 mod_php Apache PHP-FPM 运维 mod_proxy_fcgi

推荐文章

程序员茄子在线接单