LNMP动静分离实践:Nginx与PHP-FPM协同配置全解析
2026/9/14 18:37:20 网站建设 项目流程

动静分离这四个字,很多教程里写得轻飘飘,好像就是在Nginx里加一段location规则那么简单。但真正自己从头搭一套LNMP环境,把Nginx、PHP-FPM、MySQL三层理顺,再让动静请求各走各的路,你会发现真正的坑根本不在“加一段规则”,而在于你压根没搞清楚Nginx在整个体系里到底应该承揽多少活、甩出去多少活。这篇我把自己在LNMP搭建和动静分离上的完整实践过程整理出来,从组件选型到配置文件骨架,再到线上最容易翻车的几个问题排查,该走的弯路都替你走一遍,适合正在搭LNMP、或者Nginx里一堆location但不知道什么时候该用哪一种的读者。

1. 动静分离前,先把Nginx的工作模式看通透

Nginx在整个LNMP里的角色,很多人上来就误解了。它不是代替PHP解析动态脚本,也不是替你扛数据库压力,它做的事情本质上只有三类:静态文件的高速分发HTTP请求的反向代理连接与协议的调度管理

1.1 Nginx不是“Web服务器”那么简单

传统认为Web服务器负责整个HTTP请求周期,但这在Nginx的模型里是不准确的。Nginx对静态文件的处理确实非常快,因为这属于它的核心强项——基于事件驱动架构,一个worker进程可以同时处理海量连接,不依赖线程池。但对于PHP、Python这类动态语言,Nginx本身是不会“解析”的,它只负责把符合条件的请求转交给外部的FastCGI进程管理器(PHP-FPM),等PHP-FPM把脚本跑完、生成标准的HTTP响应,Nginx再把这个响应原样返回给客户端。

所以看一张典型的LNMP请求链路就清楚了:

客户端请求 ↓ Nginx 监听 80/443 端口 ├── 静态资源(.js/.css/.jpg/.png) → Nginx直接读磁盘返回 ├── 动态请求(.php 或特定URL) → 转发给 PHP-FPM(fastcgi_pass) └── 非后端请求(其他静态/重定向) → 按规则处理 ↓ PHP-FPM 解析PHP脚本 ↓ PHP 通过 MySQLi/PDO 访问 MySQL ↓ MySQL 返回数据 → PHP 生成HTML/JSON → PHP-FPM → Nginx → 客户端

Nginx在这里更像一个前厅领位员,PHP-FPM是后厨,MySQL是仓库。领位员只负责把“需要后厨处理的客单”递给后厨,把“直接拿现成小吃就能走的客单”现场交付,而不是自己钻进后厨去炒菜。

1.2 动静分离为什么能提升并发

没有动静分离时,如果让Nginx把所有文件——包括图片、CSS、JS——都转给PHP-FPM处理,那PHP-FPM就要反复解析PHP进程、连接MySQL、动态读取文件并返回二进制内容。这个过程不是不能工作,而是会让性能崩得非常快。PHP-FPM默认的pm.max_children数量有限,静态文件的高频请求很快就会把PHP进程池占满,真正需要PHP处理的业务动态请求反而排队,这就是很多站点突发访问量上来后白屏、超时的直接原因。

动静分离之后,静态资源由Nginx直接从磁盘、内存缓存甚至CDN就近返回,完全不走PHP和MySQL;动态请求才进入PHP-FPM。分离之后你会发现,同一台配置的机器能抗住的并发量,可能提升一个数量级。Nginx官方资料里常说“静态文件处理能力达到数万并发”,这里的数字就是在不经过PHP-FPM的前提下测出来的。

所以理解了这一层之后,再动手去改配置,思路就完全不一样了。

2. LNMP组件选型与平台兼容性

很多LNMP教程上来就yum install nginx php-fpm mysql-server,好像所有Linux发行版的软件源里都齐刷刷躺着这些包。但实际生产遇到的情况要复杂得多,尤其是国产化环境、ARM架构、离线内网环境,这三个场景我全部踩过。

2.1 一套经过验证的版本组合

先给出一套我在实际环境里跑稳过的组合,不一定追最新,但抗造、资料多、互相之间没有兼容性坑:

组件版本选择用途与说明
Nginx1.24.x(或发行版自带1.20+)核心Web服务器与反向代理
PHP8.1.x(支持到2024年底,长期维护)FastCGI后端,配PHP-FPM
MySQL8.0.x(Percona或官方社区版)数据存储,账号隔离

这个组合的前提是你能联网,yum/apt源里有对应包。但现实往往不按剧本来。

2.2 离线环境与ARM架构的处理

有段时间需要在麒麟V11上装LNMP,而且还是内网离线环境,yum源根本连不通,也没有现成的rpm包。当时我做的事,简单说就是找一台能联网的、同架构同操作系统的机器,用yumdownloader把需要的rpm连依赖全部拉下来,再拷进内网装。用到的核心命令大致是这样:

# 在联网机器上,安装 yum-utils yum install -y yum-utils # 只下载不安装,把指定包及其依赖全部拉到本地目录 mkdir -p /data/rpms yumdownloader --destdir=/data/rpms --resolve nginx php-fpm php-common php-mysqlnd php-json php-gd php-mbstring mysql-server # 打包拷进内网后执行本地安装 cd /data/rpms rpm -Uvh *.rpm

这里有一个容易翻车的细节:--resolve只能保证yum已知依赖,但PHP扩展之间有时存在隐式的版本匹配关系,比如php-fpmphp-common的版本必须完全一致,否则rpm会报“conflicts”或“requires”错误。我的习惯是敲完命令后在目标机器上先跑php -v确认版本,再单独验证php-fpm -t配置语法。

至于ARM架构(aarch64)移植,关键不是把x86的包硬拷过去,而是直接在ARM机器上编译安装。编译Nginx之前确认三件事:pcre-develzlib-developenssl-devel这三个依赖库是否就位;同时如果机器内存小于2G,编译时建议加--with-cc-opt='-O1'避免内存溢出;最后别忘加--with-stream,很多后来的TCP/UDP四层代理需求都会用到它,编进去了后面省得再折腾。

2.3 Docker和裸机部署怎么选

热搜词里出现很多docker pull nginxdocker部署nginx的搜索,说明容器化部署已经是大趋势。Docker部署LNMP确实省事,一条docker compose up -d就能把三个容器拉起来,但前提是你能接受三个现实问题:

一是镜像仓库在内外网之间的访问限制,内网环境得自建Harbor或导入镜像tar包;二是数据落盘问题,MySQL容器一旦被删除,数据卷如果没做好备份,数据直接蒸发;三是性能瓶颈,Docker网络NAT转发在高并发短连接场景下,确实比宿主机直接跑要损失一些性能。

我的判断标准很简单:开发环境、团队协作环境,直接用Docker Compose最香;生产环境如果是单机或双机规模不大,裸机部署反而更好排查问题。如果你已经有K8s那一套运维体系,那又另当别论。这篇文章以裸机部署为主线,因为动静分离的本质逻辑在容器里也是一模一样的,换汤不换药。

3. LNMP各层协作的配置骨架

整套LNMP的配置不需要一次到位,我的做法是先把每个组件单独跑通,再谈配合。PHP-FPM先能处理一个简单的<?php phpinfo(); ?>文件,MySQL先能建库建账号,Nginx最后把两者串起来。这样的排查路径最短。

3.1 PHP-FPM的独立配置

安装完成后,PHP-FPM的配置文件一般在/etc/php-fpm.d/www.conf(CentOS系)或/etc/php/8.1/fpm/pool.d/www.conf(Debian系)。最关键的是几个参数:

; 运行用户和用户组 user = nginx group = nginx ; 监听方式,优先选Unix Socket而不是TCP端口 listen = /run/php-fpm/www.sock listen.owner = nginx listen.group = nginx listen.mode = 0660 ; 进程管理方式 pm = dynamic pm.max_children = 50 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 35 pm.max_requests = 500

这里值得解释的是listen为什么优先用Unix Socket。Socket走的是内核进程间通信,不经过TCP/IP协议栈,没有端口监听和三次握手开销,在同一台机器上转发的性能明显优于127.0.0.1:9000这种TCP方式。代价就是Socket文件有权限问题,Nginx的工作进程(user nginx)必须对该Socket文件有读写权限,否则就会遇到经典的502 Bad Gateway。上面配置里的listen.owner = nginx就是专门解决这一步权限控制的。

pm = dynamic是生产环境最常见的选择:平时预留几个空闲进程,高峰时动态扩展到上限,避免突发流量把进程全部占满后产生502。pm.max_requests防止单个进程无限持有循环引用导致的内存泄漏,每处理500个请求后就自动重启这个worker进程,这是一种很朴素的“自我修复”机制。

3.2 Nginx主配置的结构化拆分

没有哪个正经的项目会把所有配置堆在nginx.conf一个文件里。正确做法是让主配置只干三件事:定义全局事件模型、加载http模块、include站点配置。我习惯的项目目录结构:

/etc/nginx/ ├── nginx.conf ├── conf.d/ │ ├── default.conf │ └── project.conf └── ssl/ └── yourdomain.com/ ├── fullchain.pem └── privkey.pem

nginx.conf里需要修改的核心参数不多,但每一条都有讲究:

user nginx; worker_processes auto; # 自动匹配CPU核心数 worker_rlimit_nofile 65535; # 单进程最大文件句柄数,高并发时必须提 events { use epoll; # Linux下高性能事件模型 worker_connections 10240; # 单worker可承载的连接数 } http { include mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; include /etc/nginx/conf.d/*.conf; }

worker_rlimit_nofile这一行,很多教程根本不会提到。默认情况下进程能打开的文件句柄上限是1024,这对静态文件服务器来说根本不够。如果连接数上来了,你会发现Nginx错误日志里全是“Too many open files”,这就是文件句柄不足导致的。要么在nginx.conf里加这一行,要么改/etc/security/limits.conf,二选一,但要记得改完都需要重载或重启进程才会生效。

3.3 MySQL只做该做的事

MySQL这边需要明确一个原则:不要在生产环境给PHP应用用root账号连接数据库。我曾经见过一个项目,php脚本用root连接MySQL,后来被SQL注入拖走了整库数据,教训太惨了。

规范的流程是这样:

-- 创建独立数据库 CREATE DATABASE IF NOT EXISTS app_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建专用账号,只授予应用需要的权限 CREATE USER 'app_user'@'127.0.0.1' IDENTIFIED BY 'StrongPassword123!'; CREATE USER 'app_user'@'localhost' IDENTIFIED BY 'StrongPassword123!'; -- 只给这个库授权,不给全局权限 GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON app_db.* TO 'app_user'@'127.0.0.1'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, INDEX ON app_db.* TO 'app_user'@'localhost'; FLUSH PRIVILEGES;

注意我同时创建了127.0.0.1localhost两个账号,因为PHP的mysqli/PDO如果用IP去连走的是TCP认证,用localhost走的是Socket认证,你如果不建两个账号,总有一种连接方式会报Access denied。这个细节卡过很多人。

PHP脚本里去连数据库的示例:

<?php $pdo = new PDO( 'mysql:host=127.0.0.1;dbname=app_db;charset=utf8mb4', 'app_user', 'StrongPassword123!', [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] );

等这三个组件各自跑通了,再进下一步做动静分离的整合。

4. 动静分离的核心:location匹配优先级与路径设计

终于到了这篇文章的主角。动静分离的配置集中在Nginx的server块里,通过location指令把不同类型的请求分到不同的处理路径。但很多人只记住了语法,没搞懂匹配优先级,写出来的规则经常被Nginx“无视”。

4.1 location匹配优先级,一条一条拆开讲

Nginx的location有四种匹配方式,优先级从高到低是:

  1. 精确匹配=:比如location = /favicon.ico,只有完全等于这个路径才命中,一旦命中立即返回,不再往下找。
  2. 前缀匹配^~:比如location ^~ /static/,以这个路径开头的URL都会命中,且命中后不再做正则匹配。
  3. 正则匹配~~*:比如location ~ \.php$~*表示不区分大小写。Nginx会按配置文件里的顺序逐个测试正则,一旦正则匹配上就停。
  4. 普通前缀匹配:没有=^~修饰的路径,比如location /api/location /,先做最长前缀匹配,再把匹配结果暂存,继续检查有没有正则能命中。

平时最容易踩的坑就是:正则的优先级高于普通前缀匹配。你以为写了location /api { ... }就能把所有API请求拦住,结果在它下面是location ~ \.php$ { ... },某个/api/user.php的请求反而被PHP规则接管了。

给一个典型动静分离的完整配置:

server { listen 80; server_name example.com; # 站点根目录 root /var/www/example.com; index index.php index.html; # 纯静态资源:图片、CSS、JS,交给Nginx自己处理 location ^~ /static/ { alias /var/www/example.com/static/; expires 30d; access_log off; add_header Cache-Control "public, immutable"; } # 动态请求:所有PHP文件走FastCGI 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; fastcgi_param HTTPS off; } # 其他所有请求:默认交给PHP入口文件 location / { try_files $uri $uri/ /index.php?$query_string; } }

这段配置里包含了一个最经典的坑:aliasroot的区别。

4.2 alias和root,90%静态资源404都出在这里

rootalias在静态资源路径处理上有本质区别:

  • root /var/www/example.com加上location /static/,实际访问/static/a.png时,Nginx会去/var/www/example.com/static/a.png找文件,也就是把URI原样拼到root后面。
  • alias /var/www/example.com/static/加上location /static/,实际访问/static/a.png时,Nginx会把location里匹配到的前缀去掉,再把剩余路径拼到alias后面,也就是去/var/www/example.com/static/a.png找。

看上面这个配置,如果你把alias /var/www/example.com/static/;换成root /var/www/example.com/static/;,那访问/static/a.png时Nginx会去找/var/www/example.com/static/static/a.png,直接404。

还有一个更隐蔽的坑:alias使用后面必须以/结尾alias /var/www/example.com/staticalias /var/www/example.com/static/,在拼接时前者可能case出来/var/www/example.com/statica.png这种错误的路径。所以我的习惯是alias后面永远带斜杠,root后面永远不带。

4.3 动静分离的进阶形态:前端静态项目 + 后端API

现代前后端分离项目里,动静分离的含义已经进一步升级了。前端构建产物(Vue/React的dist目录)全是静态文件,交给Nginx直接读;后端接口走/api/前缀,Nginx反向代理到后端服务。

这种场景下的配置骨架是这样:

server { listen 80; server_name admin.example.com; # 前端静态资源 root /opt/frontend/dist; index index.html; # 单页应用路由回退,刷新子路由不会404 location / { try_files $uri $uri/ /index.html; } # 后端 API 反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 60s; proxy_connect_timeout 5s; } }

这里proxy_pass结尾加不加/也有讲究。如果写成proxy_pass http://127.0.0.1:8080;,Nginx会把完整的URI(包括/api/前缀)传给后端;如果写成proxy_pass http://127.0.0.1:8080/;,Nginx会去掉/api/前缀再转发,后端拿到的就是去掉前缀后的路径。两者对应完全不同的后端设计,后端如果是直接用原始URI路由的框架(比如大多数Go的net/http),就用不带/的写法;后端如果定义的路由是/users而非/api/users,就用带/的写法。

部署Vue3项目时,还有一个常见的坑:前端路由如果是history模式(没有#),用户直接访问/about刷新页面时,Nginx找不到/about这个物理文件,会返回404。上面配置里的try_files $uri $uri/ /index.html;就是为了解决这个问题的,它会把所有找不到的请求回退到index.html,让前端路由接管。

5. 踩坑实录:三个高频问题与完整排查链路

LNMP排错,最忌讳的就是瞎改配置试运气。下面这三个问题,是我在真机上踩过后总结出完整排查链路的,按这个顺序查,能少走大量弯路。

5.1 502 Bad Gateway:从进程到权限一步步核

502的报错意思是“Nginx作为网关,没能从上游PHP-FPM拿到有效响应”。查到502,第一反应不是改Nginx配置,而是先确认PHP-FPM到底是死是活。

排查链路我一般按这个顺序走:

# 第一步:确认 PHP-FPM 进程是否在运行 ps aux | grep php-fpm # 第二步:确认监听状态 ss -ln | grep -E '9000|php-fpm' # 如果用的是Unix Socket,看socket文件 ll /run/php-fpm/www.sock # 确认socket文件存在,且属主是nginx # 第三步:看PHP-FPM错误日志 tail -n 100 /var/log/php-fpm/error.log # 第四步:单独用命令行测试FPM的返回 curl -I http://127.0.0.1/index.php

最常见的结果分三种:

  • 进程没起来systemctl start php-fpm即可,但要顺手查日志搞清楚为什么起不来,最常见是php.iniphp-fpm.conf语法错误。
  • 进程活着但socket文件属主不对:Nginx worker以nginx用户运行,而socket文件属主是apacheroot,就必然502。这个直接回头看/etc/php-fpm.d/www.confuserlisten.owner配置。
  • 进程活着socket也没错,但SCRIPT_FILENAME指向的文件不存在:比如fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;里的$document_root和你实际文件路径不匹配。这时php-fpm错误日志里会明确写出“Primary script unknown”,修正root或SCRIPT_FILENAME路径即可。

5.2 504 Gateway Timeout:超时不只是PHP的问题

504和502不同,它表示PHP-FPM“响应太慢”,让Nginx等得不耐烦了。处理这个问题的思路不是一味加大超时时间,而是先搞清楚PHP脚本为什么慢。

先看几条跟超时有关的配置:

fastcgi_connect_timeout 60s; # 连接PHP-FPM超时 fastcgi_send_timeout 120s; # 发送请求体到PHP-FPM超时 fastcgi_read_timeout 120s; # 读取PHP-FPM响应超时

以及PHP侧的配置(php.ini):

max_execution_time = 60 ; 单次脚本最大执行时间 max_input_time = 60 ; 接收请求数据最大时间 memory_limit = 256M ; 内存上限,脚本跑了太久还会触及这个

如果你发现是某个导出功能、报表统计功能经常504,那属于正常的长时间脚本,不要再死磕调大超时时间了,正确做法是把这个任务改造成异步队列,前端只返回“任务已提交”,真正跑数放到后台。但如果你发现所有请求都莫名变慢,那就得回到PHP-FPM进程池占用率和MySQL慢查询上查。我遇到过最离谱的一次,是MySQL的innodb_buffer_pool_size被调太小,导致每次查询都要反复读磁盘,整个LNMP链路的翻车,表象是到处超时,但根因根本不在Nginx。

5.3 静态文件404:先区分是Nginx没找到还是权限拒绝

静态文件404的排查比502要简单一些,但它有个容易混淆的点:404也可能是文件存在但Nginx无权读。这时候Nginx错误日志里会有两种截然不同的记录:

/post/static/a.png failed (13: Permission denied) # 这是权限问题 /post/static/a.png failed (2: No such file or directory) # 这是真的文件不存在

权限问题的标准处理链路过一遍:确认目录的r-x权限(可进入可读取)、文件权限至少r--、所有者的属主和被root/nginx访问的关系。一个常用命令:

# 查看路径里每一层目录的权限 namei -l /var/www/example.com/static/a.png

namei这个命令可以一次性显示路径每一级目录的权限和属主,比ls -l一层层敲要高效得多。常规要求是:从/到文件的所有路径级目录,都要有x权限;如果文件属主是root且权限是600,那nginx用户读不了,需要chmod 644chown nginx:nginx

文件不存在的404,十有八九是root/alias的路径拼错了。先把location ^~ /static/的alias指到正确目录,再访问时观察Nginx错误日志里的实际路径,你在哪一步发现的偏差,通常就是路径拼接的偏差。

6. 上线前的安全加固与性能调优

配置调通只是第一步,上线前有两类工作不做,后面迟早要出事。一类是安全加固,另一类是性能参数调优。

6.1 安全:版本更新、信息隐藏与目录权限

先说版本问题。Nginx这种基础设施级软件,CVE公告出来之后,官方通常会在小版本里修复。我记得有一段时间Nginx被曝出过HTTP/2相关的漏洞,后来修复版本很快跟进,如果生产环境一直停留在老版本,就等于裸奔。热搜词里也有人提到CVE-2025-1695SF-0005-22843这类编号,我的建议是:无论什么平台,安装完第一件事就是nginx -v查看版本号,然后去官方CHANGES文件核对是否已经包含对应修复版本。如果明确受影响,升级是小版本操作,不要拖。

安全加固的常规操作可以做成一张清单,逐条过:

项目配置方式说明
隐藏版本号server_tokens off;防止攻击者根据版本号定向打漏洞
HTTP头部限制移除不必要的Server详情Nginx自身响应头会变成nginx
限制上传体积client_max_body_size 10m;防大文件上传拖垮后端
仅允许必要请求方法limit_except GET POST { deny all; }禁止DELETE/PUT等不需要的方法
PHP脚本执行权限location ~ \.php$只放在项目目录内避免上传目录里的php被执行
目录列表关闭autoindex off;防止/static/目录被浏览
数据库最小权限见3.3节不用root连库

其中“上传目录禁止执行PHP”这条特别容易忽略。很多站点结构里有一个/uploads/目录,用户上传文件后又能访问,如果上传目录配了PHP解析,攻击者传一个带PHP代码的文件上去,URL直接访问它,就相当于在服务器上执行了任意代码。规避方法:

location ^~ /uploads/ { location ~ \.php$ { deny all; } }

嵌套location里用deny all屏蔽掉uploads目录下的所有PHP请求。注意Nginx的location是允许嵌套的,这种嵌套方式比在根配置里通过if判断要清晰得多。

6.2 性能:进程数、连接数、压缩与缓存

动态请求交给PHP-FPM后,静态资源的性能就全看Nginx自己了。我常用的性能参数组合:

worker_processes auto; # 有多少CPU核就起多少worker worker_rlimit_nofile 65535; events { use epoll; worker_connections 10240; multi_accept on; # 一次accept接受所有新连接 } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; gzip on; gzip_comp_level 5; gzip_min_length 1k; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss image/svg+xml; gzip_vary on; # 静态资源的过期时间,减少回源压力 map $sent_http_content_type $expires_map { default off; text/html 1h; text/css 7d; application/javascript 7d; image/jpeg 30d; image/png 30d; image/svg+xml 30d; application/font-woff2 30d; } expires $expires_map; }

这里worker_connections 10240和之前提到的worker_rlimit_nofile要联动看:worker_processes×worker_connections就是理论上的最大连接数,而每个连接都会消耗一个文件句柄,所以worker_rlimit_nofile必须大于等于连接数上限,否则连接没到上限就先把文件句柄耗光了。

gzip压缩对文本类资源非常有效,简单说就是把CSS、JS、JSON文件压缩到原来的三分之一左右,但代价是CPU开销。gzip_comp_level 5是我测试下来性能和压缩比比较均衡的位置,填9并不会带来多少额外的压缩收益,CPU反倒吃得更狠。另外,图片、视频这类本来就是二进制压缩过的资源不需要开gzip,开了只会浪费CPU。

Nginx 1.21.5这个版本出现在热搜词里不止一次,如果用的版本恰好是它,可以考虑在支持Brotli的构建版本里开启Brotli压缩。Brotli在压缩率上通常比gzip再高10%-20%,但需要额外模块,且客户端兼容性要注意,CDN上如果开启了Brotli会更容易拿到收益。

6.3 日志:什么时候可以关掉access_log

日志问题常常被忽视,直到磁盘被占满才发现。Nginx的access_log默认记录每一个请求,但如果你的站点静态资源被大量访问,且又不常做访问分析,日志会飞速膨胀。我的处理方式分两种:

一种是保留日志但做切割,Linux自带logrotate,Nginx的rpm包安装后一般自带一个日志切割配置:

cat /etc/logrotate.d/nginx

另一种是对特定location关掉日志。静态资源请求量最大,但分析价值相对低,我会在静态资源location里加一行access_log off;,业务API的日志保留,这样日志量能砍掉一大半。

注意,日志切割不是只把旧日志改名就可以,必须告诉Nginx重新打开日志文件,否则它还在往改名后的旧文件里写。标准做法是在logrotate配置里加上kill -USR1 $(cat /var/run/nginx.pid)

7. 上线前的全链路自检清单

配置全写完,不等于就可以上生产了。我把每次上线前必跑的自检清单整理成一份,现在已经成了我的操作习惯,每一条都是踩过的坑换来的。

网络层检查:

  • [ ] 防火墙是否放行80/443端口,firewall-cmd --list-portsiptables -L -n核对
  • [ ] SELinux是否是Enforcing,如果是,检查httpd_can_network_connect是否开启,否则Nginx反代PHP-FPM/Socket时会被SELinux拒绝
  • [ ] 域名解析是否已指向当前机器,dig example.com +short确认

Nginx层检查:

  • [ ]nginx -t配置语法通过
  • [ ]systemctl reload nginxps aux | grep nginx能看到worker进程数量与CPU核数一致
  • [ ] 关闭server_tokens off后的响应头是否隐藏了版本号,curl -I验证
  • [ ] 访问一个不存在的php文件,确认返回404而不是200空内容,避免PHP伪静态解析漏洞

PHP层检查:

  • [ ]php -v确认版本与php-fpm -t确认语法
  • [ ]/var/log/php-fpm/error.log没有sockprimary script unknown相关报错
  • [ ] 用php -m确认需要的扩展已经安装,特别是mysqlipdo_mysqlcurlgd

MySQL层检查:

  • [ ]mysql -u app_user -p -h 127.0.0.1 -e 'SHOW DATABASES;'能通过TCP方式连接
  • [ ]mysql -u app_user -p -e 'SHOW DATABASES;'能通过Socket方式连接
  • [ ] 执行一次简单的SELECT 1确认没有权限问题
  • [ ] 备份策略已生效,mysqldump定时任务能正常产出备份文件

业务层检查:

  • [ ] 首页能打开,无500/502/504
  • [ ] 一个静态资源(CSS/JS/图片)能直接访问,响应头带Cache-ControlExpires
  • [ ] 一个动态接口(PHP/API)能返回预期数据,不走静态缓存
  • [ ] 刷新一个前端history路由能正常显示而不是404
  • [ ] 查询Nginx access_log,确认动静请求分别落到了预期位置

这套清单跑完,才算一个能交付的LNMP环境。我在不同环境里部署过很多次LNMP,最大的体会是:这个系统并不复杂,但它要求你对每一层的边界有清晰的认识。Nginx不是万能瑞士军刀,它善于做它该做的事——处理静态连接和转发;PHP-FPM管动态脚本;MySQL管数据。动静分离之所以重要,是因为它让每层各司其职,这才是Nginx这套架构能够支撑高并发的根本。

最后再分享一个贯穿始终的小建议:遇到任何诡异问题,先看日志,不要盲目改配置。Nginx的错误日志、PHP-FPM的错误日志、MySQL的慢查询日志,三层日志按时间线对齐,90%的问题都能定位到根因。把这套环境亲手从头到尾搭三遍,你对Nginx的认知会完全不一样。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询