LAMP与LNMP环境搭建、PHP-FPM配置与故障排查实践 1. LAMP与LNMP到底是什么四个字母的分工逻辑干运维这行不管你后来是做容器、搞云原生还是转头写业务代码早晚会撞上这么一个场景扔给你一台刚装好的Linux机器扔给你一个压缩包说把站跑起来。这时候你脑子里蹦出来的第一组词八成就是LAMP和LNMP。这两个缩写在招聘要求里、在教程目录里、在老工程师的口头禅里反复出现但真要让你说清楚它们分别是什么、差在哪不少人会卡壳。这篇就沿着这个系列一直往下写把这两套经典组合从架构思路到落地实施完整走一遍顺带把我在实际部署里踩过的坑都摆出来。先把定义摊平。LAMP是Linux、Apache、MySQL现在多数场景已经换成MariaDB、PHP四个组件的首字母缩写LNMP则是把Apache换成Nginx其余三个不变PHP通常以PHP-FPM的形式独立运行。它们都不是某一个具体的软件而是一套约定俗成的Web运行环境组合方案解决的是同一件事让一个用PHP写的动态网站能够被外面的浏览器访问到并且能读写数据库。适合看这篇的人分三类刚学Linux、想搞明白服务器上到底该装什么的入门者手里有台机器、要部署一套内部系统或小站点的开发者以及想把基础打牢、准备往运维方向走的人。前两类可以照着步骤抄第三类更值得看看后面关于参数计算和故障排查的部分。1.1 四个字母各自承担什么职责把一套Web环境想象成一家餐馆四个组件的位置就很清楚了。Linux是地基和物业负责管理硬件资源、进程调度、文件权限、网络通信所有东西都跑在它上面它不直接对外服务但少了它什么都立不住。Apache和Nginx是前台接待负责监听端口、接收HTTP请求、解析URL、把静态文件直接递出去遇到需要动态处理的请求再转交给后厨。PHP是后厨的厨师拿到请求参数执行一段业务逻辑代码该查库查库该算账算账最后把做好的HTML端出来。MySQL是仓库所有用户数据、订单、文章内容都存在这里厨师要用料就得来这儿取。这个分工带来的直接好处是解耦。前台接待忙不过来可以加人横向扩展Web服务器后厨忙不过来可以加厨师增加PHP-FPM进程数仓库压力大可以单独优化索引或者上读写分离。每个环节都能独立调整不用牵一发动全身。理解了这一层后面装软件的时候你就不会觉得是在机械地执行命令而是知道自己正在给这家餐馆添置哪一件设备。还有一点值得强调PHP和Web服务器之间是需要接口的这是新手最容易糊掉的地方。早期的做法是把PHP编译成一个Apache模块mod_phpApache收到PHP请求就自己调用这个模块执行相当于前台自己兼职炒菜。后来的做法是把PHP做成一个独立的服务进程PHP-FPMWeb服务器通过FastCGI协议把请求转发给它相当于前台把单子递给后厨。后一种方式职责更清晰、资源隔离更好也是现在Nginx场景下的唯一选择。1.2 两套组合的核心差异在哪很多人以为LAMP和LNMP的差别只是换个Web服务器这个理解不算错但太浅了。真正的差异藏在并发模型和请求处理方式里。Apache最经典的工作模式是prefork一个进程处理一个连接进程之间互相独立。这种模型稳定、兼容性好任何一个模块出问题最多拖垮一个进程但代价是内存开销大——每个进程都要占一份独立的内存空间。后来Apache也发展出worker和event模式用多线程和事件驱动来提升并发能力但历史包袱在那儿配置起来也更绕。Nginx从设计之初就是事件驱动、异步非阻塞的架构一个worker进程用 epoll 这样的机制同时盯着成千上万个连接谁有数据来了就处理谁没有就挂着不占资源。同样是2GB内存的机器Apache可能撑几百个并发连接就吃紧了Nginx能轻松扛住几千个。这个差异在什么场景下最明显静态资源多、长连接多、并发量高的场合Nginx的优势非常突出比如图片站、下载站、API网关、反向代理层。而Apache的优势在于模块生态成熟、.htaccess 这种目录级配置灵活、和老系统兼容性好很多早期的CMS、商城系统默认就按Apache的规则写的。所以企业里常见的做法是Nginx放前面做反向代理和静态资源处理Apache放后面跑老应用两套并存各干各擅长的事。1.3 新手到底该先学哪一套我的建议是两套都要动手搭一遍但先搭LAMP。理由很实际——LAMP的组件之间耦合更紧mod_php或者proxy_fcgi的配置相对直白装完之后你更容易看清楚请求是怎么从浏览器走到PHP再走到数据库这条链路。等你把这条链路摸熟了再切到LNMP你会发现只是把前台换了个更高效的接待后厨和仓库的运作逻辑完全没变学习成本一下子就降下来了。反过来如果一上来就搞LNMP你会同时面对Nginx的配置语法、PHP-FPM的进程池、socket权限、FastCGI参数这一堆新概念出问题的时候根本分不清是哪一层挂的。我在带新人的时候见过太多这种情况页面报502人直接懵了因为他压根不知道502是Web服务器在说我联系不上后厨还以为是代码写错了。提示不要跳过LAMP直接上LNMP。花两三个小时把Apache那套跑通后面省下的排查时间远超这点投入。2. 动手之前的环境规划与选型真正开始装之前有几件事必须先定下来否则装到一半改主意会非常痛苦。这一节讲的是装之前的思考看着不如敲命令爽但它决定了后面整套环境好不好维护。2.1 系统版本与软件源的选择Linux发行版大致分两大阵营RHEL系包括各种衍生版本和Debian系包括Ubuntu。两边的差异主要在包管理器和默认配置路径上。RHEL系用 yum 或 dnf配置文件习惯放在 /etc/httpd/ 和 /etc/php.iniDebian系用 apt配置分散在 /etc/apache2/、/etc/nginx/、/etc/php/版本号/ 这些目录下结构更模块化。选哪个看你所在团队的技术栈。如果公司服务器清一色RHEL系那就跟着用别为了个人喜好制造维护割裂。如果是自己练手我倾向于推荐Debian系原因是它的软件源更新更及时装新版本PHP和数据库的时候不用到处找第三方源apt 直接就能拿到比较新的版本少折腾很多。软件源这块有个坑要提前说。默认的官方源里PHP版本往往偏旧比如系统自带的是7.x而你的应用要求8.x。这时候有两条路一是添加第三方软件源比如各发行版社区维护的PHP仓库二是自己编译。优先选前者因为包管理器装的软件有统一的管理入口升级、卸载、查依赖都方便。自己编译的PHP后期想装个扩展就得重新编译一遍维护成本高得离谱除非你有明确的定制需求比如要打补丁、要裁剪模块否则别碰。还有一点装之前先确认系统时间和时区。数据库的时间戳、日志的时间、证书的有效期校验全依赖这个。用timedatectl看一眼时区不对就设成 Asia/Shanghai时间不同步就配个NTP服务。这个动作只要十秒钟但能帮你省掉后面排查为什么订单时间差了8小时的几个小时。2.2 编译安装还是包管理器安装这个问题我被问过无数次。结论很明确生产环境用包管理器学习阶段可以试一次编译但别把编译当成常规手段。包管理器安装的优势是三件事依赖自动解决、升级路径清晰、卸载干净。你执行一条安装命令它会把PHP依赖的libxml、curl、gd这些库一并装上版本还是相互匹配的。哪天要升级一条命令搞定出问题还能回滚。而编译安装呢你得自己解决依赖、自己指定编译参数、自己处理扩展装完之后系统里多了一堆没有登记的文件时间一长自己都忘了装在哪。那为什么还说学习阶段可以试一次编译因为编译过程能让你看到PHP到底依赖哪些库、有哪些可选模块。执行./configure --help那一长串输出你会第一次意识到原来PHP的图像处理、压缩、加密功能都是独立模块要什么得自己开。这个认知对后面配置 php.ini、排查某个函数不存在这类问题极有帮助。表格对比一下更直观维度包管理器安装源码编译安装上手难度低一条命令高需处理依赖与参数依赖管理自动手动升级维护简单可回滚复杂需重新编译定制能力受限于官方包完全自由适用场景生产环境、日常开发特殊需求、学习原理2.3 端口、目录、运行用户的前置规划很多人装完环境之后发现端口冲突、权限混乱根子都在于装之前没规划。我把需要提前定下来的东西列成一张表照着填一遍后面能少走很多弯路。项目推荐规划说明Web服务端口80 / 443标准端口非必要不改数据库端口3306仅监听 127.0.0.1数据库绝不对外暴露PHP-FPM通信unix socket 优先同机通信比TCP更快更安全网站根目录/data/www/站点名统一放 /data 下便于备份迁移日志目录/data/logs/站点名与代码分离避免占满系统盘Web运行用户nginx 或 www-data专用低权限用户禁用root运行数据库运行用户mysql系统安装时自动创建关于运行用户这里必须多说一句。Web服务和PHP-FPM绝对不能以root身份运行。一旦应用存在文件上传或者命令执行的漏洞root权限意味着攻击者可以直接接管整台机器。正确的做法是创建一个专用的低权限用户网站目录的属主设成这个用户权限控制在755目录和644文件只有需要写入的目录比如上传目录、缓存目录、日志目录才单独给写权限。这个原则后面在实操部分还会反复提到。网站根目录放 /data 而不是 /var/www是我个人的习惯理由有两个一是 /var 在某些发行版里是独立分区容量可能不大日志和上传文件一多就容易撑爆二是把业务数据集中在一个顶层目录下做快照备份和迁移的时候一条命令就能打包走不用满系统找。3. LAMP落地ApacheMySQLPHP的完整搭建规划清楚了开始动手。这一节按装Web服务器→装数据库→装PHP→打通三者的顺序来每一步我都会说清楚这条命令在干什么、为什么要这么配。命令以RHEL系为主Debian系的差异我会在关键处标注出来。3.1 Apache的安装与核心配置安装本身没什么好说的一条命令# RHEL系 dnf install -y httpd # Debian系 apt install -y apache2装完先别急着启动把配置文件看一遍。RHEL系的主配置在 /etc/httpd/conf/httpd.confDebian系在 /etc/apache2/apache2.conf另外还有 sites-available 和 sites-enabled 两个目录用来管理站点。这个设计差异要留意Debian系采用可用配置和启用配置分离的模式你在 sites-available 里写好配置再软链接到 sites-enabled 才会生效好处是可以随时禁用某个站点而不删配置文件。核心参数里最需要关注的是ServerName和Directory 段的权限控制。ServerName 不设的话启动时会报一条警告虽然不影响运行但日志里一直刷着烦人。Directory 段决定了哪些目录可以被访问、允不允许跟随软链接、允不允许 .htaccess 覆盖配置。生产环境里我一般把 AllowOverride 设为 None把配置集中写在主配置文件里原因是 .htaccess 每次请求都会重新读取有性能损耗而且散落在各个目录里的配置极难审计。Apache的工作模式MPM需要单独提一下。用httpd -V或者apache2ctl -V能看到当前用的是哪个。prefork 是传统多进程模型worker 是多进程多线程event 是事件驱动。如果你打算用 mod_php 方式跑PHP必须用 prefork因为 mod_php 不是线程安全的。如果打算用 PHP-FPM proxy_fcgi那 event 模式性能最好。切换MPM在RHEL系需要装对应的包比如httpd默认带eventprefork 需要额外配置Debian系用a2dismod/a2enmod切换。启动并设置开机自启systemctl enable --now httpd # RHEL系 systemctl enable --now apache2 # Debian系浏览器访问服务器IP看到默认欢迎页就说明Web服务这层通了。这一步一定要先验证再往下走不然后面出问题你没法判断是Apache的事还是PHP的事。3.2 数据库的安装与初始化安全设置数据库这块现在新装环境我基本都推荐 MariaDB 而不是 MySQL 原版原因是它完全兼容MySQL协议、开源协议更宽松、社区维护活跃装起来也更省事。当然如果团队已有MySQL的技术积累继续用也没问题操作命令基本一致。dnf install -y mariadb-server # RHEL系 apt install -y mariadb-server # Debian系 systemctl enable --now mariadb装完之后第一件事是跑安全初始化脚本mysql_secure_installation这个交互式脚本会引导你做几件事设置root密码、删除匿名用户、禁止root远程登录、删除test测试库、重载权限表。这五项每一项都不是可选项——匿名用户是历史上真实出过问题的攻击入口test库任何用户都能访问root能远程登录等于把大门钥匙挂在门把手上。在生产环境里这五步一步都不能省。接下来建业务库和业务用户。原则是一个应用一个独立用户只授予它需要的权限绝不直接用root连接CREATE DATABASE app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER appuserlocalhost IDENTIFIED BY 这里换成强密码; GRANT SELECT, INSERT, UPDATE, DELETE ON app_db.* TO appuserlocalhost; FLUSH PRIVILEGES;字符集这里要特别注意用 utf8mb4 而不是 utf8。MySQL里的utf8实际只支持最多3个字节存不了emoji和部分生僻汉字utf8mb4才是真正的完整UTF-8。这个坑不知道坑过多少人上线之后才发现用户昵称里的表情存进去变成问号改起来要动表结构、动连接配置、动代码非常麻烦。还有一个极其经典的坑必须提前说appuserlocalhost和appuser127.0.0.1在MySQL眼里是两个不同的用户。前者走的是unix socket连接后者走的是TCP连接。你在PHP代码里写hostlocalhost实际走socket写host127.0.0.1走TCP。如果只授权了其中一个另一个就会报 Access denied。我第一次遇到这个问题的时候盯着密码核对了半小时最后才发现是host不匹配。3.3 PHP与Apache的对接方式PHP的安装分两种对接方式我建议直接学 proxy_fcgi PHP-FPM虽然比 mod_php 多一层配置但它是现在的主流做法而且和后面LNMP那套的知识是通用的学一次两处用。# 安装PHP及常用扩展 dnf install -y php php-fpm php-mysqlnd php-gd php-mbstring php-xml php-curl php-zip装完先看PHP-FPM的服务名RHEL系叫 php-fpmDebian系按版本号命名比如 php8.2-fpm。启动它systemctl enable --now php-fpm然后在Apache里启用代理模块并配置转发。RHEL系在 httpd.conf 里加FilesMatch \.php$ SetHandler proxy:unix:/run/php-fpm/www.sock|fcgi://localhost /FilesMatchDebian系用a2enmod proxy_fcgi然后a2enconf php8.2-fpm更方便它会自动生成配置文件。这里的关键是socket路径必须和PHP-FPM配置里的listen保持一致。PHP-FPM的池配置在 /etc/php-fpm.d/www.confRHEL系或 /etc/php/版本/fpm/pool.d/www.confDebian系里面有一行listen /run/php-fpm/www.sock。两边对不上Apache就找不到后厨报503或者502。同时运行用户也要对齐。PHP-FPM默认以 apache 用户运行RHEL系如果你的网站目录属主是 nginx那PHP就写不了文件。改 www.conf 里这几行user apache group apache listen.owner apache listen.group apache listen.mode 06603.4 验证从phpinfo到连接数据库配置改完先检查语法再重启这是好习惯apachectl configtest # 或 apache2ctl configtest systemctl restart httpd php-fpm在网站根目录放一个测试文件?php phpinfo();访问能出来那一大页紫色信息表说明Apache和PHP通了。这个 phpinfo 页面用完立刻删掉它会把PHP版本、编译参数、模块列表、环境变量全暴露出来属于典型的信息泄露。再写个小脚本验证数据库连通性?php $conn new mysqli(localhost, appuser, 你的密码, app_db); if ($conn-connect_error) { die(连接失败: . $conn-connect_error); } echo 数据库连接正常字符集: . $conn-character_set_name(); $conn-close();输出数据库连接正常字符集: utf8mb4就说明整条链路打通了。如果报错先看错误信息里的关键词——Access denied是权限或host问题Cant connect是服务没起或socket路径不对Unknown database是库名写错了。照着错误信息定位别瞎猜。4. LNMP落地NginxPHP-FPM的组合拳LAMP跑通之后LNMP其实就是把前台换掉。数据库和PHP-FPM原样不动只把Apache卸载或者停掉装上Nginx重新配一遍转发规则。这一节重点讲Nginx的配置结构和PHP-FPM进程池的调优这两块是LNMP里最有含金量的部分。4.1 Nginx安装与配置文件结构dnf install -y nginx # RHEL系 apt install -y nginx # Debian系 systemctl enable --now nginxNginx的配置结构比Apache清爽得多主配置 /etc/nginx/nginx.conf 分成几个块main全局比如 worker_processes、events连接处理比如 worker_connections、httpHTTP相关全局配置、server虚拟主机、locationURL匹配规则。这种块嵌套的结构一开始看着晕习惯之后会发现逻辑很清晰——从外到内一层层收窄作用域。几个必调的参数worker_processes auto; # 跟随CPU核心数 worker_connections 1024; # 单worker最大连接数 keepalive_timeout 65; # 长连接保持时间 client_max_body_size 20m; # 上传文件大小上限worker_processes auto让Nginx自己数CPU核心一般是核数或者核数的两倍。worker_connections乘以 worker 数就是理论最大并发但实际受限于系统文件描述符上限ulimit -n需要一起调。client_max_body_size默认只有1MB用户传个稍大的图片或者附件就报413这个坑几乎人人都踩过装完就顺手改掉。站点配置我习惯放在 /etc/nginx/conf.d/ 下一个站点一个 .conf 文件。这种布局的好处是增删站点不影响主配置迁移的时候把对应的文件拷走就行。4.2 PHP-FPM进程池配置与参数计算PHP-FPM的进程池管理是LNMP性能的核心也是我最想强调的部分——不要抄网上的配置要按自己的机器算。进程池有三种管理模式static固定进程数吃内存但响应快、dynamic按需伸缩省内存但有启动开销、ondemand有请求才拉起最省内存但延迟最高。生产环境一般用 dynamic既不至于把内存吃满响应也够快。关键参数是pm.max_children也就是最多同时跑多少个PHP进程。这个值怎么算用可用内存除以单个进程的平均内存占用。假设一台8GB内存的机器MySQL分配3GB系统和其他服务占1GB剩下4GB留给PHP-FPM。单个PHP进程的实际内存占用取决于你跑的框架和代码实测下来常规业务大概在30MB到50MB之间。按40MB保守估算max_children 4096 MB / 40 MB ≈ 100设成100。这个数字设小了并发一高就排队用户看到的是响应变慢甚至502设大了内存被吃光触发OOM系统直接杀进程后果更严重。所以宁可稍微保守一点然后通过监控观察实际使用情况再往上调。其余几个参数的经验公式pm.start_servers取min_spare (max_spare - min_spare) / 2pm.min_spare_servers取 max_children 的10%左右pm.max_spare_servers取30%左右。pm.max_requests设500到1000让进程处理一定请求数后自动重启能缓解某些PHP扩展的内存泄漏问题——这个参数救过我好几次某些第三方库确实会慢慢吃内存定时回收比事后救火省心。pm dynamic pm.max_children 100 pm.start_servers 20 pm.min_spare_servers 10 pm.max_spare_servers 30 pm.max_requests 5004.3 Nginx与PHP-FPM的两种通信方式Nginx把PHP请求转给PHP-FPM有两条路unix socket和TCP。socket方式是在文件系统里创建一个特殊文件两个进程通过它通信不经过网络协议栈少了TCP三次握手和拆包组包的开销同机部署时性能明显更好一般能快10%到20%。配置长这样location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }TCP方式就是走 127.0.0.1:9000配置换成fastcgi_pass 127.0.0.1:9000;。它的优势是跨机部署方便——PHP-FPM跑在另一台机器上或者跑在容器里只能用TCP。另外排查问题的时候TCP更直观ss -lntp一看端口通不通telnet 一下就知道连不连得上而socket出问题只能靠看日志和权限。Nginx和PHP-FPM在同一台机器上就用socket不在就用TCP这是最简单的判断标准。用socket的时候要特别注意权限socket文件的属主和属组必须是Nginx worker进程能访问的否则报502。常见做法是让两边都用同一个用户组socket权限设0660。这里有个细节容易被忽略fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;这一行必须写而且$document_root要和你 server 块里的 root 指令对应上。少了这一行Nginx根本不知道要执行哪个PHP文件PHP-FPM会返回Primary script unknown日志里刷一堆报错页面上显示File not found。我见过有人把这行写死成绝对路径换个站点就失效纯属给自己埋雷。4.4 伪静态规则与常见框架适配LNMP部署里另一件高频操作是配伪静态。所谓伪静态就是把index.php?url/article/123这种带问号的地址伪装成/article/123这种看起来像静态页的形式对搜索引擎友好用户看着也舒服。实现靠的是Nginx的 try_files 加 rewrite。通用的PHP框架规则location / { try_files $uri $uri/ /index.php?$query_string; }这行的意思是先找有没有同名的真实文件没有再找同名目录都没有就把请求交给 index.php同时把原始查询串带过去。顺序不能反如果先转给 index.php那所有静态资源CSS、JS、图片都会被PHP处理一遍性能直接崩掉。不同框架的规则有细微差别。有的框架要求把 PATH_INFO 也传过去需要加fastcgi_param PATH_INFO $fastcgi_path_info;有的框架对$query_string的处理方式不同要用$args。这些细节照框架官方文档抄就行别自己发明官方给的是经过大量验证的。抄的时候注意版本不同大版本的规则可能不一样。配完规则一定要回归测试访问首页、访问一个详情页、访问一个静态图片、访问一个不存在的地址看404页面是否正常。这四个用例能覆盖绝大多数配置错误。5. 常见故障排查实录环境搭完不代表结束真正的功夫在出问题的时候。这一节我把这些年遇到的高频故障按现象分类整理附上排查思路。排查的核心方法论只有一条从外往里逐层确认。先确认Web服务器活着再确认它和后厨通着再确认后厨能干活最后才看代码。5.1 状态码排查路线502、504、403、404502 Bad Gateway是LNMP里出现频率最高的错误意思是Nginx联系不上PHP-FPM。可能原因按概率排序PHP-FPM服务没启动、socket路径配置不一致、socket文件权限不对、进程池满了pm.max_children打满。排查顺序systemctl status php-fpm # 服务活着吗 ss -lx | grep php # socket文件在吗路径对不对 ls -l /run/php-fpm/www.sock # 属主属组和权限对不对 tail -f /var/log/php-fpm/error.log # 有fatal错误吗进程池满的表现是日志里有 server reached pm.max_children setting 的warning。这时候要么调大max_children要么查代码里有没有慢查询、有没有死循环——加进程是治标找到性能瓶颈才是治本。504 Gateway Timeout是Nginx等PHP响应超时了。默认fastcgi_read_timeout是60秒某个脚本跑个导入任务跑了5分钟Nginx等不及就断了。调大这个值能解决但更要反思的是这类长任务本来就不该同步跑正确做法是丢进队列异步处理前端轮询进度。把超时调到600秒只是把问题往后推。403 Forbidden是拒绝访问。三个方向查目录有没有 index 文件index index.php index.html配了吗、目录权限够不够Nginx用户能不能读、SELinux有没有拦getenforce看状态ausearch -m avc -ts recent看拦截记录。SELinux这块后面单独说。404 Not Found是找不到文件。先确认root指向的目录对不对再确认 location 匹配规则有没有把请求引到别处最后看伪静态规则有没有把真实存在的文件也重写了。try_files的顺序写反是经典错误。5.2 数据库连不上的几种典型情况PHP报 Access denied for user 的时候别急着改密码按这个清单过一遍现象可能原因排查动作Access denied (using password: YES)密码错误或host不匹配核对userhost组合Access denied (using password: NO)代码里没传密码检查配置文件读取Cant connect to local socketsocket路径不对或服务未启动看 my.cnf 里的 socket 路径Cant connect to MySQL server端口不通或被防火墙拦ss -lntp | grep 3306Unknown database库名拼错或库没建SHOW DATABASES;Too many connections连接数打满查连接池配置看是否泄漏其中host不匹配和socket路径这两个最坑。前者前面说过了后者是因为PHP的 mysqli 用localhost时会去读 php.ini 里mysqli.default_socket指定的路径而MariaDB实际监听的socket路径可能不一样两个对不上就报连接失败。解决办法要么在连接时显式指定socket路径要么把两边的配置对齐。还有一种情况是连接数泄漏。代码里打开了连接没关闭请求一多就把max_connections撑满。表现是运行一段时间后突然全部报错重启服务又能好一阵。这时候查SHOW PROCESSLIST;看有没有大量 Sleep 状态的连接堆积有的话就是代码的问题得去改代码改配置没用。5.3 权限与SELinux这类隐形杀手权限问题最典型的场景是本地测试一切正常部署到服务器上就报无法写入文件或Permission denied。原因通常是Web运行用户对目标目录没有写权限。先看当前用户身份。Nginx的worker进程以nginx或www-data运行PHP-FPM有自己的user配置两个可能不一样。查清楚谁在干活ps aux | grep nginx ps aux | grep php-fpm然后按最小权限原则授权网站代码目录属主设为部署用户、属组设为Web用户组目录755、文件644需要写入的子目录上传、缓存、日志单独把属组改成Web用户并给775。不要图省事直接chmod -R 777那是把门拆了当锁用任何漏洞都能直接改成webshell。SELinux是另一个隐形杀手。很多人在本地虚拟机里关了SELinux到生产环境发现开着然后各种莫名其妙的权限问题就来了。它拦截的时候不看文件权限位只看安全上下文。常见处理getenforce # 看当前状态 setsebool -P httpd_can_network_connect 1 # 允许Web服务发起网络连接 chcon -t httpd_sys_rw_content_t /data/www/upload # 给目录打可写标签建议是学会和它共处而不是直接关掉。它确实增加了一层复杂度但也是最后一道防线。真要被逼急了临时关掉排查setenforce 0确认问题所在后再开回来并做对应的策略配置。5.4 故障速查表把上面这些浓缩成一张表出问题的时候照着对现象首先看关键命令页面全白无输出PHP错误日志tail -f /var/log/php-fpm/error.log502PHP-FPM进程与socketsystemctl status php-fpm504脚本执行时间查慢日志看是否有大查询403目录权限与index文件ls -l目标目录404root路径与rewritenginx -T看完整配置500PHP语法与内存限制php -l 文件检查语法上传失败client_max_body_size / upload_max_filesize两处都要调中文乱码字符集配置库、表、连接三处都要utf8mb4有个通用技巧改动配置前先备份改完先nginx -t或apachectl configtest检查语法通过了再reload。直接restart遇到语法错误服务就起不来了线上就是事故。用systemctl reload而不是restart前者是平滑重载不断连接。6. 上线前的基础调优与安全加固环境能跑起来只是及格线能不能扛住流量、扛住扫描是另一回事。这一节讲的是上线前应该做的事都是投入不大但收益明显的动作。6.1 性能参数的计算思路性能调优最容易犯的错是抄配置。网上流传的那些参数是在别人的机器、别人的业务下测出来的照搬过来大概率不合适。正确的思路是先算、再测、后调。算的部分前面已经讲过两个核心PHP-FPM的max_children按可用内存除以单进程占用MySQL的innodb_buffer_pool_size按物理内存的50%到70%专用数据库服务器可以更高和Web混部就取低值。Nginx的worker_connections按并发预期乘以1.5的余量同时把系统的ulimit -n调到万级以上否则Nginx自己设得再高也没用。测的部分最低成本的办法是用ab或者wrk做一轮压测。不用搞得很专业本地起个压测工具打几百个并发看响应时间和错误率。重点不是追求多高的QPS而是找出瓶颈在哪一层——是CPU打满了还是内存吃紧了还是数据库慢查询拖后腿。找到瓶颈再针对性优化比盲目调参有效一百倍。调的部分一次只改一个参数改完再压测对比。同时改五个参数性能提升了你也说不清是哪个起的作用。6.2 最小化暴露面安全加固的核心思想就一句能不给的权限就不给能不暴露的端口就不暴露。数据库只监听本地回环地址在配置文件里明确写bind-address 127.0.0.1防火墙层面也不放行3306。Web服务的版本号隐藏掉Nginx的server_tokens offApache的ServerTokens Prod减少被针对性扫描的可能。目录列表功能关掉Nginx的autoindex off防止有人直接翻你的文件目录。以点开头的隐藏文件比如 .git、.env、.svn要在Nginx里显式拒绝访问这几个目录泄露导致源码和密钥外泄的案例太多了location ~ /\.(git|env|svn) { deny all; return 404; }PHP这边disable_functions里禁掉一批危险函数exec、system、passthru、shell_exec之类expose_php设为 Offdisplay_errors在生产环境关掉错误显示给用户等于给攻击者送情报但错误日志要开着。还有一条经常被忽略日志要轮转。Nginx和PHP-FPM的日志不做轮转跑几个月能占满整个磁盘然后服务全部挂掉。配个 logrotate 规则按天切分、保留30天、压缩归档这套动作十分钟能配完能避免一次半夜被告警叫醒。提示上面这些加固动作建议整理成一个部署检查清单每次上线新机器照着过一遍。靠记忆总会漏清单不会。最后说个我自己的体会。这套LAMP/LNMP的东西看着是老技术现在动不动就是容器编排、Serverless好像不用学了。但实际工作中你遇到的很多问题——502、连接池打满、权限不对、字符集乱码——根子都在这套基础架构上。容器只是把这些组件打包了运行逻辑一点没变。把这一层摸透上层工具换多少代你都能快速上手。我见过太多人会写 Dockerfile 但说不清 PHP-FPM 的 socket 为什么连不上这种知识断层在排查线上问题时特别致命。另外装环境这件事第一次照着教程装可能要三四个小时第二次两小时第三次半小时。建议把整个过程整理成自己的脚本或者笔记把踩过的坑写在旁边。等到第五次的时候你会发现真正值钱的不是那几条命令而是旁边那行这里socket路径必须改不然502的批注。