news 2026/10/15 6:24:40

Nginx多域名解析多PHP版本:单机多版本共存实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nginx多域名解析多PHP版本:单机多版本共存实战

1. 为什么需要在一台服务器上跑多个PHP版本

一台服务器只跑一个PHP版本,这在几年前还算常规操作。但现在的情况变了——你可能同时维护着三四个项目,有的跑的是老框架,只兼容PHP 7.2;有的用了新特性,必须上PHP 8.1;还有的客户指定要PHP 7.4,说升级会炸。这时候如果每个项目配一台服务器,成本直接起飞,运维也累得够呛。

Nginx根据域名解析多个PHP版本,说白了就是让同一个Nginx监听80或443端口,根据请求的域名不同,把PHP请求转发给不同的PHP-FPM进程池。比如old.example.com走PHP 7.2,new.example.com走PHP 8.1,api.example.com走PHP 7.4。用户访问的时候完全无感知,但后端各跑各的,互不干扰。

这个方案解决的核心问题是资源复用与版本隔离。一台机器、一个Nginx、多个PHP-FPM实例,每个实例监听不同的端口或Socket,Nginx在server块里根据server_name匹配域名,然后把.php请求通过fastcgi_pass指向对应的PHP-FPM。整个过程逻辑清晰,维护成本低,特别适合中小型项目、测试环境、多客户站群这类场景。

适合谁来参考?如果你手头有多个PHP项目需要共存,又不想折腾容器编排,或者你对Docker还不够熟悉,想先用传统方式快速搞定,那这套方案就是给你准备的。下面我会从整体设计、核心细节、实操过程到问题排查,一步步拆开讲。

2. 整体架构设计与方案选型思路

2.1 为什么选Nginx+多PHP-FPM而不是其他方案

多PHP版本共存,常见的有三种路子:一是Docker容器,每个项目一个容器,隔离性最好,但资源开销大,网络配置也麻烦;二是多台服务器,简单粗暴但费钱;三是单Nginx+多PHP-FPM,也就是本文要讲的方案。

我选第三种,理由很直接:轻量、可控、调试方便。PHP-FPM本身就是独立进程池,不同版本监听不同端口,天然隔离。Nginx作为反向代理层,只负责根据域名转发,不关心后端跑的是哪个版本。这种架构下,你不需要额外学容器编排,不需要处理容器网络,日志也都在同一台机器上,排查问题快得多。

当然,这个方案也有局限。如果项目之间需要完全的文件系统隔离、或者依赖不同的系统库,那还是得上容器。但对于大多数“同一台机器跑多个PHP项目”的需求,Nginx+多PHP-FPM已经够用了。

2.2 域名解析与PHP-FPM的映射逻辑

核心逻辑其实就一句话:Nginx的server块根据server_name匹配域名,然后在location ~ .php$里把请求转发给对应的PHP-FPM监听地址。

举个例子,假设你有两个域名:

  • site-a.example.com需要PHP 7.4
  • site-b.example.com需要PHP 8.1

那Nginx配置里会有两个server块,分别写server_name site-a.example.com;和server_name site-b.example.com;。在各自的location里,fastcgi_pass分别指向127.0.0.1:9074和127.0.0.1:9081。PHP-FPM那边,7.4版本的池监听9074,8.1版本的池监听9081。请求进来,Nginx一看域名,就知道该往哪个端口送。

这里的关键是端口规划。我习惯用90xx系列,比如9072对应PHP 7.2,9074对应7.4,9080对应8.0,9081对应8.1。这样一眼就能看出端口和版本的对应关系,不容易搞混。

2.3 方案的优势与潜在坑点

优势前面说了,轻量、直观、易调试。但坑点也得提前说清楚:

  • PHP-FPM配置文件路径不同:不同版本的PHP-FPM,配置文件可能在/etc/php/7.4/fpm/pool.d/或/etc/php-fpm.d/,取决于系统。你得先确认清楚。
  • Socket vs TCP:用Unix Socket性能稍好,但跨版本管理时TCP端口更直观。我一般用TCP,方便用netstat或ss快速确认哪个版本在跑。
  • Nginx的fastcgi_params路径:不同系统下fastcgi_params文件位置可能不同,有的在/etc/nginx/,有的在/usr/local/nginx/conf/。配置时要用绝对路径,避免找不到。
  • 权限问题:PHP-FPM的运行用户必须能读取项目文件,否则会报Access denied。每个池的user和group要设置正确。

这些坑我在后面会详细展开,先有个印象就行。

3. 核心细节解析与实操要点

3.1 PHP-FPM多实例的安装与端口规划

假设你用的是常见的Linux发行版,安装多个PHP版本通常有两种方式:一是用系统包管理器(如apt或yum)安装不同版本的PHP包;二是自己编译。我推荐用包管理器,省事。

以Debian/Ubuntu为例,安装PHP 7.4和PHP 8.1:

sudo apt install php7.4-fpm php8.1-fpm

安装完成后,系统会自动创建对应的FPM服务,比如php7.4-fpm和php8.1-fpm。默认情况下,它们可能都监听9000端口,这就会冲突。所以第一步是改端口。

PHP 7.4的池配置通常在/etc/php/7.4/fpm/pool.d/www.conf,找到listen = /run/php/php7.4-fpm.sock或listen = 127.0.0.1:9000,改成listen = 127.0.0.1:9074。同理,PHP 8.1改成listen = 127.0.0.1:9081。

改完后重启服务:

sudo systemctl restart php7.4-fpm sudo systemctl restart php8.1-fpm

然后用ss -tlnp | grep php确认端口监听情况。你应该能看到9074和9081分别被不同的PHP-FPM进程占用。

注意:有些系统上PHP-FPM的池配置文件可能不在pool.d目录下,而是直接在php-fpm.conf里。用php-fpm7.4 -tt可以测试配置文件语法,并显示实际加载的配置文件路径。

3.2 Nginx server块的域名匹配与fastcgi_pass配置

Nginx这边,核心是写好server块。假设项目A的代码在/var/www/site-a,项目B在/var/www/site-b。配置大概长这样:

server { listen 80; server_name site-a.example.com; root /var/www/site-a; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9074; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } } server { listen 80; server_name site-b.example.com; root /var/www/site-b; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9081; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }

关键点:

  • server_name必须和实际访问的域名一致,否则Nginx会走默认server块。
  • fastcgi_pass指向对应的PHP-FPM端口。
  • SCRIPT_FILENAME必须正确设置,否则PHP-FPM找不到文件。
  • include fastcgi_params引入通用参数,不同系统路径可能不同,用nginx -V查看编译时的默认路径。

配置完成后,nginx -t测试语法,然后systemctl reload nginx重载。

3.3 如何验证每个域名走的是正确的PHP版本

验证方法很简单:在每个项目的根目录放一个info.php,内容为<?php phpinfo(); ?>。然后分别访问http://site-a.example.com/info.php和http://site-b.example.com/info.php,看输出的PHP版本是否对应。

如果版本不对,排查顺序:

  1. 确认Nginx配置里fastcgi_pass端口是否正确。
  2. 确认PHP-FPM实际监听的端口是否和配置一致。
  3. 确认访问的域名是否匹配到了正确的server块(可以看Nginx访问日志)。
  4. 确认没有其他server块拦截了请求(比如默认server)。

实操心得:我习惯在info.php里加一行echo $_SERVER['SERVER_NAME'];,这样能同时确认域名和PHP版本,一举两得。

4. 完整实操过程与核心环节实现

4.1 环境准备与PHP多版本安装

假设你有一台干净的Ubuntu 22.04服务器,已经装好了Nginx。现在要加PHP 7.4和PHP 8.1。

首先添加第三方PPA(因为Ubuntu 22.04默认源里没有PHP 7.4):

sudo apt install software-properties-common sudo add-apt-repository ppa:ondrej/php sudo apt update

然后安装:

sudo apt install php7.4-fpm php7.4-mysql php7.4-curl php7.4-mbstring sudo apt install php8.1-fpm php8.1-mysql php8.1-curl php8.1-mbstring

安装完成后,检查服务状态:

systemctl status php7.4-fpm systemctl status php8.1-fpm

如果两个服务都active,说明安装成功。接下来改端口。

4.2 修改PHP-FPM池配置并启动多实例

编辑/etc/php/7.4/fpm/pool.d/www.conf,找到listen行,改成:

listen = 127.0.0.1:9074

同样,编辑/etc/php/8.1/fpm/pool.d/www.conf:

listen = 127.0.0.1:9081

顺便检查一下user和group,确保和Nginx运行用户一致(通常是www-data):

user = www-data group = www-data

改完后重启:

sudo systemctl restart php7.4-fpm sudo systemctl restart php8.1-fpm

用ss -tlnp | grep -E '9074|9081'确认端口已监听。

4.3 Nginx配置拆分与域名绑定

现在配置Nginx。我习惯把每个域名的配置拆成独立文件,放在/etc/nginx/sites-available/下,然后软链到sites-enabled/。

创建site-a.conf:

server { listen 80; server_name site-a.example.com; root /var/www/site-a; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9074; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }

创建site-b.conf:

server { listen 80; server_name site-b.example.com; root /var/www/site-b; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { include fastcgi_params; fastcgi_pass 127.0.0.1:9081; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; } }

然后:

sudo ln -s /etc/nginx/sites-available/site-a.conf /etc/nginx/sites-enabled/ sudo ln -s /etc/nginx/sites-available/site-b.conf /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

4.4 测试与验证:用phpinfo确认版本隔离

在/var/www/site-a和/var/www/site-b下分别创建info.php:

<?php phpinfo(); echo 'SERVER_NAME: ' . $_SERVER['SERVER_NAME'];

然后本地用curl测试(假设域名已解析到本机):

curl -H "Host: site-a.example.com" http://127.0.0.1/info.php | grep -i "PHP Version" curl -H "Host: site-b.example.com" http://127.0.0.1/info.php | grep -i "PHP Version"

你应该看到两个不同的版本号。如果相同,说明配置没生效,检查Nginx是否重载、PHP-FPM是否重启、端口是否对应。

注意:如果服务器有防火墙,确保80端口开放。另外,如果用了CDN或反向代理,SERVER_NAME可能不是预期值,需要看X-Forwarded-Host之类的头。

5. 常见问题与排查技巧实录

5.1 访问PHP文件返回502 Bad Gateway

这是最常见的问题。502通常意味着Nginx无法连接到PHP-FPM。排查步骤:

  1. 检查PHP-FPM是否在运行:systemctl status php7.4-fpm。
  2. 检查端口是否监听:ss -tlnp | grep 9074。
  3. 检查Nginx配置里的fastcgi_pass端口是否和PHP-FPM监听一致。
  4. 检查SELinux或防火墙是否拦截了本地回环通信(一般不会,但值得一看)。

如果PHP-FPM没起来,看日志:journalctl -u php7.4-fpm -n 50。常见原因是配置文件语法错误,用php-fpm7.4 -tt测试。

5.2 访问PHP文件返回404 Not Found

404说明Nginx找不到文件。检查:

  • root路径是否正确。
  • SCRIPT_FILENAME是否拼写正确。
  • 文件权限是否允许Nginx用户读取。

我遇到过一种情况:root路径末尾多了斜杠,导致$document_root$fastcgi_script_name拼出来是/var/www/site-a//info.php,虽然Linux通常能处理双斜杠,但某些PHP-FPM版本会报404。去掉多余斜杠就好了。

5.3 PHP版本不对,所有域名都走同一个版本

这说明Nginx没有正确匹配server_name,所有请求都落到了默认server块。排查:

  • 确认server_name拼写和访问域名完全一致。
  • 确认没有其他server块用了default_server且匹配了所有域名。
  • 看Nginx访问日志,确认请求落到了哪个server块。

实操心得:我习惯在Nginx配置里加access_log /var/log/nginx/site-a.access.log;,每个域名独立日志,排查时一目了然。

5.4 常见问题速查表

问题现象可能原因排查方法
502 Bad GatewayPHP-FPM未启动或端口不对检查服务状态和端口监听
404 Not Foundroot路径或SCRIPT_FILENAME错误检查Nginx配置和文件权限
版本不对server_name未匹配或fastcgi_pass错误看访问日志和PHP-FPM端口
权限拒绝PHP-FPM用户无法读取文件检查文件权限和池的user/group
配置重载失败Nginx语法错误nginx -t测试

6. 进阶技巧与长期维护建议

6.1 用Unix Socket替代TCP端口

TCP端口直观,但Unix Socket性能稍好,且不占用端口号。配置方法:PHP-FPM池里listen = /run/php/php7.4-fpm.sock,Nginx里fastcgi_pass unix:/run/php/php7.4-fpm.sock;。注意Socket文件的权限,确保Nginx用户能读写。

我一般在小规模部署时用TCP,大规模或性能敏感时用Socket。两者切换成本很低,改一行配置的事。

6.2 日志分离与监控

每个域名独立access_log和error_log,方便排查。PHP-FPM也有自己的慢日志,在池配置里开启:

slowlog = /var/log/php7.4-fpm-slow.log request_slowlog_timeout = 5s

这样能抓到执行超过5秒的脚本,对性能优化很有帮助。

6.3 版本升级与回滚策略

当某个项目需要从PHP 7.4升级到8.1时,不要直接改现有配置。正确做法是:先装好PHP 8.1的FPM,配好新端口,然后新建一个server块,用新域名或子域名测试。测试通过后,再把原域名的fastcgi_pass指向新端口,重载Nginx。这样回滚只需要改回端口,风险极低。

我在实际维护中,会保留旧版本的FPM实例至少一个月,确认没问题再停掉。这期间如果新版本出问题,改一行配置就能切回去。

6.4 资源限制与进程池调优

每个PHP-FPM池都可以独立设置pm.max_children、pm.start_servers等参数。如果一台机器上跑多个池,总进程数不能超过内存承受能力。我的经验是:先估算每个池的最大并发,然后加总,确保总内存占用不超过物理内存的70%。比如每个PHP-FPM进程占50MB,8个池各开10个进程,就是4000MB,4GB内存的机器就吃紧了。

可以用pm = ondemand模式,按需启动进程,节省内存。但ondemand模式下,第一个请求会有启动延迟,适合访问量不高的场景。

最后分享一个小技巧:如果你不确定某个域名走的是哪个PHP版本,可以在Nginx配置里加一个自定义header,比如add_header X-PHP-Version "7.4";,这样用浏览器开发者工具看响应头就能确认,不用每次都翻phpinfo。这个技巧在排查多版本混跑时特别管用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 6:24:05

STM32F103RCT6引脚功能与复用配置详解:GPIO、外部中断及调试口避坑指南

简介&#xff1a;面向嵌入式开发者和电子专业学生&#xff0c;这份资源系统梳理了STM32F103RCT6微控制器基于Cortex-M3内核的引脚功能&#xff0c;涵盖GPIO输入输出模式与中断触发、USART/SPI/I2C等复用功能、12位ADC与DAC转换、外部中断输入、内外时钟源选择以及JTAG/SWD调试接…

作者头像 李华
网站建设 2026/10/15 6:23:49

用API思维构建个人学习系统:从知识封装到高效调用

1. 先想清楚&#xff1a;学习这件事&#xff0c;为什么需要一套“方法论”先抛个问题&#xff1a;你有没有过这种体验——收藏了一堆教程、买了好几本“神书”、报了名参加训练营&#xff0c;结果几个月过去&#xff0c;真正内化成自己能力的东西少得可怜&#xff1f;不是你不够…

作者头像 李华
网站建设 2026/10/15 6:22:54

MATLAB自动选峰法:模态参数识别与阻尼比估算实战

做结构健康监测和机械振动测试的朋友&#xff0c;应该都体会过这种状态&#xff1a;传感器数据采集了一大堆&#xff0c;导入MATLAB算完频谱之后&#xff0c;剩下的时间全耗在对着幅频曲线“瞪眼睛”上。这个峰是不是真实模态&#xff1f;旁边那个小鼓包是噪声还是弱模态&#…

作者头像 李华
网站建设 2026/10/15 6:22:43

跨端迁移到OpenHarmony:Redux Toolkit选择器避坑与性能优化

最近我把一个基于 React Native 的跨端应用往 OpenHarmony 平台上迁移&#xff0c;整个过程中踩坑最多的并不是 UI 组件适配&#xff0c;也不是原生模块桥接&#xff0c;反而是状态管理里一个看似不起眼的环节——Redux Toolkit 选择器的使用。今天就把这块的实践经验完整梳理一…

作者头像 李华
网站建设 2026/10/15 6:21:56

TensorFlow实战:从模型训练到部署的完整链路与避坑指南

如果你跟我一样&#xff0c;是在实际项目里被逼着从零开始接触 TensorFlow 的&#xff0c;你大概也会经历这么一段&#xff1a;网上翻了一堆教程&#xff0c;每个开头都是"TensorFlow 是深度学习框架"&#xff0c;然后贴一段手写数字识别的代码&#xff0c;你跑通了&…

作者头像 李华