1. 为什么LNMP依然是云上部署的主流选型
大概从我开始接触服务器运维起,LNMP这套组合就一直占据着云上部署的半壁江山。就算到现在容器化、K8s已经被聊到烂大街,依然有大量中小型项目、个人站点、企业官网,包括不少跑在云主机上的SaaS应用,选择了LNMP作为基础设施。并不是说容器方案不好,而是LNMP在资源占用、维护门槛、排错成本这些维度上,依然有它不可替代的位置。
1.1 LNMP与LAMP、LNMPA的核心差异
先把LNMP这个名词拆开:Linux操作系统、Nginx Web服务器、MySQL数据库、PHP动态语言运行时。它经常被拿来和LAMP(Apache替代Nginx)以及LNMPA(Nginx反向代理 + Apache处理PHP)做比较。
Nginx处理静态文件和并发连接的能力强于Apache,而且内存占用低,在高并发静态请求场景下优势非常明显。PHP-FPM作为独立的FastCGI进程管理器,和Nginx通过FastCGI协议通信,这种解耦设计让Web层和应用层可以分开扩展。比如你某天发现PHP-CPU跑满,可以直接给PHP-FPM所在的实例升配,或者把PHP-FPM迁移到另一台机器上,Nginx只需要改一下fastcgi_pass的地址。
1.2 什么样的项目适合用LNMP
从我经手的项目来看,至少有三类场景是LNMP的主场:
- 个人博客、CMS站点(WordPress、Typecho、Zblog)这类以PHP为核心的传统应用;
- 中小型电商、企业官网、API服务,不需要微服务那套复杂治理,一台或几台云主机就能扛住;
- 作为容器化改造前的过渡形态,先把业务跑稳,后续再平滑迁移。
如果你的项目已经是纯前后端分离,后端是Go或者Java,那LNMP就不太适合了。Nginx这时一般只充当静态文件服务和反向代理,MySQL按需选型,PHP部分可以直接去掉——这一点选型时要想清楚,不要盲目套模板。
2. 云服务器选型与系统初始化的细节决策
很多人以为部署LNMP最难的是敲命令,其实我踩过最大的坑,恰恰是最开始那两步:机器配置买低了、系统镜像选错了。这两个问题一旦定下来,后期想反悔非常麻烦。
2.1 配置选型:按站点规模反推硬件需求
我一般建议按预期日活和业务类型来定最低配置。分享一个比较保守的估算方式:
| 业务规模 | 推荐配置 | 适用场景 |
|---|---|---|
| 个人博客/轻量展示站 | 2C2G,40G SSD | 日PV 1万以内,无大量图片处理 |
| 中小型企业站/电商 | 2C4G,60G SSD | 日PV 5万左右,有订单和会员体系 |
| 较高并发/数据密集型 | 4C8G起步,100G SSD | 日PV 20万以上,或跑复杂统计查询 |
很多人会在2G内存的机器上强行装MySQL 8.0,结果内存长期顶着80%以上。如果你预算有限,我建议优先保证内存。Nginx本身很省资源,PHP-FPM才是吃内存的大户——按每个PHP-FPM进程平均占用30-50MB计算,2G内存跑并发稍高的站点会非常吃力。这也是为什么我一直强调:LNMP里最先要升级的总是内存。
2.2 系统镜像选择:CentOS停更后的现实选择
这里必须提醒一个关键变化:CentOS 7已经在2024年6月停止维护,CentOS 8更早就停了。如果你还在用CentOS做新项目部署,等于一出生就带着安全漏洞隐患。现在云厂商的镜像市场里,主流选择基本是这几个:
- Ubuntu 22.04 LTS/24.04 LTS:社区活跃,软件源更新及时,MySQL、PHP、Nginx的官方源支持都很好。我自己现在新项目首选。
- Debian 12:比Ubuntu更精简,系统占用更低,适合配置较低的机器。
- Alibaba Cloud Linux 3 / TencentOS:国内云厂商自家维护的兼容CentOS的发行版,如果团队以前写惯了CentOS的运维脚本,迁移成本最低。
不建议选最新的非LTS版本,比如Ubuntu的非LTS版本只有9个月支持周期,你不可能半年重装一次系统。也不要继续用CentOS 7将就,等出事再处理代价更大。
2.3 云主机购买后的初始化清单
拿到一台全新的云主机,我习惯按这个顺序做初始化,每一步都有明确目的:
- 更新系统软件包:
apt update && apt upgrade -y(Ubuntu/Debian),把内核和基础库升到当前最新,避免后续编译或安装时遇到依赖版本过旧的问题。 - 创建普通用户并配置sudo权限:尽量不要直接用root操作日常事务,万一误删或手滑,影响面太大。创建用户后,把SSH登录方式改成密钥登录,关闭密码登录和root直登,这是云上最基本的一道防线。
- 配置时区与时间同步:
timedatectl set-timezone Asia/Shanghai,然后确认timedatectl输出里System clock synchronized: yes。MySQL的日志、PHP的报错时间如果和实际时间差8小时,排查问题时会非常崩溃。 - 优化SSH配置:修改
/etc/ssh/sshd_config中的PermitRootLogin和PasswordAuthentication,改完记得先sshd -t检查语法,再重启sshd。这里有个经验:不要关掉自己当前正在用的连接,否则一旦语法出错,你就被锁在外面了。 - 配置安全组/防火墙:云厂商控制台的安全组规则和系统内部的防火墙是两层,很多人只配了安全组忘了系统防火墙,或者反过来。原则是只放行必要端口:22(或自定义SSH端口)、80、443,管理面板类端口尽量不对外开放。
3. 软件源与版本组合:一个容易忽略的前置工作
LNMP每一步安装本身不难,难的是版本之间的兼容性。我见过太多失败案例,都是因为用了系统自带的旧版本软件源,装出来的Nginx 1.14、PHP 7.0,连新版CMS的依赖要求都满足不了。所以在动手装任何组件之前,先把软件源这件事解决好。
3.1 版本组合原则:没有绝对最新,只有相对稳定
以当前(2025年)的时间节点,我个人比较推荐的组合是:
- Nginx 1.24.x 或 1.26.x(主线版本)
- MySQL 8.0.x(不要用MySQL 8.4,除非你有明确的特性需求,8.0依然是生态最成熟、坑被踩得最多的版本)
- PHP 8.2 或 8.3(WordPress、Laravel、ThinkPHP等主流框架都已经完美兼容)
这个组合不是随便选的。Nginx 1.26是当前主线稳定版,修复了此前1.24里的一些HTTP/2和SSL相关问题;MySQL 8.0的caching_sha2_password认证插件在PHP 8.1+已经有了完善的客户端支持,不会再出现以前那种"连不上数据库"的诡异报错;PHP 8.2以后性能相比7.x有约20%-30%的提升,而且绝大多数老项目做一次小改就能迁移。
3.2 用官方源替代系统源:以Nginx和PHP为例
Ubuntu系统源里的Nginx版本往往落后官方好几个小版本,PHP更是只有旧版。我的做法是直接添加官方源:
# Nginx官方源(Ubuntu 22.04) curl -fsSL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor -o /usr/share/keyrings/nginx.gpg echo "deb [signed-by=/usr/share/keyrings/nginx.gpg] http://nginx.org/packages/ubuntu jammy nginx" | sudo tee /etc/apt/sources.list.d/nginx.list sudo apt update sudo apt install nginx -yPHP这边用的是Sury源,这个源由Ondřej Surý维护,是目前社区公认最可靠的PHP官方源之一:
sudo apt install software-properties-common -y sudo add-apt-repository ppa:ondrej/php -y sudo apt update sudo apt install php8.3-fpm php8.3-mysql php8.3-gd php8.3-mbstring php8.3-xml php8.3-curl -y这里要提醒一个容易犯的错:不要一股脑把所有PHP扩展都装上。扩展越多,内存占用越高,而且有些扩展之间还有依赖冲突。按项目实际用到什么装什么就行。比如WordPress必需php-mysql、php-gd、php-mbstring、php-xml、php-curl,Laravel还需要php-zip、php-bcmath。
3.3 MySQL 8.0的源配置
MySQL官方源同样值得单独配。Ubuntu系统自带的mysql-server包版本可能比较旧,而且维护方是Ubuntu社区,和Oracle官方的行为有些细微差异(比如配置文件路径、默认字符集设置)。直接用官方APT源:
wget https://dev.mysql.com/get/mysql-apt-config_0.8.29-1_all.deb sudo dpkg -i mysql-apt-config_0.8.29-1_all.deb sudo apt update sudo apt install mysql-server -y这个安装过程会提示你选择版本,默认选8.0即可。安装完成后MySQL会自动启动并加入开机自启,但默认的root账号密码是空的,需要马上做安全初始化(下一节详述)。
注意:
mysql-apt-config包的版本号会随官方更新而变,以官网实际下载页为准。国内服务器如果下载官方源速度慢,可以换用云厂商提供的镜像源,或者使用国内镜像站缓存。
4. Nginx安装与站点配置:从默认页到虚拟主机
Nginx装好之后,第一步不是急着配站点,而是理解它的运行机制。Nginx的配置文件核心逻辑是:nginx.conf是主配置文件,它通过include指令把/etc/nginx/conf.d/*.conf和/etc/nginx/sites-enabled/*加载进来。每个站点对应一个server块,浏览器根据域名和端口找到对应的server块——这就是虚拟主机的概念。
4.1 站点配置文件的推荐写法
我不建议在nginx.conf里直接写server块,这样维护性太差。正确的做法是在/etc/nginx/conf.d/下为每个站点建立独立的配置文件,命名规则建议使用域名,比如example.com.conf。这样一眼能看出来哪个文件对应哪个站点,也能避免"改了配置不知道改的是哪个站"的混乱局面。
一个标准的PHP站点配置长这样:
server { listen 80; server_name example.com www.example.com; root /var/www/example.com; index index.php index.html; access_log /var/log/nginx/example.com.access.log; error_log /var/log/nginx/example.com.error.log; 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/php8.3-fpm.sock; fastcgi_index index.php; } location ~ /\.(?!well-known).* { deny all; } }我这个配置里有几个地方是刻意这样写的,展开讲讲:
try_files $uri $uri/ /index.php?$query_string:这是PHP框架(Laravel、ThinkPHP、WordPress固定链接)都能正常路由的关键。把不存在的文件请求重写到入口文件,否则你访问任何一个伪静态URL都会404。fastcgi_pass unix:/run/php/php8.3-fpm.sock:用Unix Socket代替TCP(127.0.0.1:9000),避免了TCP握手开销,性能更好。但如果你打算把PHP-FPM和Nginx放在不同机器上,就必须改成IP:端口的形式。location ~ /\.(?!well-known).* { deny all; }:禁止访问所有以点开头的隐藏文件和目录(.git、.env等)。well-known目录是给Let's Encrypt证书验证用的,要放行。
配置写好后,用nginx -t检查语法,确认输出ok和successful后再执行systemctl reload nginx。永远不要改完配置直接重启nginx,应该用reload——它可以做到不中断现有连接的情况下加载新配置,对线上业务零影响。
4.2 网站根目录与权限的常见坑
很多人装完Nginx和PHP后,发现访问网页返回403 Forbidden,多半是目录权限没配好。要理解这个问题的本质:Nginx的运行用户是nginx(官方源安装时创建),PHP-FPM的运行用户是www-data,Web进程需要能读网站文件,PHP-FPM需要能写文件(比如上传图片、生成缓存)。
我推荐的做法是,把网站目录所有者设为www-data,Nginx worker进程用户也改成www-data,统一用户避免混乱:
mkdir -p /var/www/example.com chown -R www-data:www-data /var/www/example.com chmod -R 755 /var/www/example.com然后在nginx.conf里把user nginx;改成user www-data;。这样Nginx和PHP-FPM都以www-data身份运行,读写权限不会打架。上传目录(如wp-content/uploads)需要额外给775或770权限,具体看PHP-FPM的进程用户是否需要写。
我在实际项目中就遇到过:WordPress后台能登录但无法上传媒体文件,查了半天才发现是upload目录的所有者是root,PHP-FPM的www-data用户没有写权限,chown -R www-data:www-data之后立即恢复正常。
5. MySQL 8.0安装与安全加固的实操链路
MySQL装完后默认状态其实是"能用但危险"的。root用户密码为空、匿名用户可以访问、测试数据库还在——这些都是必须马上处理的安全隐患。MySQL自带了一个安全初始化脚本,但很多人过于依赖它,反而忽略了它不会自动处理的一些细节。
5.1 执行安全初始化脚本
sudo mysql_secure_installation运行这个命令后,它会交互式地让你设置root密码、删除匿名用户、禁止root远程登录、移除测试数据库、重新加载权限表。建议全部选Yes。
这里有个很多人困惑的点:为什么执行mysql -u root -p后用刚设置的密码还是登录不了?因为Ubuntu系统的MySQL 8.0默认使用auth_socket插件认证root用户,导致你只能用sudo mysql进入。解决办法是登录后用这条SQL将认证方式改为密码认证:
ALTER USER 'root'@'localhost' IDENTIFIED WITH caching_sha2_password BY '你的强密码'; FLUSH PRIVILEGES;caching_sha2_password是MySQL 8.0的默认认证插件,安全性比旧的mysql_native_password高。如果你的PHP版本低于7.4,可能不支持这种认证方式,需要改用mysql_native_password并在my.cnf里指定——但PHP 7.4都已结束官方支持了,强烈建议升级PHP而不是迁就老版本数据库插件。
5.2 创建业务账号:最小权限原则
用root账号跑业务是数据库运维第一大忌。原因很直接:就算你只是安全意识再强,总会有代码泄漏或者SQL注入的时候,一旦业务代码里的数据库账号是root,攻击者直接获得整个数据库的最高权限。正确的做法是为每个应用创建独立账号,只给这个应用需要的库的权限:
CREATE DATABASE IF NOT EXISTS example_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'example_user'@'localhost' IDENTIFIED BY '强密码'; GRANT ALL PRIVILEGES ON example_db.* TO 'example_user'@'localhost'; FLUSH PRIVILEGES;注意几点:
- 字符集用
utf8mb4而不是utf8。utf8在MySQL里实际上是utf8mb3,存不了Emoji和生僻字(比如某些语言的字符),utf8mb4才是真正的完整UTF-8。 - 排序规则用
utf8mb4_unicode_ci,对多数中文应用足够了。 - 只给
example_db.*的权限,绝对不要给*.*。后面的需求可以再根据实际情况追加权限,比如某些报表语句需要只读权限,可以单独建一个SELECT账号。
5.3 my.cnf调优:从默认配置到可用配置
MySQL 8.0默认的my.cnf其实是最保守的,不适合直接上生产。但调优有一条重要的原则:不要照抄网上的"万能配置",因为服务器的CPU、内存、磁盘类型都不一样,抄了可能更糟。我提供一个相对安全的基础配置,也是我用得最多的起步配置:
[mysqld] # 字符集 character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci # 存储引擎 default-storage-engine = InnoDB # InnoDB缓冲池大小:设置为物理内存的50%-70% innodb_buffer_pool_size = 1G # 日志文件大小 innodb_log_file_size = 256M # 连接数上限 max_connections = 500 # 默认时区 default-time-zone = '+08:00' # 慢查询日志(开启后便于排查慢SQL) slow_query_log = 1 slow_query_log_file = /var/log/mysql/slow.log long_query_time = 2 # 关闭符号链接支持,降低安全风险 symbolic-links = 0innodb_buffer_pool_size是最关键的参数。MySQL的InnoDB引擎在执行查询时优先在内存的buffer pool中找数据,如果数据量远大于buffer pool,就会频繁做磁盘IO,性能急剧下降。对于2G内存的机器,我建议设置512M(不能再高了,要留内存给系统和其他服务);4G内存的机器设置1G-1.5G比较合理。改完配置需要重启MySQL:
sudo systemctl restart mysql重启后确认MySQL正常运行:systemctl status mysql,再执行mysqladmin ping,看到mysqld is alive就说明正常。
6. PHP-FPM的安装、配置以及与Nginx的对接
PHP-FPM是整个LNMP链路里最灵活也最容易出问题的一环。它管理着一组PHP-CGI进程,负责接收Nginx转过来的PHP请求并执行。很多人只会在出问题时重启一下,但真正需要理解的是它的进程管理模式和参数含义。
6.1 进程管理方式选型:dynamic还是static
PHP-FPM支持几种进程管理模式,其中用得最多的是dynamic和static。配置文件在/etc/php/8.3/fpm/pool.d/www.conf,默认配置是dynamic模式。dynamic模式的意思是:初始按start_servers启动一定数量的进程,然后根据负载在min_spare_servers和max_spare_servers之间动态调整,最多不超过max_children。
static模式则是固定启动max_children个进程,不增不减。
我个人的经验是:
- 如果你的服务器是专用机器,流量比较稳定,用
static模式配合合理pm.max_children,性能和资源占用更可控; - 如果你的机器流量波动大,或者内存紧张,
dynamic模式更合适。
关键参数的关系如下:
| 参数 | 含义 | 建议 |
|---|---|---|
pm.max_children | 最多同时运行的PHP-FPM进程数 | 按内存÷单进程平均内存计算 |
pm.start_servers | dynamic模式下启动时的进程数 | max_children的1/4 |
pm.min_spare_servers | 空闲最少进程数 | max_children的1/4 |
pm.max_spare_servers | 空闲最多进程数 | max_children的1/2 |
pm.max_requests | 每个进程处理多少请求后自动重启 | 1000-5000 |
怎么计算max_children?先测每个PHP-FPM进程平均占多少内存:
ps -ylC php-fpm8.3 | awk '{x += $8; y += 1} END {print "Avg RSS (KB):", x/y}'比如平均每个进程占40MB,服务器可用内存是3GB(4G机器减去系统和MySQL占用),那么max_children可以设为3000MB/40MB ≈ 75。留出一些余量,设60比较稳妥。
6.2 PHP配置项:不是越多越好
/etc/php/8.3/fpm/php.ini里有几个参数值得按需调整:
memory_limit = 256M max_execution_time = 300 max_input_time = 60 post_max_size = 20M upload_max_filesize = 20M date.timezone = Asia/Shanghai这里要特别说下max_execution_time。对于一般的Web请求,300秒完全够用。但如果你想用PHP跑一些后台长任务(比如批量导入、报表生成),建议用set_time_limit()在代码里单独放开超时时间,而不是全局调大——全局调大等于让每个请求都有机会占用进程300秒,挂起请求一旦多起来,所有PHP-FPM进程全部被占满,网站就完全打不开了。
我见过好几起"PHP进程全部卡死"的事故,其背后的根源就是这个max_execution_time被调到了0(不限制),结果有请求卡在第三方API调用上回不来,进程越积越多,最后内存吃光,MySQL也被拖垮。
6.3 让Nginx与PHP-FPM真正对接起来
Nginx和PHP-FPM的对接只需要保证两点:一是php-fpm监听的地址和Nginx里fastcgi_pass的地址一致,二是配置里的SCRIPT_FILENAME参数正确传递。检查配置:
# 查看PHP-FPM监听地址 grep listen /etc/php/8.3/fpm/pool.d/www.conf默认配置是:
listen = /run/php/php8.3-fpm.sock对应的Nginx配置里就是:
fastcgi_pass unix:/run/php/php8.3-fpm.sock;修改完配置文件后,用php-fpm8.3 -t检查语法,然后systemctl reload php8.3-fpm。
为了验证PHP-FPM是否正常工作,在网站根目录创建/var/www/example.com/test.php:
<?php phpinfo();然后用浏览器访问http://你的域名/test.php,如果能看到PHP版本信息页面,说明整个链路已经通了。看到后记得马上删除这个文件——phpinfo()会暴露服务器PHP版本、模块列表等敏感信息,被攻击者收集到后能精准定位漏洞版本。
7. HTTPS证书部署与强制跳转
站点能访问了,PHP也执行了,接下来最重要的一件事:上HTTPS。2025年了,HTTP裸奔的站点不仅仅是被浏览器标"不安全"的问题,更关键的是数据传输全程明文。用户登录密码、支付信息、Cookie全部可以被中间人截获——这对任何有用户系统的站点都是不可接受的。
7.1 申请免费SSL证书:Let's Encrypt的坑与解法
用Let's Encrypt证书是最常见的方案,而且要配合它的自动化工具certbot使用。安装并签发:
sudo apt install certbot python3-certbot-nginx -y sudo certbot --nginx -d example.com -d www.example.comcertbot会自动修改你的Nginx配置,添加证书路径和SSL相关参数,非常方便。签发成功后会有输出,告诉你证书存放目录和到期时间。Let's Encrypt证书有效期只有90天,所以必须配置自动续期:
sudo certbot renew --dry-run这条命令模拟续期,用来验证自动续期流程能否跑通。certbot会在系统里生成一个/etc/cron.d/certbot定时任务,每天执行两次续期检查,到期前30天内会尝试续期。日常不需要再做额外配置,但建议每季度手动执行一次certbot renew --dry-run,防止意外情况。
7.2 配置HTTP强制跳转HTTPS
证书签好后,最后一步是让所有HTTP请求自动跳到HTTPS。certbot --nginx模式默认会询问是否帮你配置重定向,选了2就是帮你配。如果没配或者是自己手工签发的证书,在server块里加一段:
server { listen 80; server_name example.com www.example.com; return 301 https://$host$request_uri; }这段配置的意思是:所有通过80端口进来的请求,一律返回301永久重定向到相同域名和路径的HTTPS地址。这里有个细节:301是永久重定向,浏览器会缓存这个跳转结果。如果以后你取消了HTTPS(理论上不应该),用户浏览器会一直跳转不回来,缓存时间还很长,排查起来很困惑。所以如果只是临时测试,应该用302。
7.3 SSL配置的安全参数
certbot生成的配置已经很安全了,但如果你自己写SSL配置,要确认这几个关键参数:
ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; ssl_ciphers EECDH+AESGCM:EDH+aRSA;ssl_protocols只启用TLS 1.2和1.3,旧协议(TLS 1.0/1.1、SSLv3)都有已公开的漏洞,必须关闭。ssl_ciphers指定使用强加密套件,AESGCM是当前主流选择。
我遇到过一个问题:站点在Chrome上访问正常,但在某些老版本安卓浏览器上打不开。排查后发现那台手机的系统太老,只支持TLS 1.0,而我的服务器已经关闭了这个协议。考虑到互联网整体趋势,我是不会去迁就老设备的——建议你也做个取舍,旧设备用户比例如果很低,不值得为此降低全站安全性。
8. 上线前的检查清单与故障排查实录
最后这部分是我最想分享的,全是在实际项目中踩过的坑。很多人部署完LNMP,测试首页能打开就觉得万事大吉,结果上线后各种状况频出。我把上线前的检查项和一个经典故障排查过程分享出来。
8.1 上线前必做的10项检查
我整理了一份清单,每次帮客户部署完都会逐项检查:
- 站点HTTPS是否正常:用浏览器访问
https://example.com,确认证书无警告,地址栏显示锁形图标。 - HTTP是否跳转HTTPS:访问
http://example.com,确认能自动跳转。 - PHP版本信息是否暴露:检查
test.php是否已删除。 - MySQL远程连接是否关闭:
netstat -tlnp | grep 3306,确认只监听127.0.0.1。 - 数据库字符集是否为utf8mb4:登录MySQL执行
SHOW VARIABLES LIKE 'character_set_database'; - 文件权限是否安全:检查网站目录是否有不该存在的
777权限。 - 防火墙/安全组是否正确:确认80、443端口放行,其他敏感端口未开放。
- 日志是否正常记录:访问一次页面后,检查
/var/log/nginx/access.log和PHP-FPM日志是否有新记录。 - 系统时间是否准确:
date输出是否与当前时间一致。 - 磁盘空间是否充足:
df -h,至少保留20%空闲空间,否则日志和数据库一旦膨胀,系统容易进入只读状态。
8.2 经典故障:页面返回502 Bad Gateway的排查链路
502 Bad Gateway是LNMP部署后最常见的错误,含义是Nginx作为网关,从上游(PHP-FPM)没有拿到有效响应。它出现的原因可能很多,但排查思路是有套路可循的。我拿一个最近的案例来描述完整排查过程:
现象:访问https://example.com,浏览器显示502,页面下方是Nginx默认错误页。
第一步,我先看Nginx的错误日志:
tail -f /var/log/nginx/example.com.error.log日志里刷出了类似这行的内容:
connect() to unix:/run/php/php8.3-fpm.sock failed (111: Connection refused) while connecting to upstream这行日志非常关键,它直接告诉了我:Nginx想通过Unix Socket连接PHP-FPM,但连接被拒了。意味着什么?要么PHP-FPM没有在运行,要么Socket文件路径不对,要么PHP-FPM进程已经崩溃。
第二步,检查PHP-FPM状态:
systemctl status php8.3-fpm输出显示active (running),说明服务在跑。既然服务在运行,那问题可能出在Socket路径上。我用下面的命令确认PHP-FPM实际监听的路径:
ss -xl | grep php输出显示sock /run/php/php8.3-fpm.sock在监听,和Nginx配置里的路径是对上的。
第三步,我怀疑是权限问题。Unix Socket文件的权限如果拒绝nginx用户访问,也会报"Connection refused"或者"Permission denied"。检查Socket文件权限:
ls -l /run/php/php8.3-fpm.sock输出显示srw-rw---- www-data www-data,而我的Nginx worker进程用户早已改成了www-data,理论上可以访问。
到这里常规检查都没问题,我开始怀疑是不是PHP-FPM的可打开文件数限制被突破了。加载过大的脚本或者并发达到一定量,进程会拒绝新连接。查看当前PHP-FPM主进程的limits:
cat /proc/$(cat /run/php/php8.3-fpm.pid)/limits确实发现Max open files是1024,这个值在并发稍高的情况下很容易不够。解决方法是调整systemd服务配置:
mkdir -p /etc/systemd/system/php8.3-fpm.service.d cat > /etc/systemd/system/php8.3-fpm.service.d/limits.conf << EOF [Service] LimitNOFILE=65535 EOF systemctl daemon-reload systemctl restart php8.3-fpm重启后再次访问站点,502消失,页面正常打开。这个问题的根因是:PHP-FPM在处理大量请求时文件描述符耗尽,无法接受新连接,Nginx向上游请求时被拒绝。
8.3 另一个高发故障:WordPress安装时无法连接数据库
这个故障几乎每周都能在社区看到求助帖。现象是WordPress安装界面填写数据库信息后提示"Error establishing a database connection"。绝大多数情况下,不是网络问题,而是这三者中的一个:
- 数据库账号密码错误:这个不用多说,重新确认即可。
- 数据库账号权限没给对:比如账号能连接MySQL,但没有目标数据库的权限——回到第5.2节,检查GRANT授权的对象是否包含了WordPress建表需要的库。
- PHP连接MySQL时使用的Socket路径不对:这个最隐蔽。PHP的MySQL客户端默认用的Socket文件和MySQL实际监听的Socket可能不一致。用以下命令确认:
# 查看PHP中mysqli默认Socket路径 php -i | grep mysqli.default_socket # 查看MySQL实际监听的Socket mysql -e "SHOW VARIABLES LIKE 'socket';"如果两个路径不一致,在PHP-FPM的www.conf或者PHP配置里指定正确路径即可。我遇到过一台机器上PHP配置指向/var/run/mysqld/mysqld.sock,而MySQL实际监听的是/tmp/mysql.sock的,这就导致PHP无论如何都连不上数据库——这类问题不细心查是找不到的。
9. 我踩过多次之后的LNMP部署经验总结
部署LNMP这件事本身不复杂,复杂的是每一步背后的选择。这些年我见过太多人在踩同一批坑:版本乱选导致兼容性问题、权限配置错误导致500/403、MySQL配置没调导致服务器一高负载就卡死、日志不配置导致故障时无从下手。
我的体会是,部署LNMP时最该花时间的不是敲命令那一步,而是前期的规划和安装后的验证。你把软件源配好、用户权限理清、目录结构规划清楚,后面的运维会非常顺畅;反过来如果一开始就图省事,后面会不断为最初的草率买单。
还有一个小技巧想分享:建议每配置完一个模块,就用systemctl status确认服务状态正常,并配合日志文件看一眼输出。我习惯在/etc/nginx/conf.d/里把所有站点的配置文件都做完语法检查再统一reload,而不是一个个改完马上reload——这样即使出错也容易定位。部署LNMP不是冲一次就完的事情,而是让服务器进入一个可维护、可扩展的良性状态。希望你这次部署,也能一次到位。