
LNMP 这套东西很多人一开始都是从一键包或者宝塔面板入手的。我也是这么过来的但直到我自己手动在裸系统上从头搭了一遍才算真正弄明白 Nginx、PHP-FPM、MySQL 三者之间的协作关系尤其是动静分离这几个字背后的含义。作为 Nginx 实战系列第三篇这篇不打算重复前两篇讲过的安装编译和基础配置而是把重点放在 LNMP 完整搭建里最容易含糊的几个环节——Nginx 怎么和 PHP-FPM 对话、location 匹配规则怎么影响动静分离、静态资源缓存参数到底怎么设以及上线前我最常做的验证和排查动作。适合已经装好 Nginx 和 PHP但对配置文件的逻辑还停留在照着抄但不知道为什么要这样写状态的人。1. LNMP 环境选型与基础准备先想清楚再动手1.1 为什么是 LNMP而不是 LAMP学 LNMP 之前要先把 LAMP 拿出来对比一下因为两者的差异直接决定了后面很多配置思路。LAMP 里的 Apache 通过 mod_php 模块直接内嵌 PHP 解释器Apache 收到请求后可以在进程内部处理 PHP 脚本而 LNMP 里的 Nginx 本身没有能力执行 PHP它只能处理静态文件。遇到 .php 文件时Nginx 需要把请求转发给后端的 PHP-FPM 进程池PHP 执行完脚本后把结果返回给 Nginx再由 Nginx 返回给用户。这个差异不仅是架构上的也直接影响了性能特征。Apache 的 mod_php 因为解释器常驻进程资源占用偏高但处理动态请求时少了一层网络开销Nginx PHP-FPM 则是把静态处理和动态处理彻底拆开静态文件由 Nginx 直接读取返回高并发下静态资源这部分几乎不消耗 PHP 的资源池。正是这个特性让 LNMP 在做动静分离时有天然的优势——静态请求快到极致动态请求按需进入 PHP-FPM互不拖累。如果你还在纠结到底学 LAMP 还是 LNMP我的建议很明确现阶段新项目只要不是历史遗留的老系统统一选 LNMP。部署 CentOS 系系统、服务器内存 2G 以上的场景下LNMP 的并发承载能力明显更优运维上的坑也相对清晰可控。1.2 版本选型的硬性约束版本选择这件事情新手上路的时候最容易随便选一个最新版结果编译到一半报错或者 PHP 扩展和 Nginx 模块不兼容非常浪费时间。我自己在多次搭建中验证下来比较稳的版本组合如下组件推荐版本说明Nginx1.24.x主线稳定版不要在 Linux 生产环境用 1.25 的开发版PHP8.1.x 或 8.2.x8.0 以下建议跳过很多新扩展已停止维护MySQL8.0.x或 MariaDB 10.118.0 的 auth 插件注意连接时的用户认证方式操作系统CentOS 7.9 / Rocky Linux 8 / Ubuntu 20.04CentOS 7 需要留意 yum 源更换问题这里重点提醒一下 PPA/remi 源的使用。如果你用的是 CentOS 系的 remi 源安装 PHP 时务必要看清楚php-fpm这个包是否和你的php-cli版本一致。我遇到过把 php 8.0 和 php-fpm 8.2 装到一台机器上的情况当时排查了大半天最后发现php -v看的是 8.0而 php-fpm 启动的是 8.2两个版本的扩展目录又不一致导致部分函数在网页里报未定义CLI 里却正常。MySQL 8.0 需要注意的则是caching_sha2_password默认认证插件。PHP 5.6 以下的 mysqli/pdo 扩展不支持这个认证方式如果你被迫使用老版本 PHP要么在 MySQL 里给应用账号设置mysql_native_password要么升级 PHP。2025 年了还在用 PHP 5.6 维护老项目的朋友建议尽早规划升级了。1.3 目录规划与权限设计LNMP 搭建前先把目录规划好后面排错会省很多事。不同发行版路径差异很大我习惯用编译安装的方式把目录统一规划为/usr/local/nginx/ # Nginx 主目录 /usr/local/nginx/conf/ # 配置文件 /usr/local/nginx/html/ # 默认站点根目录 /usr/local/nginx/logs/ # access.log / error.log /usr/local/php/ # PHP 主目录 /usr/local/mysql/ # MySQL 主目录 /data/www/ # 实际项目代码目录 /data/wwwlogs/ # 按项目分目录的日志有朋友可能觉得编译安装麻烦直接用包管理多省事。我的观点是学习阶段强烈建议手动编译安装一次。包管理的最大问题是配置分散在多个目录里你看不到完整的链路而编译安装时每个依赖、每个模块都是你主动选择的对 Nginx 的 configure 参数、PHP 的 extension 机制会有更深的理解。等自己真的搭过一遍后再回头看宝塔或 OneinStack 的配置能看懂每个文件的意义而不是被动接受。目录规划好后要立即处理权限。Nginx 的 worker 进程通常以nginx用户运行PHP-FPM 的监听进程默认以php-fpm用户运行项目文件至少要让这两个用户可读可执行。我习惯把项目目录设置为chown -R php-fpm:nginx /data/www chmod -R 755 /data/www当 PHP 需要写入文件比如上传目录、缓存目录时再单独针对这些子目录放开写权限而不是直接对整个站点目录chmod -R 777。这个习惯能避免非常多权限类的安全事件。2. Nginx 与 PHP-FPM 的协作原理和配置落地2.1 FastCGI 协议Nginx 和 PHP 的沟通语言先打一个比方Nginx 是前台接待PHP-FPM 是后厨大厨。前台把客人点的菜HTTP 请求记录下来但自己没有能力做菜执行 PHP 脚本于是把菜单请求参数、URI、Header 等信息装进一个标准化的信封里递给后厨这个信封使用的标准格式就是 FastCGI 协议。后厨把做好的菜HTML 输出放回信封递给前台前台再端给客人。Nginx 与 PHP-FPM 通过两种方式通信TCP 套接字和 Unix Socket。使用 TCP 时配置fastcgi_pass 127.0.0.1:9000;使用 Unix Socket 时配置fastcgi_pass unix:/run/php-fpm/www.sock;。两者没有绝对的优劣但我在实践中的倾向是很明确的单机部署且没有跨机器访问需求的用 Unix Socket。少了 TCP 协议栈的开销和端口监听暴露面性能略高也更安全。需要把 PHP 和 Web 服务器拆到不同机器比如 Nginx 做负载均衡入口后端多台 PHP 节点的用 TCP。此时需要单独防火墙放行 9000 端口并且设置只允许内网访问。FastCGI 协议除了传输脚本执行结果还允许 Nginx 通过参数形式传给 PHP 一些上下文信息这就是fastcgi_params和fastcgi.conf文件的作用。它们里面定义了SCRIPT_FILENAME、DOCUMENT_ROOT、HTTP_HOST等变量PHP-FPM 正是通过SCRIPT_FILENAME这个参数知道该执行哪个脚本文件的。2.2 最小可运行配置从一个 location 开始很多人第一次看 Nginx 配置时被里面庞大的 server、location、if 块吓住。其实 LNMP 里核心的 PHP 请求处理只需要一个 location我的最小配置长这样server { listen 80; server_name example.com; root /data/www/example; index index.php index.html; # 核心把 PHP 请求交给 FPM location ~ \.php$ { root /data/www/example; 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 SCRIPT_FILENAME $document_root$fastcgi_script_name;。它告诉 PHP-FPM你要执行的脚本在磁盘上的完整路径是这里。如果这一行配置错了——比如有人写成了$request_filename或者手抄时漏掉了$document_root——最常见的报错就是访问 PHP 文件返回 404 或者Primary script unknown。这个报错在 PHP-FPM 的 error log 里能看到后面排查部分再细说。为什么这里的 location 要写成~ \.php$这个正则形式因为 Nginx 的普通 location 只是做前缀匹配默认遇到 .php 结尾的请求时会直接把它当静态文件返回浏览器里就会看到它下载原始 PHP 源码或者直接显示空白这是初学 LNMP 时最容易发现的一个危险现象。2.3 php-fpm 池配置和资源预算PHP-FPM 默认会创建一个名为www的 pool配置在/etc/php-fpm.d/www.conf或编译安装目录下对应位置。池配置的核心参数这几个必须理解listen /run/php-fpm/www.sock listen.owner nginx listen.group nginx listen.mode 0660 pm dynamic pm.max_children 20 pm.start_servers 5 pm.min_spare_servers 3 pm.max_spare_servers 8 pm.max_requests 1000pm有三种模式static固定子进程数、dynamic动态伸缩、ondemand按需启动。生产环境我一般用dynamic或者static具体取决于你的服务器内存。一个粗糙但实用的预算方法是每个 PHP-FPM 进程平均占用内存约 30-50MBWordPress 这类框架可能到 80MB 以上max_children乘以单个进程内存就是 PHP 部分的内存上限。假设机器内存 4GB给 MySQL 预留 1.5GB系统和其他进程预留 1GBPHP 最多可以分配 1.5GB那么max_children设为 30 左右按 50MB/进程估算。pm.max_requests表示每个子进程处理多少次请求后自动重启。这个参数能有效防止 PHP 脚本里的内存泄漏逐渐累积到 OOM建议设置 500-1000。有些情况下你发现 PHP-FPM 长时间运行后 CPU 涨上去、响应变慢八成就是某些脚本的内存回收不彻底这个参数能续命。2.4 SQL 层的一个提醒不要忽略连接池和相关状态LNMP 里 MySQL 也是重要一环虽然本篇主讲 Nginx但 PHP-FPM 与 MySQL 的连接方式直接影响 php-fpm 进程的健康度。PHP-FPM 进程处理完请求后不会销毁进程但也没有内建的 MySQL 连接池。如果使用 pdo 短连接每次请求都会新建和释放 MySQL 连接高并发下 MySQL 的Threads_created会非常高甚至出现Too many connections错误。我的习惯是如果项目本身没有大型框架的自动重连机制可以在 php-fpm 的 pm 设置里保持稳定的max_children避免进程频繁大量创建销毁。进程数量稳定了MySQL 连接波动也会跟着稳下来。另外 PHP 8.1 开始内置了mysqli的持久连接支持但注意持久连接如果配合pm dynamic模式不同的 php-fpm 进程会各自持有自己的持久连接连接数上限约等于max_children需要把 MySQL 的max_connections调大到进程数以上。3. 动静分离的规则设计与真实场景推演3.1 动静分离的本质请求分类和处理策略动静分离这个说法本质上是在做请求分类静态请求图片、CSS、JS、字体等直接由 Nginx 读取磁盘静态文件返回不经过 PHP-FPM动态请求.php 页面、API接口才交给 PHP 执行。这样可以减少 PHP-FPM 的进程占用静态资源请求再多也不会快速消耗完 PHP 进程池。静态资源由 Nginx 直接服务响应速度更快还可以叠加缓存、压缩、浏览器缓存策略。就算 PHP 进程池在某一时刻被动态请求占满静态资源仍然能正常返回页面不至于白屏。动静分离的项目配置并不复杂但很多人把规则写乱了。下面是我最常用的一套静态资源处理配置# 静态资源目录直接命中绕过 PHP location ^~ /static/ { alias /data/www/example/static/; expires 7d; access_log off; } # 常见静态文件扩展名 location ~* \.(jpg|jpeg|gif|png|webp|ico|css|js|woff2?|svg)$ { expires 30d; add_header Cache-Control public, immutable; access_log off; } # 动态请求兜底 location / { try_files $uri $uri/ /index.php?$query_string; }几个细节值得展开。expires 7d和Cache-Control是给浏览器用的缓存策略不是 Nginx 自身的文件缓存。expires 30d会返回Cache-Control: max-age2592000告诉浏览器这文件一月内不用重新请求。但把这类规则用在整个站点时要小心如果 CSS/JS 文件名不带上哈希版本号更新前端代码后用户浏览器可能因为缓存而继续加载旧文件。3.2 location 匹配规则的优先级不要被直觉骗了动静分离能否正确处理很大程度上取决于你懂不懂 Nginx location 规则的匹配优先级。很多人写配置时心浮气躁觉得我写了两个 location一个\.php$一个\.css$它应该自动分派结果测试时发现某些请求走错了分支。Nginx location 的匹配顺序我总结成口诀精确优先正则优先前缀靠边最长前缀具体优先级如下写法示例优先级精确匹配location /login最高^~普通前缀匹配命中后不再检查正则location ^~ /static/高~或~*正则匹配location ~ \.php$中普通前缀匹配location /app低/兜底location /最低动手验证一下就会发现一个容易踩坑的场景是location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; # ... }在这个配置下访问foo.css时Nginx 会先检查所有正则发现只有\.php$这个正则不匹配于是退回普通前缀匹配命中location /按try_files处理。如果文件存在直接返回静态文件不存在则重写到 index.php。这就是 Laravel、ThinkPHP 等框架最经典的配置方式动静分离的本质就藏在这个匹配顺序里。3.3 静态资源缓存策略不只 expires还有 open_file_cache动静分离的静态部分很多人的认知停在了expires。实际上面向性能调优时还有两个非常重要的配置gzip和open_file_cache。gzip把文本类文件HTML、CSS、JS、SVG压缩后再传输能大幅减少带宽消耗。一个 300KB 的 JS 文件开启 gzip 后通常能压到 80KB 左右对移动端用户尤其友好。默认的配置片段gzip on; gzip_min_length 1k; gzip_comp_level 5; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svgxml;open_file_cache则是 Nginx 层面的文件描述符缓存。它的作用是缓存已经打开过的静态文件的元数据避免每次请求都执行一次open()系统调用。高并发静态文件场景下收益明显open_file_cache max1000 inactive20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;有一个我自己掉过坑的细节open_file_cache默认不缓存文件描述符本身它缓存的是打开结果成功打开或打开失败。所以如果项目上线后修改了静态文件内容旧的缓存不会立刻失效用户可能拿到更新前的文件。此时nginx -s reload并不能清空这个缓存需要nginx -s reopen或者等inactive时间过期。遇到明明替换了文件但线上还是旧内容的问题时第一个先查它。3.4 多项目共存时的动静分离设计很多朋友搜Nginx 部署多个 web 项目时是在同一个 IP 上要挂两三个网站。我这里提供一个通用做法每个项目一个 server 配置文件用 server_name 区分动静分离规则在各自的 server 块里独立配置。# /etc/nginx/conf.d/site_a.conf server { listen 80; server_name a.example.com; root /data/www/site_a/public; # 动态请求交给 index.php location / { try_files $uri $uri/ /index.php?$query_string; } # 静态文件直接给 location ~* \.(css|js|png|jpg)$ { expires 7d; access_log off; } # PHP 请求 location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; } } # /etc/nginx/conf.d/site_b.conf server { listen 80; server_name b.example.com; root /data/www/site_b/public; # 类似配置 }当两个项目都要动 PHP 时需要注意 PHP-FPM 的 pool 资源分配。默认同一个www池会被多个站点共用如果允许更好的做法是给每个站点单独建 pool; /etc/php-fpm.d/site_a.conf [site_a] user php-fpm group nginx listen /run/php-fpm/site_a.sock pm dynamic pm.max_children 10 ; /etc/php-fpm.d/site_b.conf [site_b] user php-fpm group nginx listen /run/php-fpm/site_b.sock pm dynamic pm.max_children 10然后 Nginx 里分别fastcgi_pass unix:/run/php-fpm/site_a.sock;和fastcgi_pass unix:/run/php-fpm/site_b.sock;。这样互相之间不会因为某一个站点峰值流量挤爆进程池而互相影响。隔离度和运维可控性比一个大池子好很多。4. 上线前必须做的一次完整验证与性能检查4.1 用 curl 验证动静请求走向配置写完之后不要急着用浏览器打开。浏览器有缓存、有各种预加载干扰很多。我习惯先用 curl 从命令行的角度做一次体检确认每次请求到底走的是哪条分支。第一组验证静态资源请求。看看响应头里是否有 Nginx 直接处理的迹象curl -I http://127.0.0.1/static/test.css正常返回类似HTTP/1.1 200 OK Content-Type: text/css Cache-Control: max-age604800 Expires: ...注意响应头里没有X-Powered-By: PHP也没有出现 PHP 执行的迹象这代表它是由 Nginx 直接返回的静态文件。第二组验证动态请求。访问 PHP 文件时重点是确认X-Powered-By、生成的 HTML 内容是否正常同时观察 php-fpm 的访问日志curl -I http://127.0.0.1/index.php tail -f /var/log/php-fpm/access.log如果 PHP 请求返回502 Bad Gateway说明 Nginx 连接不到 PHP-FPM可能是 sock 文件路径不对、sock 权限不足或者 PHP-FPM 根本没启动。第三组验证最容易被忽略的一个直接访问一个不存在的 PHP 文件比如http://127.0.0.1/test_not_exist.php观察是否返回404还是200空页。这能检查SCRIPT_FILENAME配置是否正确。如果返回 404大概率是try_files $uri的路径拼错了如果返回 200 但页面空白说明实际执行的是 index.php 而不是你请求的文件路由规则不太对需要检查location ~ \.php$和try_files的搭配。4.2 压测参数解读和瓶颈定位LNMP 搭完如果不做一次压测你永远不知道自己的配置在多大流量下会崩。这里我用最常用的abApacheBench举例先看参数ab -n 5000 -c 100 http://127.0.0.1/index.php-n 5000表示总共发 5000 个请求-c 100表示同时 100 个并发。测完重点看三组数值Requests per secondQPS看整体容量。Time per request (mean per request)平均单请求耗时看单个请求的服务能力。Failed requests失败数如果非零直接看失败原因。Non-2xx responses和Connect reset的含义不同前者的瓶颈多半在应用层后者的瓶颈多半在连接层连接队列满或系统 fd 满。压测时如果发现Requests per second很低Failed requests里大量Connect reset典型原因是 Nginx 的 worker_connections 不够。Nginx 每个 worker 能建立的连接数写在events块里events { worker_connections 1024; }一般这个值和worker_processes auto配合默认 1024 在并发 1000 以上时就开始不够了。改到 4096 或 8192 通常问题就缓解了。但不要盲目往大调因为这个值直接关联系统的最大文件描述符限制ulimit -n两者不匹配时 worker 进程会报too many open files。4.3 日志口径与错误排查常用命令LNMP 排错第一反应不是打开浏览器 F12而是看日志。访问日志access.log回答请求进来没有、给谁处理的、状态码多少错误日志error.log回答哪里出了问题、为什么出问题。Nginx 的日志格式可以在nginx.conf里用log_format自定义我长期使用一套包含接口耗时和上游请求耗时的高信息量格式log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for $request_time $upstream_response_time;其中$request_time是 Nginx 收到请求到返回响应的总耗时$upstream_response_time是转发给 PHP-FPM 后 PHP 的执行耗时。两者之差基本就是 Nginx 自身处理耗时。如果$request_time大而$upstream_response_time小说明是 Nginx 层面排队反过来 PHP 执行慢马上能定位到 MySQL 慢查询或者 PHP 脚本本身。真正排查故障时我习惯这样按顺序来# 1. 确认 Nginx 进程正常 ps aux | grep nginx nginx -t # 2. 确认 PHP-FPM 进程和 socket 文件存在 ps aux | grep php-fpm ls -l /run/php-fpm/www.sock # 3. 用 curl 拉接口看是否返回 502/504/500 curl -v http://127.0.0.1/app.php # 4. 同时 tail 两份日志对照状态码 tail -f /usr/local/nginx/logs/error.log /usr/local/nginx/logs/access.log以最常见的 502 为例如果 error.log 出现connect() to unix:/run/php-fpm/www.sock failed (13: Permission denied)第一件事是检查 sock 文件的属主和 php-fpm 配置里的listen.owner、listen.group是否一至。出现connect() failed (111: Connection refused)时检查 PHP-FPM 到底监听的是 TCP 还是 Unix Socket两个协议互连时会报这个错。5. 我踩过的几个坑和最终的调优心得5.1 静态资源 404 的坑正则 location 的隐藏优先级前公司部署一套前端项目时所有.js和.css文件在浏览器里 404但是 HTML 页面正常返回。我检查了文件权限、路径、selinux全都没问题。最后突然想到 Nginx location 的匹配顺序当时项目里有一条比较宽泛的正则location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ { root /data/www/root_dir; expires 7d; }问题就出在这里的root写死了根目录而这个项目并没有把全部静态文件放在这个根目录下。因为正则 location 优先于普通前缀 location所以即使另一个location /app/里的alias指向了正确的资源地址请求到css文件时仍然命中这条正则去/data/www/root_dir/app/css/xxx.css找文件自然 404。正确做法是不要用root写死所有静态文件而是按实际目录把静态请求分类或者去掉写死的root让location继承 server 块里的root。这件事给我的教训是——正则 location 的正则两个字不是摆设它的优先级比所有不带~的前缀配置都高千万别靠直觉以为后面写的配置会覆盖前面的。5.2 大文件上传统时中断php-fpm 和 Nginx 的双边超时有一次帮朋友调一个上传视频的功能前端那边传一个 200MB 的文件传到一半就报504 Gateway Timeout。排查发现是fastcgi_read_timeout没有调大。Nginx 把请求转给 PHP-FPM 后如果 PHP 执行超过默认的 60 秒没有返回Nginx 就会主动断开返回 504。除了改 Nginx 侧的fastcgi_read_timeout还要同步检查 PHP 侧两个配置; php.ini max_execution_time 300 post_max_size 256M upload_max_filesize 256M这三个参数必须同时满足缺一个都会出问题。post_max_size小于upload_max_filesize时大文件上传直接失败max_execution_time太小处理大文件时超时被杀。另外 PHP-FPM 还有一个容易被忽略的连接配置; php-fpm/www.conf request_terminate_timeout 300如果request_terminate_timeout没设或者设得比 php.ini 的max_execution_time小即使 PHP 脚本没超时PHP-FPM 也会在达到该时间后强制终止请求。这个参数超时监控里最难发现因为它不会出现在 PHP 的错误日志里看起来就像请求被静默掐断。5.3 502 排查的完整链路从一次半夜事故说起凌晨两点收到告警线上接口大面积 502。我当时的排查链路是这样的你可以直接抄作业。先看 Nginx error logtail -n 100 /usr/local/nginx/logs/error.log出现connect() to unix:/run/php-fpm/www.sock failed (11: Resource temporarily unavailable)。这个错误说明 Nginx 可以访问 socket但 PHP-FPM 已经没有能力接受新连接了也就是进程池被耗尽了。再看 PHP-FPM 的日志tail -n 100 /var/log/php-fpm/error.log日志里有一条WARNING: [pool www] seems busy (you may need to increase pm.start_servers, or pm.min/max_spare_servers)。紧接着一条ERROR: [pool www] server reached pm.max_children setting (30), consider raising it。到这里已经确认是进程池耗尽。为什么耗尽的我在高峰期用top查看 php-fpm 进程的RES内存发现单个进程占到了 150MB 左右——正常的 Laravel 项目空跑也就 80-100MB150MB 明显偏高。再进项目看慢日志; php-fpm/www.conf slowlog /var/log/php-fpm/slow.log request_slowlog_timeout 10s慢日志里出现了大量调用外部 HTTP 接口的记录有个第三方接口响应超过 30 秒导致每个 PHP-FPM 进程长时间阻塞在等待上。最终方案是给这个第三方接口调用加超时和熔断同时在 php-fpm 里把max_children上调到 50。这场事故给我的经验是max_children很小的时候会 502但盲目调大也只是推迟问题爆发真正的问题是单个请求占住进程的时间太长。要根治就得在上游接口耗时和慢代码上加东西。5.4 一些小的安全加固最终调优时顺手把几个安全细节也处理掉# 隐藏 Nginx 版本号 server_tokens off;# 限制单 IP 的请求频率 limit_req_zone $binary_remote_addr zoneperip:10m rate20r/s; location / { limit_req zoneperip burst30 nodelay; }; php.ini 里禁用危险函数 disable_functions system, exec, shell_exec, passthru, proc_open, popen, curl_execserver_tokens off会让Server响应头里不再带 Nginx 的主版本号limit_req对接口防刷非常有用10m 的 zone 可以存 16 万个 IP 地址rate20r/s表示每个 IP 每秒 20 次超出后按burst30 nodelay处理disable_functions里curl_exec是否禁用要看项目需求一些项目会用 curl 调第三方接口这个需要按实际情况取舍。这些加固项不复杂但会影响网站的暴露面和被恶意抓取时的稳定性。这套 LNMP 搭建和动静分离的方案我用它部署过好几个不同类型的项目从纯静态官网到 ThinkPHP 接口项目再到 Laravel 应用整体跑下来是稳定的。如果你也正在从能跑起来向知道为什么这样跑过渡卡住的时候多翻翻 access.log 和 error.log很多问题不是玄学是日志里的线索你没对上。下一篇我打算写 Nginx 反向代理和负载均衡里 upstream 的细节先把这一篇里的配置消化透再往上加东西就不会乱。