最近在给一台运行了好几年的服务器做环境整理,上面的老项目 PhpAsk 一直跑在 PHP 5.6 上,稳定是稳定,但新接的几个接口需求必须依赖 PHP 8.x 的语法和扩展库,不能直接升级,因为整个系统里有一堆历史代码用了老旧函数,强行升上去大概率直接白屏。于是“一台机器上多版本 PHP 共存”就成了绕不开的需求。这篇文章就是把我实际配置 PhpAsk 多版本 PHP 环境的过程完整梳理出来,包含具体操作步骤、踩过的坑和排查思路,给同样被“新项目要求新版本、老项目不敢动”困扰的朋友做个参考。
先说清楚要解决什么问题:服务器上已经有一套完整的 LAMP/LNMP 环境,PhpAsk 是通过 Nginx + PHP-FPM 对外提供服务,现在需要在不破坏现有 PHP 5.6 环境的前提下,再装一套 PHP 8.2,并且让不同站点/不同路径按需使用不同版本,最后还要处理 CLI 环境下默认 php 命令怎么切换、Composer 依赖怎么按版本执行这些细节。
1. 项目需求拆解:为什么要做多版本共存
1.1 PhpAsk 项目的实际背景
PhpAsk 本身是一套基于 PHP 的问答/工单流转系统,代码量不算特别大,但历史包袱很典型:早期版本大量使用mysql_connect这类早已移除的函数,还有一些第三方扩展只兼容到 PHP 7.2。现在业务上要新增一个模块,需要用到match表达式、str_contains、枚举类型这些 PHP 8 才有的特性,如果直接在原环境里升级,整个 PhpAsk 核心模块就会崩溃,风险不可控。
这个现象在真实运维里非常常见:老业务和老系统跑到地老天荒,新业务又等不了。所以多版本共存不是理论上的最优解,而是实际运维里最稳妥、最平滑的过渡方案。
1.2 多版本共存的三种常见方案
我在动手之前先梳理了当前主流的多版本共存思路,对比之后才做的决定:
| 方案 | 实现方式 | 优势 | 劣势 |
|---|---|---|---|
| Docker 容器隔离 | 每个 PHP 版本单独跑一个容器 | 隔离干净、互不影响、迁移方便 | 对已有物理环境部署的项目改造量较大,需要重构目录和网络 |
| PHP-FPM 多实例 | 同一台机器编译/安装多个 PHP,各自启动独立的 FPM 进程 | 改动最小、最贴近现有环境、Nginx 切换极快 | 需要人工管理多个 FPM 服务,对新手稍难 |
| 虚拟机或独立服务器 | 一套环境一台机器 | 彻底隔离 | 成本高、维护成本高、对小项目太浪费 |
最终选择的是第二套方案:在一台服务器上通过源码编译方式安装多个 PHP 版本,每个版本对应一个独立的 PHP-FPM 实例,Nginx 层面按站点配置不同的fastcgi_pass地址,实现细粒度切换。这也是目前生产环境里最常见、最可控的做法,适合 PhpAsk 这种基于标准 Web 服务架构的项目。
2. 环境摸底与版本规划
2.1 先搞清楚当前环境底细
动手之前绝对不要直接开装,先把现状摸清楚。我一般在服务器上依次执行这几个命令,把底子看明白:
# 查看操作系统发行版 cat /etc/os-release # 查看当前 PHP 版本和具体安装路径 php -v which php # 查看 PHP-FPM 进程状态 ps -ef | grep php-fpm # 查看已编译的 PHP 扩展 php -m # 查看 Nginx 里 PhpAsk 站点的配置 nginx -T | grep -A 5 "fastcgi_pass"我这次遇到的服务器是 Ubuntu 20.04,原有的 PHP 是通过apt安装的 5.6 版本,由 Ondřej 的 PPA 源提供,PHP-FPM 的监听方式是 Unix Socket,位于/run/php/php5.6-fpm.sock。Nginx 配置文件里有一段:
location ~ \.php$ { fastcgi_pass unix:/run/php/php5.6-fpm.sock; include fastcgi_params; }这个信息非常关键,因为后续装好新版本后,只需要把这段fastcgi_pass替换成新的 socket 路径或端口即可,不用动 Nginx 的其他逻辑。
2.2 PHP 版本选型与规划建议
多版本并存不是装得越多越好,够用就行。我在装之前做了版本规划,核心原则是“新版本选当前主流稳定版,老版本保持原样不动”:
- 老版本 PHP 5.6:继续作为 PhpAsk 默认运行版,不做任何改动,避免引入回归风险;
- 新版本 PHP 8.2:给新增模块和新站点使用,这也是目前稳定性较好的版本;
- 额外预留 PHP 7.4:用于某些依赖旧版语法但需要迁移的中间场景,方便后面过渡。
规划好之后,给每个 PHP 版本分配独立的安装目录、独立配置文件、独立日志目录,避免互相污染。这一步看起来繁琐,但后期排查问题时会省非常多的时间。
3. 多版本 PHP 的安装落地
3.1 编译安装前的基础依赖准备
我采用的是源码编译方式安装 PHP 8.2,因为这样自由度最高,可以把依赖库和扩展路径完全独立出来,不会干扰系统原有的 PHP 5.6。编译前先安装基础编译工具和依赖库:
apt update apt install -y build-essential autoconf libxml2-dev libsqlite3-dev \ libcurl4-openssl-dev libssl-dev libonig-dev libzip-dev \ libpng-dev libjpeg-dev libfreetype6-dev libreadline-dev \ libpq-dev pkg-config re2c bison这些依赖包里,有几个特别关键:
libonig-dev:PHP 的多字节正则依赖mbstring需要它,缺了编译时直接报错;libzip-dev:PHP 8.x 的ZipArchive扩展依赖,少装的话编译到一半就断掉;re2c和bison:PHP 源码自带的语法解析器生成工具,版本太老会编译失败。
依赖缺失是编译 PHP 最常见的坑,所以我会习惯性地把常见依赖一次性装齐,避免反复补装浪费时间。
3.2 下载 PHP 源码并创建独立目录
选定版本后,从官网下载源码包。这里建议不要用wget直接拉最新开发分支,而是去官方归档页挑一个正式发行版,例如 8.2.12。
cd /usr/local/src wget https://www.php.net/distributions/php-8.2.12.tar.gz tar xzf php-8.2.12.tar.gz cd php-8.2.12为了不影响系统已有的 PHP 环境,新版本安装路径统一放到/usr/local/php82下。这样整个 PHP 8.2 及所有扩展都隔离在这个目录里,和旧版本的/usr/bin/php、/etc/php/5.6路径完全分开。
3.3 配置编译参数
这是整个安装过程里最关键的一步。./configure的参数直接决定了 PHP 支持哪些功能、扩展怎么加载、FPM 是否启用。我使用的配置参数如下:
./configure --prefix=/usr/local/php82 \ --with-config-file-path=/usr/local/php82/etc \ --with-config-file-scan-dir=/usr/local/php82/etc/conf.d \ --enable-fpm \ --with-fpm-user=www-data \ --with-fpm-group=www-data \ --enable-mbstring \ --enable-zip \ --enable-bcmath \ --enable-pcntl \ --enable-posix \ --enable-opcache \ --enable-sockets \ --with-mysqli=mysqlnd \ --with-pdo-mysql=mysqlnd \ --with-curl \ --with-openssl \ --with-zlib \ --with-readline \ --with-libxml \ --with-freetype \ --with-jpeg重点说明几个参数的作用:
--prefix=/usr/local/php82:指定安装根目录,这是多版本共存的核心,所有二进制文件、库文件、扩展都会装在这里,不会覆盖原系统。--with-config-file-path和--with-config-file-scan-dir:指定 PHP 主配置文件和扩展配置目录,这样每个版本都拥有独立的php.ini和conf.d目录,修改其中一个版本不会影响另一个版本。--enable-fpm:必须开启,否则后面无法启动独立的 PHP-FPM 进程。--with-mysqli=mysqlnd和--with-pdo-mysql=mysqlnd:使用官方推荐的 mysqlnd 驱动,PhpAsk 连接 MySQL 靠的就是 PDO,这个不装的话站点直接连不上数据库。--with-fpm-user=www-data和--with-fpm-group=www-data:让新的 FPM 进程继承 Nginx 的用户权限,避免文件读写权限问题。
编译参数里容易踩的坑是扩展依赖不全。比如--with-freetype和--with-jpeg需要系统里有对应的开发头文件,否则 configure 阶段会报“libpng not found”或“freetype.h not found”这类错误。好在前面基础依赖基本覆盖了。
3.4 编译安装与初始化配置
确定 configure 过程中没有 warning 或 error 之后,执行编译和安装。这一步耗时比较长,通常在几分钟到十几分钟不等,取决于服务器 CPU 性能。
make -j$(nproc) make install编译完成后,PHP 8.2 的二进制文件就出现在/usr/local/php82/bin/php,FPM 会安装在/usr/local/php82/sbin/php-fpm。
接下来需要手动初始化配置文件。源码包里有现成的参考配置,直接复制过来:
cp php.ini-production /usr/local/php82/etc/php.ini cp /usr/local/php82/etc/php-fpm.conf.default /usr/local/php82/etc/php-fpm.conf cp /usr/local/php82/etc/php-fpm.d/www.conf.default /usr/local/php82/etc/php-fpm.d/www.conf这里我建议把php.ini-production作为配置文件,因为它默认开启了display_errors=Off等生产环境更安全的选项。如果你还需要启用某些扩展,比如opcache,编辑php.ini取消对应行的注释即可。
3.5 用 update-alternatives 管理 CLI 版本
安装好多个 PHP 版本后,命令行下默认php指向哪个版本需要明确管理。很多新手会直接改 PATH 环境变量,但这样会造成混乱,尤其当你手动执行php命令时,根本不确定用的是哪个版本。
Ubuntu/Debian 系统里推荐用update-alternatives管理:
update-alternatives --install /usr/bin/php php /usr/bin/php5.6 56 update-alternatives --install /usr/bin/php php /usr/local/php82/bin/php 82设置默认版本并切换:
update-alternatives --config php执行后会列出所有已注册的 PHP 版本,输入数字即可切换。我实际测试中发现,如果以前是用apt安装的 PHP 5.6,它已经是一个update-alternatives条目,把新版本注册进去后,切换非常丝滑。但有一点要特别注意:phpize、php-config这两个命令也需要同步管理,否则后面编译 PECL 扩展时会选错目标版本。
update-alternatives --install /usr/bin/phpize phpize /usr/local/php82/bin/phpize 82 update-alternatives --install /usr/bin/php-config php-config /usr/local/php82/bin/php-config 823.6 另一个选择:用软件源直接安装多版本
除了源码编译,还有一条相对省心的路:如果你用的系统是 Ubuntu,可以通过第三方 PPA 源直接安装多个 PHP 版本。我之前在另一台测试机上用这种方法装过 PHP 7.4、8.0、8.2 共存,体验也不错。方法如下:
add-apt-repository ppa:ondrej/php apt update apt install php8.2-fpm php8.2-mysql php8.2-mbstring php8.2-xml这种方式装出来的版本会放在/etc/php/8.2目录下,而且会自动配置好对应版本的 FPM 服务,比如php8.2-fpm,管理起来特别方便。但它的局限也很明显:PPA 源里的版本更新策略不完全可控,某些服务器系统或内网环境无法访问外部源时,这套方案就抓瞎了。
所以我的建议是:如果你能接受外部源且对版本路径不敏感,优先用软件源方案,省心;如果你要精准控制版本、目录和扩展组合,或者需要在离网环境安装,源码编译是唯一可靠的路。
4. PHP-FPM 服务管理与端口规划
4.1 为每个版本创建独立的 FPM 服务
多版本 PHP 共存的第二个关键环节是让每个版本都有自己独立的 PHP-FPM 服务。这样 Nginx 可以按站点配置将请求交给不同版本的 FPM 进程处理,进程之间互不影响。
源码编译安装的 PHP 不会自动注册系统服务,需要手动创建 systemd 服务文件。我在/etc/systemd/system/php82-fpm.service里创建了如下内容:
[Unit] Description=PHP 8.2 FastCGI Process Manager After=network.target [Service] Type=simple ExecStart=/usr/local/php82/sbin/php-fpm --nodaemonize --fpm-config /usr/local/php82/etc/php-fpm.conf ExecReload=/bin/kill -USR2 $MAINPID PrivateTmp=true [Install] WantedBy=multi-user.target关键点在于ExecStart里通过--fpm-config指定使用本版本的独立 FPM 配置文件,避免与系统旧版本的配置文件混淆。
执行如下命令启动并设置开机自启:
systemctl daemon-reload systemctl start php82-fpm systemctl enable php82-fpm启动后可以这样验证进程是否正常:
ps -ef | grep php-fpm正常会看到多个 Master 和 Worker 进程,并且 Master 进程的命令行里带上了/usr/local/php82/路径,和旧版本的进程区分很明显。如果启动失败,通常要看 FPM 主配置文件里的日志路径是否正确,以及监听地址是否被占用。
4.2 端口 vs Unix Socket:怎么选
FPM 的监听方式有两种:TCP 端口和 Unix Socket。我这次给两个版本分别采用了不同方案:
- 旧版本 PHP 5.6 保持原来的 Unix Socket 监听:
unix:/run/php/php5.6-fpm.sock - 新版本 PHP 8.2 使用 TCP 端口监听:
127.0.0.1:9002
为什么新版本不用 Socket?因为 TCP 端口在做版本切换、压力排查时有三个明显优势:
- Nginx 配置里直接填
127.0.0.1:9002,调试时可以用curl或telnet直接探活; - 多个 PHP 版本如果都用 Socket,放在不同目录里容易搞混,端口则一眼就能看出对应关系;
- 后面如果要接负载均衡或把 FPM 服务挪到其他机器,端口方案迁移成本更低。
需要修改 PHP 8.2 的 FPM 配置文件,打开/usr/local/php82/etc/php-fpm.d/www.conf,找到 listen 配置项并修改:
listen = 127.0.0.1:9002同时建议把listen.allowed_clients保留默认的 127.0.0.1,避免暴露到公网。这个文件里还有一个非常重要的参数user和group,确认它们和 Nginx 的 worker 进程用户一致,一般是www-data,否则会出现文件写入权限问题。
4.3 Nginx 配置切换的核心逻辑
Nginx 是连接外部请求和 PHP-FPM 的桥梁。要让 PhpAsk 老项目继续用 PHP 5.6,同时新增模块用 PHP 8.2,根本方法就是在 Nginx 的 server 配置或 location 配置里分别指定不同的fastcgi_pass。
PhpAsk 现在的域名配置是独立 server block,我不想按照全站切换,而是希望更精细地按目录切换。我先看一下当前 PhpAsk 的 Nginx 配置关键段:
server { listen 80; server_name ask.example.com; root /var/www/phpask/public; index index.php; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass unix:/run/php/php5.6-fpm.sock; } }为了让 PhpAsk 的新模块newapi使用 PHP 8.2,我加了一个精确的 location 规则:
location ^~ /newapi/ { alias /var/www/phpask/newapi/; location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $request_filename; fastcgi_pass 127.0.0.1:9002; } }修改配置后,一定要先检查 Nginx 配置语法:
nginx -t确认无误后平滑重启:
systemctl reload nginx这样同一条 URL 下,/newapi/路径走 PHP 8.2,其他路径继续走 PHP 5.6,切换非常干净。
如果希望整个站点直接切到新版本,只需要把第一个 location 中的fastcgi_pass unix:/run/php/php5.6-fpm.sock改为fastcgi_pass 127.0.0.1:9002,然后重启,但这一步要谨慎,确保 PhpAsk 的代码能在 PHP 8.2 下跑起来,否则不要轻易全站切换。
5. PhpAsk 项目的版本切换与验证
5.1 在新版本下安装运行 PhpAsk 的依赖
既然新模块要在 PHP 8.2 环境下运行,那么 PhpAsk 项目本身也要能兼容新版本,这里有一个非常重要的操作:Composer 依赖必须在对应 PHP 版本下重新安装。
很多人在多版本共存环境里栽跟头,就是因为直接执行:
composer install这个命令用的还是默认 PHP 版本,导致项目依赖扩展和当前运行环境不一致。正确的做法是使用对应版本的php可执行文件来运行 Composer:
/usr/local/php82/bin/php /usr/local/bin/composer install或者用php8.2 $(which composer)的方式执行。由于我前期已经把 PHP 8.2 注册到了update-alternatives,也可以用:
update-alternatives --config php临时切到 PHP 8.2 后再执行 Composer。但要注意:切换默认 CLI 版本只是影响当前终端会话里的php命令,Nginx 里的 FPM 进程不受影响,所以这种做法是安全的。
5.2 用探针脚本验证切换是否生效
配置完成后,验证是必不可少的环节。我在 PhpAsk 的/var/www/phpask/newapi/目录下放了一个临时 PHP 探针文件phpinfo.php:
<?php phpinfo();然后用浏览器访问http://ask.example.com/newapi/phpinfo.php,看到页面显示 PHP Version 8.2.x 就说明 Nginx 确实把请求转发到了新版本的 FPM。同时还能看到Configure Command、扩展加载情况、Loaded Configuration File路径等关键信息,这些信息对排查扩展缺失、配置不生效非常有帮助。
验证完必须要做的动作是:立即删除这个探针文件,防止 phpinfo 页面暴露服务器敏感信息。这是生产环境的基本习惯。
5.3 在 PhpAsk 代码中检测当前的 PHP 版本
实际开发中,我们经常需要在代码层面对版本做判断。PhpAsk 作为一个老项目,我建议在入口文件或者公共函数里加一段兼容性检查,防止线上环境误切换:
if (PHP_MAJOR_VERSION >= 8) { // 新逻辑 } else { // 旧逻辑兼容 }这里顺带提一下 PHP 8.x 版本的破坏性变更对 PhpAsk 的影响,主要表现在三个方面:
- 移除
mysql_*系列函数:老代码如果还在用mysql_connect,PHP 8 下会直接报 fatal error,需要改写为 PDO 或 mysqli; - 动态属性废弃:PHP 8.2 对动态属性的使用会触发弃用警告,PhpAsk 里如果存在这类代码,需要加
#[AllowDynamicProperties]或改用显式属性声明; each()函数移除:老项目里常见,需要替换成foreach。
这些变更一定要在新版本真正接入前用静态扫描工具过一遍,比如使用 Rector 或 phpcs 配合 PHPCompatibility 规则集,把不兼容问题提前暴露出来。
6. 踩坑实录与排查技巧
6.1 编译 PHP 的时候报“configure: error: Package requirements (libxml-2.0 >= 2.7.8) were not met”
这个错误我几乎每次编译新版 PHP 都会遇到,尤其是装完系统后直接编译。原因是缺少 libxml2 的开发头文件。解决方法是:
apt install -y libxml2-dev装完重新执行./configure即可。类似的情况还包括缺libcurl、libssl、libonig等,所以第一步基础依赖安装一定要做完整,不要偷懒。
6.2 FPM 启动失败:listen 端口被占用
MySQL、Redis 等常见服务默认监听 9000 端口,而 PHP-FPM 默认监听 9000,极易冲突。我的 PHP 8.2 故意选了 9002 就是为了避开。如果启动时报:“Address already in use”,先用命令查看端口占用:
netstat -tlnp | grep 9002然后把 FPM 配置里的listen改成其他端口即可。这种问题在配置多个 FPM 实例时非常典型,所以我强烈建议每个 PHP 版本使用不同的端口号,并做成表格记录在案。
6.3 Nginx 返回 502 Bad Gateway 却看不到 FPM 错误
502 最常见的三个原因:
- FPM 进程没启动或已崩溃;
- FPM 监听的端口或 socket 路径与 Nginx 的
fastcgi_pass不一致; - FPM 配置文件里有语法错误,导致进程启动后立即退出。
排查思路就是看两个日志文件:Nginx 错误日志和 PHP-FPM 的 error log。我一般这样做:
tail -f /var/log/nginx/error.log tail -f /usr/local/php82/var/log/php-fpm.log如果 FPM 日志显示“pool seems busy”或者“max_children reached”,说明并发量超过了 FPM 的pm.max_children设置,需要适当调高这个值,同时关注服务器内存余量。
6.4 多版本 PHP 扩展安装的坑:pecl 装错版本
这是最隐蔽的问题。多版本共存时,系统的pecl命令默认只会为“当前默认 PHP 版本”安装扩展。比如我用pecl install redis,最后生成的redis.so会被复制到默认版本的扩展目录里,但php.ini里没有自动加载,于是你满世界找扩展在哪里。
解决办法有两种,我推荐先显式指定版本:
/usr/local/php82/bin/pecl install redis然后再在/usr/local/php82/etc/php.ini或/usr/local/php82/etc/conf.d/redis.ini中添加:
extension=redis.so最后重启 PHP 8.2 的 FPM 服务:
systemctl restart php82-fpm然后通过php -m验证扩展加载情况:
/usr/local/php82/bin/php -m | grep redis6.5 环境变量污染:phpize、php-config 指错版本
编译第三方扩展时,系统会调用phpize来准备编译环境。如果这个命令指向了错误的 PHP 版本,后续所有编译都会错乱。我在多版本环境里最谨慎的操作就是先用以下命令确认:
which phpize phpize --version如果发现 phpize 指向旧版本,可以用前面提到的update-alternatives重新切换,或者直接export PATH=/usr/local/php82/bin:$PATH后重新执行编译,但要注意这只会影响当前终端。
6.6 常见问题速查表
| 问题 | 常见原因 | 快速排查方法 | 解决方式 |
|---|---|---|---|
| 访问网站继续显示旧版本 | Nginx 的 fastcgi_pass 没有改 | 查看站点配置和实际请求路径 | 修改 Nginx 配置并 reload |
| 编译报 configure error | 缺少依赖开发包 | 查看报错中缺失的库名 | apt 安装对应 -dev 包 |
| FPM 启动即退出 | 配置语法错误 / 端口占用 | journalctl -u php82-fpm 或查看 FPM 日志 | 修正配置或用其他端口 |
| php 命令版本不对 | 未配置 update-alternatives | php -v | 用 update-alternatives 切换 |
| 扩展加载不上 | php.ini 配置缺失或扩展目录不对 | php -i 查看 extension_dir | 将扩展拷贝到对应目录并加载 |
| Composer 依赖报错 | 使用默认 PHP 版本安装依赖 | which php && php -v | 用目标版本 PHP 执行 Composer |
6.7 关于性能与资源占用
多版本 PHP 共存并不是免费的午餐。每启动一个 PHP-FPM 实例,就会常驻一组 Master/Worker 进程,占用一定内存。以 PHP 8.2 配置 4 个 worker 为例,每个 worker 大约占 30~80MB 内存,总共 200~300MB。如果你同时跑 5 个 PHP 版本,内存占用就要认真评估了。
优化建议是:不太活跃的项目可以把 FPM 的进程管理策略设为ondemand,只在实际有请求时才启动 worker:
pm = ondemand pm.max_children = 20 pm.process_idle_timeout = 10s把pm从默认的dynamic改成ondemand,在低并发场景下能明显降低常驻内存。但也别在高并发场景下用这个,因为 worker 频繁启动会带来额外的 CPU 开销。
7. 运维视角的几点补充分享
最后再补一点实操中我觉得非常有价值的小技巧,这些通常不在常规教程里写,但确实能让你少踩很多坑。
第一个是目录规划和命名习惯。我强烈建议为每个 PHP 版本建一个“版本说明文件”,比如写在/usr/local/php82/README.txt里,记录安装日期、编译参数、启用的扩展、FPM 端口、关联的 Nginx 站点。服务器上同时存在多个 PHP 版本时,时间一长你根本不可能靠记忆分清谁是干什么的,一份说明文件比什么都可靠。
第二个是配置文件的版本管控。PHP 的php.ini、FPM 的www.conf这种关键配置文件,我习惯用 Git 仓库统一管理,服务器上的实际路径通过软链接指向仓库内的文件。每次修改配置都有变更记录,出了问题可以回溯。生产环境里这种习惯可以救命。
第三个是平滑过渡的思路。如果 PhpAsk 老项目要整体从 PHP 5.6 升级到 PHP 8.2,不要一次性切全站。可以先把最小可用的新模块在新版本环境跑通,然后逐步扩展路径范围:先/newapi/用 8.2,再把/admin/切过来,最后才整体切换。每一步都经过完整的业务验证,比一次大升级稳得多。我在实践里发现,甚至可以在 Nginx 层做 10%(按 IP 或 cookie)的灰度流量切换,观察错误日志后再逐步放量,效果比一刀切好太多。
根据我这次给 PhpAsk 配置多版本 PHP 共存的完整过程来看,最核心的思路就一句话:隔离目录、独立 FPM、Nginx 按需切换。老版本不折腾,新版本不妥协,它们在同一台机器上和平共处。这套方法不仅适用于 PhpAsk,任何 PHP 项目遇到版本升级困境时都可以按同样的路子来处理。希望对正在折腾多版本环境的你有实际帮助,只要你严格按照步骤操作,主动避开我上面提到的那些坑,大概率一次就能顺利跑通。