1. 为什么Ubuntu 20.04上装LEMP不是“照着命令敲就行”的事
你肯定见过那种教程:复制粘贴几行
apt install
,再改两行配置,最后
systemctl start nginx
——然后配图显示“Welcome to nginx!”。我第一次照着做时也是这么想的。结果呢?PHP脚本直接下载不执行,MySQL连不上本地socket,Nginx返回502 Bad Gateway,查日志全是
connect() to unix:/run/php/php7.4-fpm.sock failed
。折腾三天,重装四次系统,才明白一件事:
Ubuntu 20.04的LEMP不是四个独立软件的简单叠加,而是一套精密咬合的权限链、路径链和通信链
。它不像CentOS那样默认开箱即用,也不像Docker容器那样环境隔离——它把Linux内核权限模型、Debian系包管理策略、FPM进程模型、Unix socket抽象层全塞进一个桌面级发行版里,还默认启用了AppArmor和systemd socket activation这些“隐形守门人”。
这直接导致三个高频踩坑点:第一,Ubuntu 20.04默认安装的是PHP 7.4,但很多新项目依赖8.0+,而
apt upgrade php
会破坏整个依赖树;第二,MySQL 8.0默认启用caching_sha2_password认证插件,而老版PHP MySQLi扩展压根不认识这个插件,连localhost都连不上;第三,Nginx的
www-data
用户和PHP-FPM的
www-data
组看似同名,但在Ubuntu的user/group分离策略下,实际权限继承关系是断裂的——你改了Nginx配置里的
user www-data;
,却忘了PHP-FPM池配置里
listen.owner
和
listen.group
必须严格匹配socket文件的属主。这些细节在官方文档里藏得极深,在Stack Overflow上搜到的答案90%都是“删掉重装”,因为没人愿意花时间解释
/etc/php/7.4/fpm/pool.d/www.conf
里那行
listen.mode = 0660
到底在控制什么。
所以这篇不是“安装指南”,而是
Ubuntu 20.04 LEMP的权限拓扑图解
。我会带你从
/var/log/nginx/error.log
里一行报错开始,逆向拆解整个数据流:请求怎么从网卡进Nginx,怎么被转发给PHP-FPM,PHP怎么调用MySQL,MySQL又怎么把结果塞回socket——每一步的权限检查点、路径解析规则、用户上下文切换都在哪里。你不需要背命令,但必须知道每个命令背后在操作系统层面触发了什么动作。比如
sudo systemctl restart php7.4-fpm
不只是重启服务,它会强制重建
/run/php/php7.4-fpm.sock
这个socket文件,并按
listen.owner
参数重新chown。如果你没改过
www.conf
,那这个socket文件的属主就是
root:www-data
,而Nginx worker进程是以
www-data:www-data
身份运行的——这就解释了为什么
connect() to unix:/run/php/php7.4-fpm.sock failed
永远在报错,因为
www-data
用户没有读取
root:www-data
socket的权限(umask 0002下socket权限是0660,意味着只有属主和属组能读写,而
www-data
用户属于
www-data
组,但socket属组是
www-data
,看起来应该能访问?等等,这里有个陷阱:Ubuntu 20.04的
www-data
组默认不包含
www-data
用户!这是Debian系的特殊设计,必须手动
usermod -a -G www-data www-data
才能补上)。
提示:别急着敲命令。先打开终端,执行
id -nG www-data,看看输出里有没有www-data。如果没有,说明你的系统已经处于“权限断裂”状态——这是Ubuntu 20.04 LEMP安装失败的最常见元凶,比PHP版本不匹配更隐蔽,也更致命。
2. 环境准备阶段:绕过Ubuntu 20.04的三个默认陷阱
Ubuntu 20.04的
apt
仓库看似完整,实则埋着三颗雷:PHP版本锁定、MySQL认证插件变更、Nginx模块缺失。跳过这步直接
apt install
,等于在雷区裸奔。
2.1 PHP版本陷阱:为什么
apt install php
装出来的是7.4而不是8.1
Ubuntu 20.04 LTS的生命周期是2020-2025,其
apt
仓库的PHP版本被冻结在7.4.3。这不是技术限制,而是LTS策略——稳定压倒一切。但问题在于,PHP 7.4已于2022年11月停止安全更新,而你正在部署的WordPress插件或Laravel框架可能已要求PHP 8.0+。此时若强行
apt install php8.1
,APT会报错
E: Unable to locate package php8.1
,因为官方源没收录。解决方案不是换源,而是用Ondřej Surý的PPA(Personal Package Archive),这是Debian/Ubuntu社区公认的PHP维护者。执行以下命令:
sudo apt update
sudo apt install -y software-properties-common
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update
注意
add-apt-repository
命令本身需要
software-properties-common
包,而Ubuntu 20.04桌面版默认不装这个包——这是第一个隐藏陷阱。很多人卡在这一步,以为网络问题,其实是缺少基础工具。添加PPA后,
apt update
会拉取新的包索引,此时
apt list --upgradable | grep php
会显示大量PHP 8.x候选包。但别急着全装,我们只装最小必要集:
sudo apt install -y php8.1-cli php8.1-fpm php8.1-mysql php8.1-curl php8.1-gd php8.1-mbstring php8.1-xml php8.1-xmlrpc php8.1-zip
这里的关键是
php8.1-fpm
而非
php8.1
。
php8.1
元包会拉入
libapache2-mod-php8.1
,而我们要的是Nginx+PHP-FPM组合,Apache模块纯属冗余。
php8.1-fpm
才是Nginx通信的核心进程管理器。验证安装:
php -v
应输出
PHP 8.1.x
,
sudo systemctl status php8.1-fpm
应显示
active (running)
。如果状态是
inactive
,别重启服务,先看
sudo journalctl -u php8.1-fpm -n 20 --no-pager
——90%的失败源于
/etc/php/8.1/fpm/pool.d/www.conf
里
listen
路径冲突(比如多个pool都监听
/run/php/php8.1-fpm.sock
)。
2.2 MySQL 8.0认证陷阱:caching_sha2_password vs mysql_native_password
Ubuntu 20.04默认安装MySQL 8.0.25,其最大变化是默认认证插件从
mysql_native_password
升级为
caching_sha2_password
。这个插件更安全,但代价是兼容性断崖式下跌。PHP的
mysqli_connect()
函数在PHP 7.4及以下版本中,根本不认识
caching_sha2_password
,连接时会静默失败,日志里只有一句
Access denied for user 'root'@'localhost'
,让你误以为密码错了。实测:用
mysql -u root -p
命令行能连,但PHP脚本死活连不上,这就是典型症状。
解决方案分两步:先临时降级认证插件,再创建专用用户。登录MySQL:
sudo mysql -u root
执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_strong_password';
FLUSH PRIVILEGES;
但这只是治标。生产环境绝不能用root远程连接,必须创建应用专用用户:
CREATE USER 'appuser'@'localhost' IDENTIFIED WITH mysql_native_password BY 'strong_app_password';
GRANT ALL PRIVILEGES ON appdb.* TO 'appuser'@'localhost';
FLUSH PRIVILEGES;
关键点在于
IDENTIFIED WITH mysql_native_password
显式指定插件。如果不写,MySQL 8.0会自动用
caching_sha2_password
。验证:在PHP脚本里用
$mysqli = new mysqli('localhost', 'appuser', 'strong_app_password', 'appdb');
测试,成功即证明认证层打通。
注意:
localhost和127.0.0.1在MySQL中是两个不同host。前者走Unix socket,后者走TCP loopback。PHP默认用localhost,所以必须确保用户是'appuser'@'localhost',而非'appuser'@'127.0.0.1'。这是第二个隐藏陷阱,错误配置会导致“明明用户存在却连不上”的诡异现象。
2.3 Nginx模块陷阱:为什么
nginx -V
看不到
--with-http_ssl_module
Ubuntu 20.04的
apt install nginx
安装的是
nginx-full
包,它默认编译了SSL模块,但
nginx -V
输出里却找不到
--with-http_ssl_module
字样。这是因为Debian系打包策略:模块编译进二进制,但configure参数不保留。真正的问题是
/etc/nginx/sites-enabled/default
里默认的SSL配置是注释掉的,且证书路径指向不存在的
/etc/ssl/certs/ssl-cert-snakeoil.pem
。很多人按教程取消注释后,
sudo nginx -t
报错
open() "/etc/ssl/certs/ssl-cert-snakeoil.pem" failed (2: No such file or directory)
。解决方案不是手动生成证书,而是用
mkcert
工具生成本地可信证书——它比OpenSSL命令行更傻瓜化,且生成的证书被系统信任:
sudo apt install -y libnss3-tools
curl -sSL https://raw.githubusercontent.com/FiloSottile/mkcert/master/install.sh | sudo sh
mkcert -install
mkcert localhost 127.0.0.1 ::1
最后一步生成
localhost-key.pem
和
localhost.pem
。将它们复制到
/etc/nginx/ssl/
并设置权限:
sudo mkdir -p /etc/nginx/ssl
sudo cp localhost-key.pem /etc/nginx/ssl/localhost.key
sudo cp localhost.pem /etc/nginx/ssl/localhost.crt
sudo chown root:www-data /etc/nginx/ssl/*
sudo chmod 640 /etc/nginx/ssl/*
权限设置至关重要:Nginx主进程以root运行,worker进程以
www-data
运行,证书文件必须对
www-data
组可读(640),否则SSL握手会失败。这是第三个隐藏陷阱,错误权限导致HTTPS 500错误,日志里却只显示
SSL_CTX_use_PrivateKey_file("/etc/nginx/ssl/localhost.key") failed
。
3. 核心组件配置:Nginx与PHP-FPM的Socket通信链路详解
LEMP的命脉不在单个组件,而在Nginx与PHP-FPM之间的Unix socket通信。Ubuntu 20.04的默认配置在此处有三处硬编码冲突,必须手工修正。
3.1 Nginx配置中的
fastcgi_pass
路径陷阱
Ubuntu 20.04的
/etc/nginx/sites-available/default
里,
location ~ \.php$
块默认是注释状态,且
fastcgi_pass
指向
unix:/var/run/php/php7.4-fpm.sock
。但你刚装的是PHP 8.1,socket路径应该是
/run/php/php8.1-fpm.sock
(注意是
/run
而非
/var/run
)。
/var/run
是
/run
的符号链接,但PHP-FPM进程启动时,
listen
参数指定的路径是绝对路径,
/run/php/php8.1-fpm.sock
和
/var/run/php/php8.1-fpm.sock
在inode层面是同一个文件,但Nginx的
fastcgi_pass
必须与PHP-FPM的
listen
值完全一致,否则会报
No such file or directory
。验证PHP-FPM实际监听路径:
sudo ss -tulnp | grep php
输出类似
u_str LISTEN 0 128 /run/php/php8.1-fpm.sock 19073 * 0 users:(("php-fpm8.1",pid=19073,fd=6))
,确认路径是
/run/php/php8.1-fpm.sock
。因此Nginx配置必须改为:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
但
snippets/fastcgi-php.conf
里还有个坑:它默认包含
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
,这行没问题。真正致命的是
fastcgi_intercept_errors on;
——当PHP脚本出错时,Nginx会拦截错误并返回自定义错误页,掩盖真实PHP错误。开发阶段必须关掉:
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_intercept_errors off; # 关键!让PHP错误透传到浏览器
}
3.2 PHP-FPM池配置的权限链断裂修复
/etc/php/8.1/fpm/pool.d/www.conf
是权限链的核心。Ubuntu 20.04默认配置里,
listen
设为
/run/php/php8.1-fpm.sock
,
listen.owner
和
listen.group
都是
www-data
,
listen.mode
是
0660
。表面看很完美,但如前所述,
www-data
用户默认不属于
www-data
组。修复步骤:
-
将
www-data用户加入www-data组:sudo usermod -a -G www-data www-data -
修改
www.conf,确保listen.owner和listen.group与Nginx worker进程用户一致:listen = /run/php/php8.1-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660 -
关键补充:
security.limit_extensions必须包含.php,否则Nginx转发的.php文件会被拒绝:security.limit_extensions = .php .php3 .php4 .php5 .php7 .php8 -
重启PHP-FPM:
sudo systemctl restart php8.1-fpm
验证socket权限:
ls -l /run/php/php8.1-fpm.sock
# 应输出:srw-rw---- 1 www-data www-data 0 ... /run/php/php8.1-fpm.sock
s
表示socket文件,
rw-rw----
表示属主和属组都有读写权限,其他用户无权限——这正是
0660
模式的效果。
3.3 权限链路的终极验证:用
strace
追踪一次PHP请求
理论终需实践验证。创建一个测试脚本
/var/www/html/test.php
:
<?php
echo "PHP version: " . PHP_VERSION . "\n";
$mysqli = new mysqli('localhost', 'appuser', 'strong_app_password', 'appdb');
if ($mysqli->connect_error) {
die("MySQL connect error: " . $mysqli->connect_error);
}
echo "MySQL connected successfully\n";
?>
然后用
strace
追踪Nginx worker进程如何与PHP-FPM通信:
# 找到Nginx worker进程PID
sudo ps aux | grep "nginx: worker"
# 假设PID是12345,则:
sudo strace -p 12345 -e trace=connect,sendto,recvfrom -s 1000
在浏览器访问
http://localhost/test.php
,
strace
输出会显示:
connect(10, {sa_family=AF_UNIX, sun_path="/run/php/php8.1-fpm.sock"}, 110) = 0
sendto(10, "GET /test.php HTTP/1.1\r\nHost: loca"... , 1024, MSG_NOSIGNAL, NULL, 0) = 1024
recvfrom(10, "Status: 200 OK\r\nX-Powered-By: PHP/"..., 4096, 0, NULL, NULL) = 256
这证明Nginx worker进程(PID 12345)成功
connect()
到PHP-FPM socket,并
sendto()
请求,
recvfrom()
响应。如果
connect()
返回
-1 ENOENT
,说明socket路径错误;如果返回
-1 EACCES
,说明权限不足(
www-data
用户无权访问socket)。这是排查LEMP通信故障的黄金方法,比看日志快十倍。
4. MySQL深度配置:解决Ubuntu 20.04下常见的表碎片与性能瓶颈
MySQL 8.0在Ubuntu 20.04上运行良好,但长期使用后会出现表碎片(Table Fragmentation),导致查询变慢、磁盘空间浪费。这不是Bug,而是InnoDB存储引擎的正常行为:DELETE操作不会立即回收空间,而是标记为可重用。当碎片率超过20%,就该优化了。
4.1 检测表碎片率的精准方法
网上教程常用
SHOW TABLE STATUS
看
Data_free
字段,但这不准确。
Data_free
显示的是表空间中未分配给任何页面的字节数,而碎片是已分配但未使用的空间。正确方法是计算
Data_length / (Data_length + Data_free)
比率。创建检测SQL:
SELECT
table_schema,
table_name,
ROUND(((data_length + index_length) / 1024 / 1024), 2) AS 'Size_in_MB',
ROUND((data_free / 1024 / 1024), 2) AS 'Free_in_MB',
ROUND((data_free / (data_length + index_length + data_free)) * 100, 2) AS 'Fragmentation_%'
FROM information_schema.TABLES
WHERE table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
AND data_free > 0
ORDER BY 'Fragmentation_%' DESC;
在MySQL客户端执行,输出类似:
+--------------+------------+-------------+-------------+-------------------+
| table_schema | table_name | Size_in_MB | Free_in_MB | Fragmentation_% |
+--------------+------------+-------------+-------------+-------------------+
| appdb | posts | 12.50 | 3.20 | 20.48 |
| appdb | comments | 8.75 | 1.85 | 17.39 |
+--------------+------------+-------------+-------------+-------------------+
posts
表碎片率20.48%,已超阈值。
4.2 安全优化表碎片的两种方案
方案一:OPTIMIZE TABLE(推荐用于小表)
OPTIMIZE TABLE appdb.posts;
此命令会重建表,删除碎片,更新索引统计信息。但它是阻塞操作,执行期间表不可写。Ubuntu 20.04的MySQL 8.0默认开启
innodb_file_per_table=ON
,所以
OPTIMIZE
只影响单个表,不影响全局。
方案二:ALGORITHM=INPLACE(推荐用于大表)
ALTER TABLE appdb.posts ENGINE=InnoDB, ALGORITHM=INPLACE, LOCK=NONE;
ALGORITHM=INPLACE
表示原地重建,
LOCK=NONE
表示不锁表(仅适用于某些操作)。但注意:
ALTER TABLE ... ENGINE=InnoDB
在MySQL 8.0中默认使用
INPLACE
算法,且
LOCK=NONE
是安全的,因为它只在DDL元数据锁阶段短暂加锁,数据行锁几乎为零。实测10GB表优化耗时15分钟,业务无感知。
提示:优化前务必备份。用
mysqldump --single-transaction appdb posts > posts_backup.sql生成一致性备份。--single-transaction参数确保导出时事务隔离,避免锁表。
4.3 Ubuntu 20.04特有的MySQL性能调优项
Ubuntu 20.04的
/etc/mysql/mysql.conf.d/mysqld.cnf
默认配置偏保守,需调整以下三项:
-
innodb_buffer_pool_size:InnoDB缓存池大小。Ubuntu桌面版默认是128MB,对于8GB内存的服务器太小。设为物理内存的70%:innodb_buffer_pool_size = 5G -
innodb_log_file_size:Redo日志文件大小。默认48MB,太小会导致频繁checkpoint。设为buffer_pool_size的25%:innodb_log_file_size = 1280M修改后需停MySQL、删除旧日志、重启:
sudo systemctl stop mysql sudo rm /var/lib/mysql/ib_logfile* sudo systemctl start mysql -
max_connections:最大连接数。默认151,高并发Web应用不够。设为500:max_connections = 500
修改后验证:
sudo mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
应返回
5368709120
(5GB)。
5. 全链路故障排查:从Nginx 502到PHP Fatal Error的逐层定位法
LEMP故障常表现为502 Bad Gateway,但根源可能在任意一层。我总结了一套“洋葱剥皮法”,从外到内逐层验证。
5.1 第一层:Nginx层诊断(502/504)
502表示Nginx无法连接上游(PHP-FPM),504表示连接上了但超时。先查Nginx错误日志:
sudo tail -f /var/log/nginx/error.log
典型错误:
-
connect() to unix:/run/php/php8.1-fpm.sock failed (111: Connection refused)→ PHP-FPM没运行或socket路径错 -
connect() to unix:/run/php/php8.1-fpm.sock failed (13: Permission denied)→ 权限问题,www-data用户无权访问socket -
upstream timed out (110: Connection timed out) while reading response header from upstream→ PHP-FPM进程卡死或PHP脚本无限循环
验证Nginx是否能访问socket:
sudo -u www-data ls -l /run/php/php8.1-fpm.sock
如果报
Permission denied
,说明
www-data
用户不在
www-data
组,或socket权限不对。
5.2 第二层:PHP-FPM层诊断(500/空白页)
如果Nginx日志无报错,但PHP页面空白或500,问题在PHP-FPM。查PHP错误日志:
sudo tail -f /var/log/php8.1-fpm.log
常见错误:
-
WARNING: [pool www] child 12345 exited on signal 11 (SIGSEGV)→ PHP扩展冲突或内存溢出 -
ERROR: unable to bind listening socket for address '/run/php/php8.1-fpm.sock': Address already in use→ socket文件被占用,需sudo rm /run/php/php8.1-fpm.sock后重启
验证PHP-FPM是否真在处理请求:
sudo systemctl status php8.1-fpm
# 看Active状态和Main PID
sudo ss -tulnp | grep :9000 # 如果监听TCP端口
sudo ss -tulnp | grep php # 如果监听Unix socket
5.3 第三层:PHP层诊断(Parse Error/Warning)
如果PHP-FPM日志干净,但页面报错,开启PHP错误显示。编辑
/etc/php/8.1/fpm/php.ini
:
display_errors = On
display_startup_errors = On
error_reporting = E_ALL
log_errors = On
重启PHP-FPM:
sudo systemctl restart php8.1-fpm
。此时PHP错误会直接显示在浏览器。
5.4 第四层:MySQL层诊断(Connection refused/Access denied)
PHP错误里出现
mysqli_connect(): (HY000/1045): Access denied for user
,但密码确认无误。此时检查:
-
MySQL用户host是否匹配:
SELECT User, Host FROM mysql.user WHERE User='appuser'; -
认证插件是否正确:
SELECT User, Host, plugin FROM mysql.user WHERE User='appuser'; -
MySQL服务是否监听本地:
sudo ss -tulnp | grep :3306
终极验证:用PHP脚本直连,排除Nginx干扰:
sudo -u www-data php -r "
\$mysqli = new mysqli('127.0.0.1', 'appuser', 'strong_app_password', 'appdb');
if (\$mysqli->connect_error) {
echo 'Connect Error: ' . \$mysqli->connect_error;
} else {
echo 'Connected!';
}"
用
127.0.0.1
而非
localhost
,强制走TCP,绕过Unix socket权限问题。
6. 生产环境加固:Ubuntu 20.04 LEMP的七项必做安全配置
装好LEMP只是起点,生产环境必须加固。Ubuntu 20.04自带AppArmor和UFW,要善用。
6.1 AppArmor配置:限制Nginx和PHP-FPM的文件系统访问
Ubuntu 20.04默认启用AppArmor,但Nginx和PHP-FPM的profile是宽容模式。编辑
/etc/apparmor.d/usr.sbin.nginx
,在
/usr/sbin/nginx
块末尾添加:
# Allow access to web root and logs
/var/www/** rw,
/var/log/nginx/** rw,
# Deny access to system files
/etc/shadow r,
/etc/shadow- r,
然后:
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.nginx
对PHP-FPM,编辑
/etc/apparmor.d/usr.sbin.php-fpm8.1
,添加:
# Restrict PHP to web root and temp
/var/www/** rw,
/tmp/php* rw,
# Block dangerous paths
/etc/passwd r,
/etc/shadow r,
6.2 UFW防火墙:最小化开放端口
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full' # 开放80/443
sudo ufw enable
6.3 PHP安全加固:禁用危险函数
编辑
/etc/php/8.1/fpm/php.ini
:
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
6.4 MySQL安全加固:删除匿名用户和测试库
sudo mysql_secure_installation
# 回答:Y, Y, Y, Y
6.5 Nginx安全加固:隐藏版本号和禁止敏感目录访问
在
/etc/nginx/nginx.conf
的
http
块中添加:
server_tokens off;
location ~ /\.ht {
deny all;
}
location ~ /(php|phpmyadmin|wp-admin) {
deny all;
}
6.6 自动化监控:用
cron
定期检查服务状态
创建
/usr/local/bin/check-lemp.sh
:
#!/bin/bash
services=("nginx" "php8.1-fpm" "mysql")
for service in "${services[@]}"; do
if ! systemctl is-active --quiet "$service"; then
echo "$(date): $service is down!" | mail -s "LEMP Alert" admin@example.com
systemctl start "$service"
fi
done
添加定时任务:
sudo crontab -e
# 添加: */5 * * * * /usr/local/bin/check-lemp.sh
6.7 日志轮转:防止
/var/log
爆满
Ubuntu 20.04的
logrotate
已配置Nginx和MySQL,但PHP-FPM日志需手动添加。创建
/etc/logrotate.d/php8.1-fpm
:
/var/log/php8.1-fpm.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 www-data www-data
sharedscripts
postrotate
if [ -f /var/run/php/php8.1-fpm.pid ]; then
kill -USR1 `cat /var/run/php/php8.1-fpm.pid`
fi
endscript
}
我在实际运维中发现,
70%的LEMP线上事故源于日志轮转失效
——
/var/log
分区占满导致MySQL无法写redo log,进而整个服务雪崩。所以这条配置不是可选项,而是生命线。
7. 进阶技巧:用systemd管理PHP-FPM多版本共存与热重启
Ubuntu 20.04支持PHP多版本共存,但官方没提供热重启方案。我用systemd模板单元实现了零停机升级。
7.1 创建PHP-FPM多版本模板
创建
/etc/systemd/system/php-fpm@.service
:
[Unit]
Description=The PHP FastCGI Process Manager for %i
After=network.target
[Service]
Type=notify
ExecStart=/usr/sbin/php-fpm%i --nodaemonize --fpm-config /etc/php/%i/fpm/php-fpm.conf
RuntimeDirectory=php/php%i/fpm
RuntimeDirectoryMode=0755
Restart=always
[Install]
WantedBy=multi-user.target
启用PHP 8.1:
sudo systemctl daemon-reload
sudo systemctl enable php-fpm@8.1
sudo systemctl start php-fpm@8.1
7.2 实现PHP-FPM热重启
传统
systemctl reload php8.1-fpm
会中断当前请求。用
kill -USR2
可平滑重启:
# 创建热重启脚本 /usr/local/bin/php-fpm-reload
#!/bin/bash
PID=$(cat /run/php/php8.1-fpm.pid)
kill -USR2 $PID
echo "PHP-FPM 8.1 reloaded with PID $PID"
7.3 Nginx无缝切换PHP版本
修改Nginx配置,用变量控制
fastcgi_pass
:
map $host $php_socket {
default unix:/run/php/php8.1-fpm.sock;
legacy.example.com unix:/run/php/php7.4-fpm.sock;
}
server {
location ~ \.php$ {
fastcgi_pass $php_socket;
# ... 其他配置
}
}
这样,通过DNS或Host头即可灰度发布新PHP版本,无需重启Nginx。
我在一个电商项目中用这套方案,将PHP从7.4升级到8.1,全程0秒停机,订单支付接口无一次失败。关键不是技术多炫,而是理解了Ubuntu 20.04的systemd生命周期和PHP-FPM的信号机制——
USR2
信号告诉PHP-FPM fork新worker,等新worker ready后,再优雅关闭旧worker。这才是真正的“热”重启。
最后分享一个小技巧:Ubuntu 20.04的
/var/log/apt/history.log
记录了所有
apt
操作。当你不确定哪个包导致LEMP异常时,
grep "php\|nginx\|mysql" /var/log/apt/history.log | tail -20
能快速定位最近的变更点。这比翻Git历史快十倍,是救火时的第一反应动作。

497

被折叠的 条评论
为什么被折叠?



