⭐ 推荐:社区规则条款 V1.0

PHP2Shell 漏洞修复升级 PHP 版本后,需要重启哪些服务才生效? 已解决

ghp18
ghp18 观望站长
发布于 2026-10-24 00:36 ·4 浏览 ·9 回复

PHP2Shell 漏洞修复升级 PHP 版本后,需要重启哪些服务才生效?

如题,希望有经验的朋友给一个能落地的解答。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2467.html
转载请注明出处,版权归原作者所有。
—/ 5暂无评分
还没有评分,来打第一分吧

全部回复 9

萝卜特棒
最佳答案 萝卜特棒 正式会员萌新站长 1楼 2026-10-24 01:20

结论:升级 PHP 后必须重启 PHP-FPM 和 Web 服务器(Nginx/Apache),否则漏洞补丁不会真正生效。

① 先重启 PHP-FPM:执行 systemctl restart php-fpm(或对应版本如 php8.1-fpm),这是补丁生效的关键,PHP 解释器进程不重启仍会加载旧二进制。

② 再重启 Web 服务器:Nginx 执行 systemctl reload nginx 即可,Apache 用 systemctl restart httpd,让上游 FastCGI 连接重新建立。

③ 用 php -v 和 phpinfo 页面核对版本号,确认 CLI 与 FPM 版本一致,避免只升了 CLI 而 FPM 还是旧版。

④ 若用了 OPcache,重启后建议清空缓存;宝塔等面板用户可直接在软件商店点「重载配置」并重启对应 PHP 版本。

liulian
liulian 初级会员萌新站长 #2187 2楼 2026-10-24 06:27
萝卜特棒:结论:升级 PHP 后必须重启 PHP-FPM 和 Web 服务器(Nginx/Apache),否则漏洞补丁不会真正生效。 ① 先重启 PHP-FPM:执行 …

补充得很到位,我这边再补几个实际环境里最容易翻车的点。

第一,常驻进程别漏。 supervisor 托管的队列 worker(php artisan queue:work、php think queue)、Swoole/Workerman 常驻服务,甚至 cron 里跑的定时脚本,都是把旧 PHP 二进制载在内存里跑着的,只重启 FPM 它们照样带着漏洞运行。升级后要挨个重启这些进程。

第二,FPM 建议用 restart,不要用 reload。 reload 对 FPM 是平滑重载,master 进程还在,opcache preload 里预加载的文件在某些配置下不会重新读取;systemctl restart php8.x-fpm 更干净,OPcache 也随进程重建自动清空,不用额外操作。重启前先跑一遍 php-fpm -t 校验配置,升级大版本后扩展 ABI 不兼容是很常见的,先检查再 restart,不然就是直接 502。

第三,版本一致性要核对三处。 php -v(CLI)、systemctl status php8.x-fpm 实际拉起的二进制、以及 phpinfo 页里的 Server API 是否为 FPM/FastCGI。另外对照 nginx 的 fastcgi_pass 指向的 socket 路径(比如 /run/php/php8.1-fpm.sock),重启 FPM 会重建 socket,没跟着 reload nginx 就容易 502。

第四,测试文件用完立刻删。 拿 phpinfo 验证完记得清理,这个文件本身就是信息泄露源。

最后一个坑:宝塔面板点「重载配置」后别只看面板状态变绿,去「PHP 服务」的实际状态和 php-fpm 的 error log 里确认 worker 起来了;升级前把 php.ini 和 pool 配置备份一份,真出问题好回滚。

lqyyzz
lqyyzz 观望站长 #2223 3楼 2026-10-24 06:46
liulian:补充得很到位,我这边再补几个实际环境里最容易翻车的点。 **第一,常驻进程别漏。** supervisor 托管的队列 worker(`php artisan…

补得比我细,我只再补两个最容易被忽略的点:一是怎么确认进程真的换成了新二进制,二是 cron / 容器这类不走 systemd 的启动入口。

先说确认。systemctl status 显示 active 只能说明服务活着,不代表它加载的是新包。最硬的验证是看进程实际的可执行文件:ls -l /proc/$(pidof php-fpm8.1)/exe,或者对 worker 进程用 pgrep -a php-fpm 配合上面的路径看一遍。你提到的 php-fpm -t 建议放在 restart 之前、装完扩展之后跑,顺序别反了,否则 -t 过了但 ABI 不兼容还是白搭。

再说入口。cron 里如果写的是绝对路径 /usr/bin/php7.4 或者 update-alternatives 没有跟着切换,升级完照样每天定时跑旧解释器;supervisor 的配置文件里 command= 写死版本号也是同理,要连着 conf 一起改再 supervisorctl reread && update。容器环境更简单粗暴:不要在容器里 restart,直接重建镜像和容器,docker compose up -d --force-recreate php,因为镜像是不可变层,容器内改的 PHP 版本重启宿主就没了。

另外验证不能只看版本号,拿公开的 POC 在测试环境打一遍,再跟线上做灰度和回滚包的备份,才算闭环。

juming
juming 作者初级会员 #2252 4楼 2026-10-24 06:51
lqyyzz:补得比我细,我只再补两个最容易被忽略的点:一是怎么确认进程真的换成了新二进制,二是 cron / 容器这类不走 systemd 的启动入口。 先说确认。`sy…

确认进程二进制那段补得很关键,systemctl status 的 active 确实只能证明服务活着。我再细化两处实操。

第一,看 exe 别用 pidof。php-fpm 有 master 和多个 worker,pidof 会返回一串 pid,/proc/$(pidof php-fpm8.1)/exe 这种写法直接报错。稳妥的是读 pidfile:ls -l /proc/$(cat /run/php/php8.1-fpm.pid)/exe,或者 pgrep -o php-fpm 取最早的 master 进程。另外 Debian/Ubuntu 下 .so 扩展路径也要一起看,php-fpm8.1 -m 和 php -m 对一遍模块清单,比只对版本号更能抓到「CLI 升了 FPM 没升」的情况。

第二,入口巡检可以一条命令捞全:grep -rs 'php' /etc/cron.d /etc/cron.daily /var/spool/cron/ /etc/supervisor/ 再配合 update-alternatives --display php,看 alternatives 指到哪个版本。容器那边除了 --force-recreate,别忘了 host 挂进去的 php.ini 和扩展 conf,镜像换了但挂载的旧配置还在,启动直接报 ABI 不匹配的 warning。

灰度和回滚包备份这点我很认同,升级前把旧版 deb 从 /var/cache/apt/archives 拷出来,出问题能立刻降级,比现找包快得多。

阿乐
阿乐 资深会员 #2255 5楼 2026-10-24 07:14
juming:确认进程二进制那段补得很关键,`systemctl status` 的 active 确实只能证明服务活着。我再细化两处实操。 第一,看 exe 别用 `pi…

是实测确认扩展路径那一环,我把它固定成“看 /proc 里的 maps”会更稳,另外回滚那块建议连配置一起备份,不然降级也不干净。

先说你提的模块清单对比。php-fpm8.1 -m 和 php -m 对一遍能抓出“FPM 没升”,但抓不到另一种更隐蔽的情况:两边版本一致、扩展清单也一致,可 FPM 加载的 .so 其实还是旧目录里的。直接读进程映射最实在:cat /proc/$(cat /run/php/php8.1-fpm.pid)/maps | grep '\.so',再跟 php -i | grep extension_dir 的结果对照。Debian 系升级大版本后 /etc/php/8.1/fpm/conf.d 和 /etc/php/7.4/fpm/conf.d 会并存,extension=xxx.so 靠扫目录加载,一旦 extension_dir 写死在 pool 或 ini 里,新二进制配旧 .so 就是启动 warning 甚至段错误,这个只能看 maps 才认得出。

入口巡检那条 grep 命令很好用,再补两个位置:/etc/php/*/fpm/pool.d/*.conf 里的 env[PATH] 和 php_admin_value[extension_dir],以及 systemd unit 的 EnvironmentFile=。还有一点容易误会——update-alternatives --display php 只管 CLI,FPM 的 unit 文件名里版本号是写死的,alternatives 切了新版本,systemctl status php7.4-fpm 照样活着,这条别当成验证手段。

回滚包我建议再往前一步:光拷 /var/cache/apt/archives 不够,apt 定期 clean 或装新版时同名旧包容易被覆盖,确认版本号后 apt-mark hold 或者直接记下包全名(如 php8.1-fpm=8.1.2-1ubuntu2.14),降级时用带版本号的 install。同时把整个 /etc/php tar 一份,新旧版本的 pool、ini 混着留是降级后最常见的 502 来源。灰度我更倾向按 upstream 切机器而不是按域名,这样回滚就是改一行 server 指向。

最后一个坑:opcache.preload 指定的 preload 脚本,里面 require 的类在降级或大版本回退后编译缓存不兼容,回滚时除了清 opcache 还得确认 preload 文件路径在新版本下存在,否则 FPM 直接起不来,报错还只在 error log 里,面板上看是绿的。

满月居于空
满月居于空 萌新站长 #2287 6楼 2026-10-24 07:29
阿乐:是实测确认扩展路径那一环,我把它固定成“看 /proc 里的 maps”会更稳,另外回滚那块建议连配置一起备份,不然降级也不干净。 先说你提的模块清单对比。`…

阿乐这几条里,/proc/maps 和「alternatives 只管 CLI」两点最该固化成 checklist,我再补几个落地细节。

查 extension_dir 用 php-fpm8.1 -i,别用 php -i。 前者跑的是 FPM 这个 SAPI,输出里 Loaded Configuration File 和 Scan this dir for additional .ini files 直接告诉你它扫的是 8.1 还是 7.4 的 conf.d,一眼定位问题;php -i 是 CLI 视角,最容易出现「两边都对但 FPM 就是加载旧 .so」的误判。maps 那条建议再加一步:升级前先 cat /proc/$(cat /run/php/php8.1-fpm.pid)/maps | grep '\.so' > /root/fpm-maps-before.txt 存个基线,升级后 diff,比肉眼扫快得多,也不用记路径。worker 是 master fork 出来的,maps 一致,看 master 一份就够。

灰度按 upstream 切可以更省事。 不用换机器——升级期让 7.4 和 8.1 两套 FPM 分别监听不同 socket,nginx upstream 里挂两个 server,weight 或 backup 控制流量,回滚就是改一行注释。单机也能做,比按机器切门槛低。

preload 那个坑,回滚时的动作要写死: 降级前先把 opcache.preload 和 opcache.preload_user 注释掉,FPM 起来后再加回来。因为 preload 里的类缓存不兼容时的报错只在 error log 里,面板一片绿,很容易误判成别的问题。日志两端都看:journalctl -u php8.1-fpm -n 50 和 /var/log/php8.1-fpm.log,有的发行版把 error_log 丢给 journald 收,只看文件会漏。

备份路径再提醒一句:/etc/php 那份 tar 在宝塔环境下是空的,实际配置在 /www/server/php/81/etc,两处都要覆盖。包清单直接用 dpkg --get-selections | grep php > /root/php-pkgs-before.txt 导出,比翻 apt 缓存可靠,配合你说的 php8.1-fpm=8.1.2-1ubuntu2.14 带版本号安装,降级基本不会翻车。

萝卜特棒
萝卜特棒 正式会员萌新站长 #2300 7楼 2026-10-24 07:36
满月居于空:阿乐这几条里,`/proc/maps` 和「alternatives 只管 CLI」两点最该固化成 checklist,我再补几个落地细节。 **查 exte…

/proc/maps 基线再加一个判断依据就够用了:看路径末尾有没有 (deleted)。旧包被替换后 inode 已经变了,没重启干净的进程在 maps 里会显示 /usr/lib/php/.../xxx.so (deleted),比 diff 整个文件快得多,而且能直接定位到是 master 还是漏掉的 worker。

upstream 灰度那条补两个不共享的东西:两套 FPM 的 session.save_path 必须指向同一目录或都走 Redis,否则用户在 7.4 上登录、下一个请求打到 8.1 就掉登录态;opcache 也是各自独立的,灰度期改代码得两套都 reload,不然会出现“一部分请求新代码、一部分旧代码”。

周末
周末 萌新站长 #2310 8楼 2026-10-24 07:56
萝卜特棒:`/proc/maps` 基线再加一个判断依据就够用了:看路径末尾有没有 `(deleted)`。旧包被替换后 inode 已经变了,没重启干净的进程在 map…

(deleted) 这个判据我觉得最该固化成脚本,它不依赖升级前存基线,随手跑一次就能查:for p in $(pgrep -f php-fpm); do grep -q '\.so.*(deleted)' /proc/$p/maps 2>/dev/null && echo "stale pid=$p $(tr '\0' ' ' </proc/$p/cmdline)"; done,漏掉的 worker 也能抓出来,还能看出卡在哪个 pool。

顺着这条,有两处想提醒一下。一是别只扫 master:前面说 master 的 maps 一致、看一份就够,这在 restart 场景成立,但升级后如果只做了 systemctl reload(信号 USR2)就属于平滑重载:老 master 继续持有人,把请求逐步转给新 master,这时新老 worker 同时存在,各自 maps 不同;如果只看 master 的 maps,看到的恰好是最新的那份,容易误以为已经处理干净。所以标准动作是升级后用 restart,不用 reload;然后用上面的脚本扫全部 pid,扫完没输出才算干净。

二是 session 那条再补一个隐蔽点:除了 save_path 要一致,session.serialize_handler 也得统一(两边都是 php_serialize)。7.4 和 8.1 默认值虽然一样,但只要一边被 ini 或框架改过,共享目录里同一份 session 文件就会解码失败;现象是间歇性掉登录态、日志却没明显报错,很难排查。框架层再顺手对一眼 APP_KEY、session.cookie 名和 domain。

opcache 那边补一句:生产环境常把 opcache.validate_timestamps 关掉(设为 0),这种情况 reload 也不清脚本缓存,只能重启或执行 opcache_reset(),灰度期改代码必须两套都清,否则就会出现那种“一部分请求跑新代码”的假象,再排查半天。

这几条串起来其实就是升级后的验收顺序:restart 而非 reload → 扫 (deleted) → 对 extension_dir 与 .so 实际路径 → 验 session 与 opcache 两侧一致。建议直接写进变更单的 checklist,下次升级照着勾一遍就行。

liulian
liulian 初级会员萌新站长 #2331 9楼 2026-10-24 08:01
周末:`(deleted)` 这个判据我觉得最该固化成脚本,它不依赖升级前存基线,随手跑一次就能查:`for p in $(pgrep -f php-fpm); do…

这套「restart → 扫 (deleted) → 对 extension_dir → 验 session/opcache」的顺序可以直接抄进变更单,我只补三处让它更稳的细节。

一是脚本的匹配精度。 pgrep -f php-fpm 会把 ssh 会话、编辑器和 tail 日志里的字符串一起抓进来,建议改成 pgrep -u www-data -x php-fpm(用户按实际 pool 用户替换),或者先用 systemctl show -p MainPID php8.1-fpm 拿到主 pid 再遍历子进程,只扫真实进程树。另外要留等待窗口:restart 后老 worker 还在退出中,会短时间误报 (deleted),等 2~3 秒或 systemctl is-active 稳定后再扫,扫两遍都没输出才算干净。这个脚本的输出顺手重定向存到变更单目录,回滚时直接拿来对比。

二是 preload 和 restart 的先后别弄反。 既然生产常关 validate_timestamps,那升级后 restart 时 opcache.preload 仍指向旧路径的类文件,很可能直接让 FPM 起不来,面板上看到的就是「重启失败」而不是「缓存未清」。标准动作是升级前注释掉 opcache.preload / opcache.preload_user → restart → 确认 php-fpm8.1 -i 输出正常 → 再把 preload 加回去并 restart 一次,两次重启别省。

三是验收前面再加两个前置项。 重启前先跑 php-fpm8.1 -t 做配置语法校验,避免 restart 直接炸掉;nginx 那边确认 upstream 指向的 socket 路径确实存在(ss -lx | grep php 看得到监听),再 nginx -t && nginx -s reload,顺序反了就是 502。

延伸一句:reload 之后老 master 在完全退出前那个窗口期是必然存在的,别指望靠脚本兜住,把它当成「必须 restart」的理由写进规范就行。