
说实话一开始我对“nginx php-fpm”这套东西是有点抵触的。当年我最早接触PHP还是在Windows上用Apache跑改个配置重启一下全搞定。后来换到Linux服务器第一次自己手动搭LNMP环境就被502折腾到凌晨两点。你问我为什么不用宝塔宝塔确实方便但它把太多细节藏起来了一旦出了问题你连从哪里入手查都不知道只能重启重试。真正拿下一个环境是要搞清楚nginx和php-fpm是怎么配合的要把从安装到排错的整条链路都跑通。这篇文章我不打算给你堆一堆命令完事。我会从架构原理讲起然后带你完整走一遍编译安装、站点配置、多站点隔离最后把最常见的502、504、404、证书不生效这些问题的排查思路拆开揉碎。这不是写给新手看的“点鼠标教程”是给你一份可以照着撸、遇到问题知道怎么查的实战手册。1. nginx和php-fpm到底是怎么“说话”的——先搞懂架构再动手1.1 为什么nginx不处理PHP非要再拉一个php-fpm很多刚接触这套东西的人会有一个疑惑nginx既然是个Web服务器为什么不能像Apache那样直接处理PHP文件答案是设计思路不同。nginx的核心定位是高性能的静态文件服务和反向代理它处理PHP这类动态脚本时自己根本不认识PHP代码。它的做法是把请求按规则转发给一个专门执行PHP的外部进程等这个进程执行完把结果返回nginx再把结果原样交给浏览器。这个外部的PHP执行进程就是php-fpm。所以你可以把nginx理解成一个高效的前台接待员它负责接收客人的请求、判断这个请求该找谁而php-fpm是后台的厨房真正的PHP代码逻辑和数据库操作都在这里完成做完把成品递回给前台。这种前后台分离的架构好处是nginx可以同时代理很多种后端服务PHP请求给php-fpmJava请求给TomcatPython请求给GunicornNode请求给Node进程。nginx本身完全不需要关心后端是什么只要转发就行。这也解释了为什么很多公司会用nginx作为统一的入口网关。1.2 从CGI到FastCGI再到php-fpm这条路是怎么走过来的要理解php-fpm得先知道FastCGI这个协议。早期的Web服务器处理PHP走的是CGI通用网关接口。每来一个PHP请求服务器就要fork一个全新的PHP解析进程执行完立刻销毁。这个模型的缺点是显而易见的——进程创建和销毁的开销太大请求一多就扛不住并发能力极差。后来的改进版本叫FastCGI。它把“进程的生命周期”从“每个请求一个进程”改成“常驻的进程池”。FastCGI进程启动后一直存活等着Web服务器通过CGI协议把请求传过来处理完返回结果进程继续等下一个请求。这就是资源共享和反复利用性能比传统CGI强了不止一个量级。php-fpmPHP FastCGI Process Manager就是FastCGI协议的PHP实现它不光是一个协议实现还是一个进程管理器。它可以在启动时预先生成一批子进程根据负载情况动态调整进程数量还能配置每个进程能处理多少请求后自动回收防止长时间运行的PHP进程出现内存泄漏。这里要提一个关键点很多入门教程会让你在php-fpm里开启listen 127.0.0.1:9000然后nginx里配置fastcgi_pass 127.0.0.1:9000。这就是nginx和php-fpm之间的“对话通道”之一。这条通道可以是TCP端口也可以是Unix Socket文件后者在同一台机器上性能更好后面配置时会讲到。1.3 理解fastcgi_pass与listen的关系配置中最容易被你忽略的是把nginx的fastcgi_pass和php-fpm的listen这两个配置项对应起来。php-fpm的listen决定它在哪个地址上等待接收请求nginx的fastcgi_pass决定把解析PHP的请求往哪个地址送。这两者必须保持一致。如果你php-fpm监听的是127.0.0.1:9000nginx却写成了unix:/run/php-fpm/www.sock结果必然是502。这类低级错误占了新手报错原因的很大一部分排查的时候先看这两个配置是不是对得上。另一个容易踩的点是PHP-FPM默认是在本机监听如果你监听的是0.0.0.0:9000意味着服务器所有网卡上的9000端口都可以被连接。如果没有防火墙限制外部完全可以连进来有安全风险。我建议在同一台机器上用127.0.0.1或者unix socket跨机器通信才用TCP并配合防火墙白名单。2. 环境准备从编译PHP到启动php-fpm一步都不能省2.1 依赖安装与PHP编译参数选择现在动手装。我用的是阿里云的ECS系统是CentOS 7.9用编译安装的方式装PHP 7.4。注意CentOS 7自带的yum源里PHP版本很老直接用yum装容易出现扩展不兼容的问题所以源码编译是更可控的方案。先把编译需要的基础依赖装齐yum install -y gcc gcc-c make autoconf libxml2-devel openssl-devel curl-devel libjpeg-turbo-devel libpng-devel freetype-devel libzip-devel oniguruma-devel然后下载PHP源码并解压wget https://www.php.net/distributions/php-7.4.33.tar.gz tar xzf php-7.4.33.tar.gz cd php-7.4.33接下来是最重要的configure步骤。这里我给出一个生产环境常用的编译参数组合./configure --prefix/usr/local/php \ --with-config-file-path/usr/local/php/etc \ --with-fpm-userphp-fpm \ --with-fpm-groupphp-fpm \ --enable-fpm \ --with-mysqlimysqlnd \ --with-pdo-mysqlmysqlnd \ --with-mbstring \ --with-iconv \ --with-zlib \ --enable-zip \ --enable-opcache \ --enable-pcntl \ --with-openssl \ --with-curl \ --enable-sockets这里重点说几个参数的含义--with-fpm-user和--with-fpm-group指定php-fpm运行时的系统用户和用户组。这个非常关键它决定了PHP进程以什么身份去读文件。如果你的网站目录属于用户www但PHP以php-fpm用户运行就会出现权限不足的报错。我在生产环境一般统一创建一个php-fpm用户并把网站目录的属主设置为它或者直接让php-fpm用户加入www用户组通过组权限控制。--enable-fpm必须开启否则不生成php-fpm。--with-opcache宝塔这类面板默认是开着的别小看它PHP的字节码缓存对性能影响巨大真的是白拿的性能。--with-mysqlimysqlnd和--with-pdo-mysqlmysqlnd使用mysqlnd驱动比老的libmysqlclient快而且不需要额外装客户端库。编译安装make -j4 make install-j4代表4个核心并行编译机器核心多就写大点能省不少时间。编译如果报错大概率是缺了依赖看错误信息把对应的-devel包装上再重新configure即可。2.2 配置php.ini与php-fpm的启动管理安装完成后PHP本身不会自动生成配置文件需要手动复制cp php.ini-production /usr/local/php/etc/php.ini cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.confphp.ini里面我建议改几个值memory_limit调成256Mupload_max_filesize根据业务调成20M以上date.timezone设置成Asia/Shanghai不然日志时间不对排查问题的时候非常别扭。接着配置php-fpm的进程池。默认的是www.confcp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf打开www.conf核心配置项如下[www] user php-fpm group php-fpm listen 127.0.0.1:9000 pm dynamic pm.max_children 50 pm.start_servers 5 pm.min_spare_servers 5 pm.max_spare_servers 35 pm.max_requests 1000 request_terminate_timeout 30pm.max_children50意味着php-fpm最多同时50个进程每个进程大约占20-30MB内存50个就是1-1.5GB。这个数字要根据服务器内存来设定不是越大越好后面调优部分会详细说。为了使用systemd管理php-fpm创建一个service文件vim /etc/systemd/system/php-fpm.service内容如下[Unit] DescriptionPHP FastCGI Process Manager Afternetwork.target [Service] Typeforking PIDFile/usr/local/php/var/run/php-fpm.pid ExecStart/usr/local/php/sbin/php-fpm ExecReload/bin/kill -USR2 $MAINPID ExecStop/bin/kill -QUIT $MAINPID PrivateTmptrue [Install] WantedBymulti-user.target启动并设置开机自启systemctl daemon-reload systemctl start php-fpm systemctl enable php-fpm验证一下是否在监听ss -tlnp | grep 9000看到类似LISTEN 0 128 127.0.0.1:9000的输出就说明php-fpm起来了。2.3 安装nginx并确认FastCGI模块nginx的安装比PHP简单CentOS下可以直接用官方源rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm yum install -y nginx也可以源码编译但在CentOS下用官方yum源就够用了版本不算旧而且默认就带好了fastcgi相关模块。这一点你不需要额外操心nginx的fastcgi支持是编译期就内建的不需要额外的“插件”。启动nginxsystemctl start nginx systemctl enable nginx访问服务器IP看到Welcome to nginx!的页面环境骨架就算搭完了。下一步才是重头戏——把nginx和php-fpm真正连起来。3. nginx站点配置location匹配、路径传递与rewrite规则3.1 一份完整可用的server配置块nginx的站点配置一般放在/etc/nginx/conf.d/目录下一个域名一个文件后缀为.conf。一个最基础但完整的PHP站点配置长这样server { listen 80; server_name www.example.com example.com; root /var/www/example; index index.php index.html; access_log /var/log/nginx/example.access.log; error_log /var/log/nginx/example.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 127.0.0.1:9000; } location ~ /\.(?!well-known).* { deny all; } }几个关键点逐个说。3.2 fastcgi_params里最容易忽视的一个参数include fastcgi_params这一行把nginx自带的/etc/nginx/fastcgi_params文件内容引入进来它定义了REQUEST_METHOD、QUERY_STRING、HTTP_USER_AGENT等FastCGI标准参数。但其中没有SCRIPT_FILENAME所以绝大多数教程都会让你手动加一行fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;这一行的含义是告诉php-fpm“你要执行的PHP文件在服务器上的绝对路径”。如果少了它php-fpm就不知道解析哪个文件直接返回空白页或者404。这里有个很容易踩的坑$document_root变量取自root配置项但如果你在location ~ \.php$里面又写了新的root就会覆盖外面那个。我见过一个案例外层root是/var/www/a内层location里被同事写成root /var/www/a/public结果所有PHP文件的路径都指向了/var/www/a/public/xxx.php真实文件却在/var/www/a/xxx.php全部404而且日志里看不出来因为路径被拼歪了。所以root原则上只在server层写一次。3.3 WordPress和ThinkPHP这类框架的伪静态处理现在主流PHP框架基本都使用单一入口路径是/index.php/controller/action/params的形式你需要把除静态文件之外的所有请求都交给index.php处理。nginx里最通用的是这行使用try_files做路由伪静态location / { try_files $uri $uri/ /index.php?$query_string; }这个配置的含义是寻找请求对应的真实文件如果找不到就重写到/index.php把多余的路径作为QUERY_STRING传给PHP。WordPress、ThinkPHP、Laravel都可以用这个方案不需要额外的rewrite。但有些框架需要PATH_INFO支持比如ThinkPHP的5.0版本会解析URI中的pathinfo部分。这时候会遇到一个经典问题nginx默认不会把路径信息传给PHP。解决办法是使用fastcgi_split_path_infolocation ~ ^/index\.php($|/) { fastcgi_split_path_info ^(.\.php)(/.*)$; fastcgi_pass 127.0.0.1:9000; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_param PATH_INFO $fastcgi_path_info; }手动配置过的同学应该都有体会PATH_INFO突然失效比502还难排查因为页面能打开但路由解析不到控制器全都是404。这个问题我建议在配置阶段就把fastcgi_split_path_info写上不要等出bug再回头查。4. 进阶场景多站点、多版本PHP与开发环境域名映射4.1 用多个php-fpm pool隔离站点生产环境中一台服务器上部署多个PHP站点很常见但不同业务可能需要不同的PHP配置甚至不同版本。这时候如果在同一个www池里跑所有站点一个站点的慢请求或者崩溃会波及全站。php-fpm天然支持多pool。你可以新建/usr/local/php/etc/php-fpm.d/site2.conf内容如下[site2] user site2 group site2 listen 127.0.0.1:9001 pm dynamic pm.max_children 20 pm.start_servers 3 pm.min_spare_servers 2 pm.max_spare_servers 10 pm.max_requests 500 php_admin_value[memory_limit] 128M php_admin_value[upload_max_filesize] 10M然后nginx里就把对应的fastcgi_pass 127.0.0.1:9001这样site2的处理进程和主站是隔离的内存限制、超时时间都可以单独控制。如果要做多版本PHP共存比如一个站要用PHP 7.4另一个站要用PHP 8.2那你就得编译两个PHP版本到不同目录比如/usr/local/php74和/usr/local/php82。两个php-fpm分别监听不同端口或者不同socketnginx里按server块指定不同的fastcgi_pass。我实际部署过一套双PHP版本的环境nginx配置大概逻辑如下server { listen 80; server_name old.example.com; root /var/www/old_site; location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9001; # PHP 7.4 } } server { listen 80; server_name new.example.com; root /var/www/new_site; location ~ \.php$ { include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9002; # PHP 8.2 } }这样旧站迁移期间两边都能稳定跑上线验证没问题再切流量风险小很多。4.2 一机多站server_name与listen的配合多站点第二个维度是nginx层的隔离。nginx支持一个进程监听多个端口、多个域名关键是server_name和listen的组合要分清楚不同域名共用80端口各server块只写server_name不同listen 80相同。不同应用用不同端口比如开发环境里server1用listen 8080server2用listen 8081。一个很常见的坑是很多人会在一个server块里既想监听80又想监听443然后忘了把PHP的location写进两个块结果HTTPS访问时所有PHP文件都变成下载或者404。更稳妥的做法是把共用配置用include拆出来然后443和80各自引用避免复制粘贴时漏掉。4.3 本地开发环境自定义域名与小端口转发的组合本地开发或者虚拟机环境下一个非常实用的技巧是用自定义域名配合nginx的多端口配置。比如你在VMware虚拟机里跑了一套nginx php-fpm宿主机Windows里想访问虚拟机里的两个站点。这时候完全不需要改本地hosts绑端口可以这样做第一步修改宿主机C:\Windows\System32\drivers\etc\hosts192.168.1.101 site1.local 192.168.1.101 site2.local第二步在虚拟机的nginx里写两个server块都监听80端口server_name分别对应这两个自定义域名。第三步开发时访问http://site1.local和http://site2.local即可。如果还想模拟线上多端口访问比如不依赖域名、直接用http://192.168.1.101:8081访问第二个站点就在nginx里再写一个server块listen 8081;。这样开发环境的站点隔离方式就和线上保持一致不用为了本地环境去改业务代码里的接口地址。这类开发环境里甚至可以直接用nginx的proxy_pass把某个路径转发给本地其他端口跑的服务。比如你把前端项目跑在5173端口后端PHP在9000端口开发的时候域名指向nginxnginx直接把/api/前缀的请求转发到后端的PHP入口。这样你的浏览器永远只面对一个端口规避了大量跨域和端口混用带来的调试问题。5. 常见问题排查实战从502到404再到证书不生效5.1 502 Bad Gateway完整排查链路502是nginx php-fpm环境里最常见的错误网上99%的回复都是“重启一下php-fpm”。重启确实能好一阵子但治标不治本。我来写一版完整的排查链路你不妨直接照着走。第一步确认php-fpm进程存活ps -ef | grep php-fpm如果进程都没了去看php-fpm错误日志一般在/usr/local/php/var/log/php-fpm.log。如果是被OOM Killer杀掉日志里会有memory allocation failed/var/log/messages里也有记录。内存不足就要降低pm.max_children或者加内存。如果进程正常进入第二步确认监听端口ss -tlnp | grep 9000如果端口没有监听说明php-fpm的listen配置有问题或者服务根本没有起来。注意如果你改成unix socket了这里要看socket文件是否存在ls -l /run/php-fpm/www.sock第三步直接测试FastCGI接口是否正常响应。注意不能用curl http://127.0.0.1:9000这种方式因为php-fpm不接受普通HTTP协议需要用一个FastCGI客户端。最简单的办法是临时创建一个测试PHP文件并访问如果502依旧继续看nginx日志tail -100 /var/log/nginx/error.log常见错误和对应原因我整理成了表格错误日志关键内容原因解决办法connect() failed (111: Connection refused)nginx连不上php-fpm的端口/socket检查fastcgi_pass和listen是否一致php-fpm是否启动connect() failed (13: Permission denied)进程权限不足无法访问socket调整socket的listen.owner/group或让nginx用户加入php-fpm用户组Primary script unknownSCRIPT_FILENAME路径不对检查root和fastcgi_param SCRIPT_FILENAME配置upstream sent too big header while reading response header上游返回的头太大调大fastcgi_buffer_size和fastcgi_buffers第四步也是很多老手会忽略的一步查看php-fpm日志。很多时候nginx日志里什么都没有只有php-fpm的日志里会记录具体是哪个PHP脚本执行超时或报错。那种“重启一下就正常但过几天又502”的情况多半和pm.max_requests有关。当某个worker处理请求数超过这个值就会重启如果频繁重启说明PHP代码里有内存泄漏或者某个脚本不稳定。把pm.max_requests保留在500-1000是合理的不设置的话进程永远不回收内存只涨不降。5.2 504 Gateway Timeout超时参数与慢请求定位504意味着nginx等php-fpm处理超时。nginx在转发FastCGI请求时有三个超时参数fastcgi_connect_timeout 5s; fastcgi_send_timeout 60s; fastcgi_read_timeout 60s;connect_timeout是建立连接的超时send_timeout是把请求数据发给php-fpm的超时read_timeout是等待php-fpm返回结果的超时。PHP里如果一个脚本执行超过60秒还没结束nginx就会报504。排查504先要看是偶发还是持续如果某个接口一直504基本就是那个接口的SQL或外部请求太慢跟nginx配置关系不大。这时候可以用php-fpm的慢日志定位slowlog /usr/local/php/var/log/php-fpm-slow.log request_slowlog_timeout 5s加上这两行后超过5秒的脚本会记录到慢日志里精确到函数调用栈。我经常用它发现某条查询没走索引、某个curl请求外呼超时这类问题。另外注意如果服务器上有大量PHP-FPM进程卡死常见原因还和pm.max_children不足有关——所有子进程都在处理慢请求新请求排队等进程等待时间超过request_terminate_timeout就被杀掉于是表现为大量504。这时候加大max_children只能缓解一时真正的解决思路是把慢请求单独分流到专门的高超时pool避免拖垮主站。5.3 访问PHP文件变成下载或源码泄漏的两种原因访问xxx.php浏览器直接下载这个文件或者源码明文展示在页面上这种现象的本质是nginx没有把.php请求交给php-fpm而是把它当成普通文件发送给了客户端。造成这个结果的原因基本就两个第一种location ~ \.php$这个正则没匹配上。比如你配置文件里写的是location \.php$少了~nginx就当成普通前缀匹配永远匹配不上。或者文件名里有大小写问题你的location写的是\.Php$而实际文件是.php。第二种server块里没有include fastcgi相关配置或者nginx配置里压根没有PHP的location块。我见过一个案例公司把nginx配置改成了只监听location /统一指向一个前端静态目录然后所有PHP文件就真的成了静态文件被下载了。另外还有安全隐患的场景如果让nginx允许上传目录里的PHP文件被直接访问执行攻击者上传一个shell.php后就能直接执行任意命令。我强烈建议对所有上传目录单独加配置location ~* /(upload|images|static)/.*\.php$ { deny all; }5.4 替换SSL证书不生效浏览器缓存和配置位置的坑我接手过一个项目客户说“换了新证书访问还是旧的”把nginx配置导出来看了又看证书文件确实已经换成新的了但浏览器始终显示的旧证书信息。第一层原因通常是浏览器缓存了证书信息。Chrome对证书的缓存时间不短排查时先用无痕窗口打开如果无痕下是新的就说明浏览器缓存问题换个浏览器或者清缓存即可。第二层原因nginx没真正加载新配置。很多人把证书文件替换了但只执行了nginx -t检测语法没有执行nginx -s reload。reload才能让nginx重新读取证书文件到内存。你们可以做一个测试直接把证书文件内容改成一个明显错误的内容正常nginx -t不报错nginx -s reload后访问必然失败。这时候你就能确认证书是不是生效了。第三层原因是证书链不完整。你在申请证书的平台上通常能下载一个包含证书本体和CA证书的链文件。如果只把网站证书填进去没把中间证书chain也合进去PC浏览器可能因为信任链断掉而报NET::ERR_CERT_AUTHORITY_INVALID而不是显示证书内容。nginx配置里常见的做法是ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/private.key;fullchain.pem就是证书本体和中间证书合并后的文件这是我记得最早踩的坑之一。还有一点容易被忽略如果证书配置在旧版本nginx上ssl_protocols写得太老浏览器不认。比如只开了TLSv1和TLSv1.1Chrome和Firefox都已经默认禁用这些协议你会看到连接层面就失败了看起来像证书没生效。把协议调成TLSv1.2 TLSv1.3能解决大部分这类兼容问题。5.5 文件权限与路径拼接导致的“伪404”“伪404”是我自己起的叫法——nginx日志里明明显示返回404但文件确实存在。最常见的原因是PHP-FPM进程用户没有读取文件的权限。假设你的网站目录是/var/www/example属主是root:root权限是750。nginx的worker用户是nginxphp-fpm用户是php-fpm这两个用户都不在root组里自然读不到文件。这个时候你知道什么感受吗——静态文件能打开因为nginx本身读取静态文件但PHP文件全部404而且你检查document_root路径也没问题很容易被绕进去。解决办法是统一网站目录的权限策略。我常用的做法chown -R php-fpm:php-fpm /var/www/example chmod -R 755 /var/www/example如果你不想PHP进程拥有整个网站的写权限可以把目录属主设为nginx:nginx然后让php-fpm用户加入nginx组。具体怎么设计根据你业务的写入需求来但记住一个原则不需要写权限的目录一律不给写权限。还有一种情况是fastcgi_param SCRIPT_FILENAME里的路径拼接方式导致404比如你写的不是$document_root$fastcgi_script_name而是写死了/home/www/example$fastcgi_script_name一旦以后改了root就会引出一堆莫名其妙的404。排查这类问题最好的工具是打开nginx debug级别的错误日志或者临时在PHP入口文件里打印$_SERVER[SCRIPT_FILENAME]看实际值到底是什么。6. 稳定运行的经验日志、性能参数与维护习惯6.1 日志怎么配才能快速定位问题环境跑起来之后日志的合理配置决定了出问题时你能不能在5分钟内定位。我看到太多服务器上nginx的access_log和error_log全都被注释掉出问题了只能干瞪眼。nginx的日志格式建议自定义加上request_time和upstream_response_timelog_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for rt$request_time uct$upstream_connect_time uht$upstream_header_time urt$upstream_response_time;加了upstream_response_time之后你一眼就能看出请求耗时是花在nginx层还是后端PHP层。如果rt很大但urt很小说明nginx自身在处理这个请求时卡住了通常是等待锁或者磁盘IO问题。如果urt很大问题就在PHP代码或数据库。php-fpm日志也别忘了配。我见过有人网站访问慢得离谱一查php-fpm的request_slowlog_timeout完全没开根本不知道哪个脚本在拖后腿。建议所有新环境都先加慢日志配置有备无患。6.2 pm参数调整与CPU/内存观察pm参数是php-fpm调优的核心我用一个表格来帮大家建立直观感受参数含义设置原则pm.max_children最大子进程数每个PHP进程内存约30MB内存总量/30约等于可设上限pm.start_servers启动时生成的进程数取决于常规并发量pm.min_spare_servers最小空闲进程数防止请求突增时现场建进程pm.max_spare_servers最大空闲进程数超过就回收避免空跑占用内存pm.max_requests每个子进程最多处理请求数500-1000达到后自动重启防内存泄漏我在一台2G内存的机器上一般设置pm.max_children40左右然后观察free -m的可用内存。如果内存长期告急就得调小如果还有富余但出现了max_children reached的日志那就往上调。另外要看的是CPU。top -Hp php-fpm进程号可以看到每个worker的CPU占用。如果是PHP进程持续占用CPU说明脚本有死循环或者某条SQL没走索引如果CPU占用都不高但页面很慢问题可以转向数据库连接数、外部API调用这些环节。6.3 升级PHP或切换版本时的操作顺序PHP升级切换是我见过翻车最多的操作。很多人在跑着的环境上直接替换二进制文件或者改了systemd里的路径就reload结果老进程还在跑旧版本、新请求哈去了新版本两者互相干扰。我的建议是操作顺序按这几步来新版本PHP先编译到新目录比如/usr/local/php82。把旧版本的php.ini导出一份按新版本差异调整扩展路径和后缀名PHP 8对某些旧扩展不兼容这个必须提前检查。先手动启动新版本的php-fpm监听一个新端口或新socket用curl带上Host头或者X-Forwarded来验证新环境。验证通过后修改nginx对应server块的fastcgi_pass指向新版本然后nginx -s reload。观察一段时间的access_log、error_log和业务监控确认没有兼容问题再停掉旧版本的php-fpm。照这个流程做即使出问题也只需要把nginx的fastcgi_pass改回去就能秒级回滚不会造成长时间的业务中断。还有一个小细节PHP 8和PHP 7的配置项里mysqli.default_socket这类参数的默认值如果不对会导致连数据库失败。切换版本后务必跑一遍连接数据库的冒烟测试别只看首页能开就完事。最后说回我自己的体会。搭建一套稳定可用的环境最重要的不是某个配置项而是你对这条链路里每个节点的理解。nginx只做转发php-fpm只负责执行它们之间的桥梁就是FastCGI协议出问题永远先分清是“连不上”还是“执行不了”。曾经我花了一下午排查502最后发现是网上教程里的socket路径和实际编译路径差了一个目录。说白了配置这个事慢就是快先把原理吃透再动手操作坑就少一大半。