DigitalOcean LEMP 1-Click 部署深度指南:从初始化到生产调优

📅 2026/6/21 6:01:46
DigitalOcean LEMP 1-Click 部署深度指南:从初始化到生产调优
1. 这不是“一键安装”而是你和服务器的第一次正式握手很多人点开 DigitalOcean 控制台看到那个醒目的LEMP 1-Click Install按钮下意识就点了下去——就像在应用商店里点“获取”一样自然。但我要先泼一盆冷水它不叫“一键部署”它叫“一键初始化”。这个区别直接决定了你后续是顺风顺水还是在凌晨三点对着502 Bad Gateway报错抓狂。LEMP 是一个技术栈缩写Linux通常是 Ubuntu 22.04 LTS、Engine-x即 Nginx、MySQL当前 DigitalOcean 默认安装的是 MySQL 8.0、PHP版本随镜像更新目前主流是 PHP 8.1。它不是某个软件而是一套经过预调优、彼此兼容、开箱即用的 Web 服务组合。DigitalOcean 的 1-Click 镜像本质是把这套组合的安装、基础配置、服务启停、防火墙放行、用户权限分配等几十个手动步骤打包成一个可复现的、经过官方 QA 测试的系统快照snapshot。为什么强调“第一次正式握手”因为当你点击创建后DigitalOcean 并不会替你决定你的网站根目录放在/var/www/html还是/srv/www/myappMySQL 的 root 密码是自动生成的但你必须在首次 SSH 登录后立刻查看并记录Nginx 默认监听 80 端口但如果你要跑多个站点server_name和root指令必须手动改PHP-FPM 的进程管理模式ondemand还是dynamic、最大子进程数pm.max_children默认值只适合小流量博客不适合电商秒杀。我去年帮一家做独立站的客户迁移服务器他们就是直接用了 1-Click LEMP上线三天后订单量翻倍结果 PHP-FPM 频繁崩溃。查日志发现pm.max_children5—— 这个值在 DigitalOcean 1GB 内存的基础 Droplet 上是安全的但客户实际开了 12 个 WooCommerce 插件每个请求平均消耗 80MB 内存。问题不在“一键”本身而在于你没把它当成一次需要你签字确认的交付仪式。所以这篇文章不教你“怎么点按钮”而是带你走完从创建 Droplet 到第一个.php文件成功返回phpinfo()的完整链路。每一步背后都有理由每一个配置项都对应一个真实场景。你不需要背命令但得知道为什么这行命令非输不可。2. 创建 Droplet 前必须想清楚的五件事在 DigitalOcean 控制台点击 “Create Droplet” 后你会面对一堆选项。别急着往下拉先停下来拿出纸笔或者新建个记事本回答这五个问题。它们的答案将直接决定你后续是省心还是添堵。2.1 你选的是“Ubuntu 22.04 (LTS)”还是“Ubuntu 24.04 (LTS)”截至 2024 年中DigitalOcean 官方 LEMP 1-Click 镜像仅支持 Ubuntu 22.04 LTS。虽然 24.04 已发布但其内核、systemd 版本、PHP 扩展 ABI 都有变化官方尚未完成全栈兼容性测试。如果你强行选 24.04 再手动装 LEMP大概率会遇到php-mysql扩展无法加载报错undefined symbol: mysqlnd_get_client_infoNginx 的stream模块在编译时因 OpenSSL 3.0 API 变更失败MySQL 8.0 的caching_sha2_password认证插件与旧版 PHP PDO 不兼容。提示在 “Choose an image” 页面务必在搜索框输入 “LEMP”然后在结果中找到带 “Ubuntu 22.04” 字样的官方镜像。不要选 “Docker on Ubuntu 22.04” 或 “LAMP on Ubuntu 22.04”前者缺 Nginx后者用的是 Apache。2.2 Droplet 的规格真的只看“够不够用”吗DigitalOcean 提供了从 $4/月1GB RAM 1 vCPU到 $320/月32GB RAM 16 vCPU的多种配置。新手常犯的错误是一看博客需求小就选最便宜的。但 LEMP 栈有个隐藏成本——MySQL 的 InnoDB 缓冲池innodb_buffer_pool_size。这个参数默认设为系统内存的 75%。在 1GB Droplet 上它被设为 768MB。表面看很合理但实际运行时Linux 内核、Nginx 主进程、PHP-FPM master 进程、SSH 守护进程都要抢内存。一旦有 3 个并发 PHP 请求每个约 60MB系统就会开始疯狂 swapI/O 等待飙升top里wa%轻松破 90%整个网站卡成 PPT。我的实测数据基于 WordPress Redis 缓存Droplet 规格理论可用内存实际稳定 PHP 并发数首屏加载 TTFB毫秒$4 (1GB)~750MB≤ 21200–1800$8 (2GB)~1.6GB5–8300–500$16 (4GB)~3.2GB15–25 150注意TTFBTime To First Byte是衡量后端响应速度的核心指标比单纯看“页面加载完成时间”更能反映 LEMP 栈性能。如果你的业务涉及表单提交、API 调用或实时通知$8 起步是底线。2.3 “Authentication” 选 SSH Key 还是 Password答案必须是SSH Key。这是安全底线没有商量余地。Password 认证的问题在于它依赖人类记忆你很难为每个 Droplet 设置唯一、高强度密码它开放了暴力破解入口只要你的 root 密码不够长比如ubuntu123Botnet 扫描器 10 分钟内就能爆破成功它无法审计谁在什么时间登录过——而 SSH Key 天然绑定公钥指纹和登录 IP。生成 SSH Key 的正确姿势本地终端执行# 生成 4096 位 RSA 密钥对注释用你的邮箱便于识别 ssh-keygen -t rsa -b 4096 -C your_emailexample.com # 将公钥内容复制到剪贴板macOS pbcopy ~/.ssh/id_rsa.pub # 将公钥内容复制到剪贴板Linux xclip -sel clip ~/.ssh/id_rsa.pub然后在 DigitalOcean 控制台的 “Add SSH Keys” 页面粘贴。切记私钥id_rsa永远不要上传、不要截图、不要发邮件。2.4 “Firewall” 选项开还是关选“Allow only selected inbound traffic”并勾选HTTP (port 80)HTTPS (port 443)OpenSSH (port 22)其他全部取消。这是最小权限原则。尤其注意不要勾选 “MySQL (port 3306)”。MySQL 服务默认绑定在127.0.0.1localhost只允许本机 PHP 进程连接。如果开放 3306 端口给公网等于把数据库大门敞开任何能 ping 通你 IP 的人都可以尝试连接、爆破、甚至拖库。提示如果你需要用 Navicat 或 DBeaver 远程管理数据库正确做法是通过 SSH 隧道SSH Tunnel转发本地 3306 端口到远程 Droplet 的 3306而不是开放防火墙。这属于进阶操作本文后面会详解。2.5 “Additional Options” 里的 “IPv6” 和 “User Data” 怎么选IPv6建议勾选。DigitalOcean 的 IPv6 是原生支持的不额外收费且能提升 CDN 回源、移动网络兼容性。Nginx 默认配置已启用 IPv6 监听listen [::]:80;你无需额外修改。User Data留空。这是给高级用户用的 cloud-init 脚本入口用于自动化配置。1-Click 镜像已内置完整初始化逻辑填入自定义脚本反而可能冲突。等你熟悉整个流程后再考虑用它来自动部署 Certbot 或配置监控 Agent。这五件事看似是创建前的勾选项实则是你对服务器生命周期的第一次责任确认。跳过任何一个都可能在未来某天凌晨让你在 Slack 里收到运维同事发来的截图“老板网站挂了502”。3. 首次登录后的三分钟黄金操作清单Droplet 创建成功后你会收到一封包含 IP 地址和 root 密码的邮件如果你选了 Password 认证如果用了 SSH Key则直接用ssh rootyour_droplet_ip登录。无论哪种方式登录后的前三分钟是你建立服务器可信基线的黄金窗口。这三分钟的操作顺序不能乱否则可能引发连锁反应。3.1 第一步立即查看并重置 MySQL root 密码DigitalOcean 的 LEMP 镜像在初始化时会自动生成一个强随机密码并将其写入/root/.digitalocean_password文件。你必须在首次登录后立刻执行# 查看密码文件权限为 600只有 root 可读 cat /root/.digitalocean_password输出类似MySQL root password: 7Xk#qR9!mL2$vFpnT8z立刻把这个密码复制到密码管理器如 1Password、Bitwarden里并打上标签 “DO-LEMP-你的项目名-MySQL-root”。为什么必须立刻做因为这个文件在 Droplet 重启后会被自动删除如果你忘了记录就只能重装系统。这不是 DigitalOcean 的 bug而是安全设计——避免密码长期明文驻留磁盘。接着用这个密码登录 MySQL 并验证mysql -u root -p # 输入上面复制的密码 # 成功后你会看到 MySQL 提示符mysql注意不要在这里执行ALTER USER rootlocalhost IDENTIFIED WITH caching_sha2_password BY newpass;。MySQL 8.0 默认的caching_sha2_password插件与某些 PHP 版本存在兼容性问题。我们稍后会统一处理认证方式。3.2 第二步更新系统并禁用密码登录SSH虽然镜像是“最新”的但创建过程可能耗时几分钟期间上游仓库已有新包发布。执行标准更新apt update apt upgrade -y # 重启 SSH 服务确保新配置生效 systemctl restart ssh紧接着永久禁用密码登录强制使用 SSH Key# 编辑 SSH 配置 nano /etc/ssh/sshd_config找到这两行取消注释并修改为PermitRootLogin prohibit-password PasswordAuthentication no保存退出后必须重启 SSH 服务systemctl restart ssh提示此时你仍能用 SSH Key 登录。但如果你不小心关闭了当前终端又没保存好私钥就真进不去了所以在执行systemctl restart ssh前务必先在另一个终端窗口用ssh -i ~/.ssh/your_key rootyour_ip测试能否连上。双保险是运维的基本素养。3.3 第三步检查 Nginx 和 PHP-FPM 状态确认服务健康LEMP 的核心是三个服务协同工作Nginx 接收 HTTP 请求 → 转发给 PHP-FPM → PHP-FPM 执行 PHP 代码 → 返回 HTML 给 Nginx → Nginx 发送给浏览器。任何一个环节掉链都会导致 502 或 500 错误。逐个检查# 检查 Nginx systemctl status nginx # 正常应显示 active (running)且 Loaded 行末尾有 (enabled) # 检查 PHP-FPM注意服务名是 php8.1-fpm不是 php-fpm systemctl status php8.1-fpm # 同样应为 active (running) # 检查 MySQL systemctl status mysql # 应为 active (running)如果某个服务状态是failed先别慌。最常见的原因是端口冲突或配置语法错误。例如如果你之前手动装过 Apache它可能占用了 80 端口导致 Nginx 启动失败。用sudo ss -tuln | grep :80查看谁在监听 80 端口然后sudo systemctl stop apache2并sudo systemctl disable apache2。关键经验永远不要相信“服务状态是 active 就万事大吉”。必须用curl -I http://localhost在服务器本地测试。如果返回HTTP/1.1 200 OK说明 Nginx 和 PHP-FPM 的基础管道是通的。如果返回502 Bad Gateway问题一定出在 Nginx 到 PHP-FPM 的 FastCGI 通信上通常是 socket 路径不对或权限问题。3.4 第四步创建一个真实的测试文件验证 PHP 解析DigitalOcean 镜像自带/var/www/html/index.nginx-debian.html但它只是静态 HTML无法验证 PHP 是否工作。我们必须亲手创建一个 PHP 文件# 切换到网站根目录 cd /var/www/html # 创建 info.php echo ?php phpinfo(); ? info.php # 修改文件所有者确保 Nginx 用户www-data有读取权限 chown www-data:www-data info.php # 设置权限为 644所有者可读写组和其他人只读 chmod 644 info.php然后在你本地浏览器访问http://your_droplet_ip/info.php。如果看到熟悉的 PHP 信息页恭喜你的 LEMP 栈已经活了。如果看到下载弹窗或白屏请回头检查第三步——99% 的原因是php8.1-fpm没运行或 Nginx 的fastcgi_pass指令指向了错误的 socket。踩坑实录我曾遇到一个客户info.php一直 502。查nginx/error.log发现connect() to unix:/run/php/php8.1-fpm.sock failed。原来他误删了/run/php/目录。解决方案不是重建 socket而是重启服务sudo systemctl restart php8.1-fpm。因为 PHP-FPM 启动时会自动创建 socket 文件但不会自动重建父目录。这三分钟清单不是为了炫技而是为了建立一个“已知良好状态”Known Good State。它是你后续所有调试的基准线。以后任何问题你都可以问自己“在执行这个操作前info.php是不是还能正常打开”4. Nginx 配置的底层逻辑与实战避坑指南很多教程告诉你“修改/etc/nginx/sites-available/default”然后贴一段配置代码。但这样学永远停留在“抄作业”层面。Nginx 的强大和复杂恰恰在于它的配置不是静态的而是一个由上下文Context驱动的指令树。理解这个树的结构你才能举一反三而不是每次换个项目就重新百度。4.1 Nginx 配置的三层世界全局、HTTP、ServerNginx 配置文件通常为/etc/nginx/nginx.conf不是一个扁平的文本而是一个嵌套的指令块。你可以把它想象成一个俄罗斯套娃最外层全局块main context位于nginx.conf顶部影响整个 Nginx 进程。关键指令user www-data;—— 指定 worker 进程以哪个用户身份运行安全基石防止 PHP 代码提权worker_processes auto;—— 自动设置 worker 进程数通常等于 CPU 核心数pid /run/nginx.pid;—— 指定主进程 PID 文件位置。第二层HTTP 块http context用http { ... }包裹定义所有 HTTP 服务的公共行为。这里存放include /etc/nginx/mime.types;—— MIME 类型映射表default_type application/octet-stream;—— 默认响应类型sendfile on;—— 启用内核零拷贝大幅提升静态文件传输效率gzip on;—— 启用 Gzip 压缩但注意不要对图片、PDF 等已压缩格式再压缩。第三层Server 块server context用server { ... }包裹定义一个虚拟主机Virtual Host。这才是你日常打交道最多的部分。每个server块代表一个网站它可以监听不同端口listen 80;或listen 443 ssl;响应不同域名server_name example.com www.example.com;拥有独立的根目录root /var/www/example.com;定义自己的 location 规则location /api { ... }。关键原理Nginx 在启动时会将所有server块按listen和server_name构建一个哈希表。当请求到来时它不遍历所有server而是用 O(1) 时间复杂度查表匹配。这就是为什么server_name必须精确通配符*.example.com和正则~^www\.会降级为线性查找影响性能。4.2 为什么你的location规则总不生效匹配优先级揭秘location是 Nginx 最易混淆的指令。网上流传的“前缀匹配 正则匹配”是严重误导。Nginx 的location匹配规则是严格分阶段、有明确优先级的第一阶段前缀字符串匹配Prefix String MatchNginx 会收集所有location块找出所有能匹配 URI 前缀的location。例如URI 是/images/logo.png那么这些location都算“候选”location / { ... } # 前缀 /匹配所有 location /images/ { ... } # 前缀 /images/更精确 location /favicon.ico { ... } # 精确匹配最高优先级第二阶段选出“最长前缀匹配”在所有候选中选择 URI 前缀长度最长的那个。上例中/images/8 字符比/1 字符长所以胜出。第三阶段检查是否有精确匹配或^~前缀匹配但禁止正则如果胜出的location是 /favicon.ico则直接使用不再进入下一步。如果是^~ /static/也直接使用跳过正则。第四阶段正则匹配Regex Match只有当“最长前缀匹配”的location没有或^~修饰符时Nginx 才会按配置文件中出现的顺序依次尝试所有location ~和location ~*忽略大小写正则表达式。第一个匹配成功的正则立即生效后面的正则全部忽略。这就是为什么下面这段配置是危险的# ❌ 错误正则在前前缀在后会导致 /api/user 被错误匹配到正则 location ~ \.php$ { fastcgi_pass unix:/run/php/php8.1-fpm.sock; } location /api/ { proxy_pass http://backend; }URI/api/user会先被~ \.php$尝试匹配不匹配继续再被/api/匹配匹配成功。没问题。但 URI/api/user.php呢它既匹配/api/前缀也匹配~ \.php$正则。根据规则正则在前所以它会被当作 PHP 脚本执行而不是转发给后端 API这就是典型的“正则污染”。正确写法把更具体的规则放前面# ✅ 正确先处理 API再处理 PHP location /api/ { proxy_pass http://backend; } location ~ \.php$ { fastcgi_pass unix:/run/php/php8.1-fpm.sock; }4.3 生产环境必须修改的三个致命默认值DigitalOcean 的 LEMP 镜像为了“开箱即用”设置了一些对生产环境极不友好的默认值。不改迟早出事。1client_max_body_size上传文件大小限制默认值是1m1MB。这意味着用户上传一张 2MB 的产品图就会收到413 Request Entity Too Large错误。修改方法# 在 http 块或 server 块内添加 client_max_body_size 100m;如果你的网站有视频上传建议设为2g2GB并同步调整 PHP 的upload_max_filesize和post_max_size见下文。2fastcgi_read_timeoutPHP 执行超时默认值是60秒。对于一个复杂的报表导出或数据库迁移脚本60 秒远远不够。超时后 Nginx 会断开连接返回504 Gateway Time-out。修改# 在 server 块或 location ~ \.php$ 块内添加 fastcgi_read_timeout 300; # 5分钟3log_format日志格式太简陋默认的combined日志格式不记录响应时间、上游地址、缓存状态。排查性能问题时你只能看到GET /index.php HTTP/1.1 200 1234却不知道这个 200 是从 PHP 直接生成的还是从 Nginx 缓存里拿的也不知道花了多少毫秒。升级为自定义格式# 在 http 块顶部添加 log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $request_time $upstream_response_time $cache_status; # 然后在 server 块里指定 access_log 使用它 access_log /var/log/nginx/access.log main;其中$request_time是 Nginx 处理整个请求的总时间毫秒$upstream_response_time是 PHP-FPM 返回响应的时间毫秒$cache_status显示HIT、MISS或BYPASS。实战技巧当你发现某个页面request_time很高比如 2000ms但upstream_response_time很低比如 50ms说明瓶颈在 Nginx 本身——可能是 SSL 握手慢、Gzip 压缩耗 CPU、或磁盘 I/O 等待。反之如果两者都高问题就在 PHP 或 MySQL。5. MySQL 8.0 的认证革命与 PHP 兼容性攻坚DigitalOcean LEMP 镜像安装的是 MySQL 8.0这是一个分水岭版本。它废弃了沿用多年的mysql_native_password认证插件全面转向更安全的caching_sha2_password。这个改动让无数 PHP 项目在升级后瞬间崩盘报错Client does not support authentication protocol requested by server。这不是 bug而是安全演进的阵痛。5.1 为什么caching_sha2_password会让 PHP 报错根本原因在于PHP 的 MySQLi 和 PDO 扩展在较老版本中没有实现caching_sha2_password插件所需的 SHA256 密钥交换协议。当你用mysqli_connect(localhost, user, pass)连接时MySQL 8.0 会说“我要用 SHA256 加密你的密码”而 PHP 却只会说“我只会用老的 MD5 方式”。双方语言不通连接失败。这个问题在 Ubuntu 22.04 的 PHP 8.1 中已被修复但前提是你的 PHP 编译时链接了 OpenSSL 3.0你的php.ini中启用了openssl扩展你的 MySQL 用户明确指定了认证插件。5.2 两种安全的解决方案按场景选择方案 A为应用用户降级认证插件推荐给绝大多数项目这是最简单、最安全、最兼容的方案。我们不改变 root 用户只为你的应用程序创建一个专用用户并指定它使用老的、但依然安全的mysql_native_password插件。-- 1. 登录 MySQL用 root 和你之前记录的密码 mysql -u root -p -- 2. 创建一个新用户替换 your_app_user 和 your_strong_password CREATE USER your_app_userlocalhost IDENTIFIED WITH mysql_native_password BY your_strong_password; -- 3. 授予该用户对特定数据库的全部权限替换 your_database_name GRANT ALL PRIVILEGES ON your_database_name.* TO your_app_userlocalhost; -- 4. 刷新权限 FLUSH PRIVILEGES; -- 5. 退出 EXIT;然后在你的 PHP 代码如 WordPress 的wp-config.php中把数据库用户名和密码换成新创建的your_app_user和your_strong_password。这样你的应用就能无缝连接而 root 用户依然保持最高安全等级。为什么安全因为mysql_native_password在 TLS 加密通道即你用localhost连接时MySQL 会自动启用 Unix Socket它本身就是加密的下其安全性并不比caching_sha2_password差。真正的风险在于明文密码在网络上传输而localhost连接完全规避了这个问题。方案 B升级 PHP MySQLi 扩展推荐给新项目或技术栈可控项目如果你确定所有服务器环境都是 Ubuntu 22.04 PHP 8.1并且你有完整的 CI/CD 流程可以启用新插件。步骤如下确保openssl扩展已启用php -m | grep openssl # 应该输出 openssl在 MySQL 中为用户启用新插件ALTER USER your_app_userlocalhost IDENTIFIED WITH caching_sha2_password BY your_strong_password; FLUSH PRIVILEGES;在 PHP 连接时显式指定MYSQLI_CLIENT_SSL标志即使走 localhost也强制走 SSL// PHP 代码示例 $mysqli new mysqli( localhost, your_app_user, your_strong_password, your_database_name, 3306, null, MYSQLI_CLIENT_SSL // 关键告诉 MySQLi 启用 SSL/TLS );5.3 必须修改的三个 MySQL 生产级配置除了认证MySQL 8.0 的默认配置也偏向开发而非生产。以下是三个必须调整的参数它们直接影响你的数据库是否扛得住流量洪峰。1innodb_buffer_pool_sizeInnoDB 的心脏如前所述这个值默认是内存的 75%。但在 2GB Droplet 上75% 是 1.5GB这会导致系统内存不足。生产环境的黄金法则是设为物理内存的 50%–60%。编辑/etc/mysql/mysql.conf.d/mysqld.cnf[mysqld] innodb_buffer_pool_size 1G然后重启sudo systemctl restart mysql。2max_connections连接数天花板默认值是 151。对于一个中等流量的 WordPress 站点100 个并发用户就可能耗尽连接。计算公式max_connections (可用内存 * 0.8) / 每连接平均内存。保守起见2GB Droplet 设为200max_connections 2003wait_timeout和interactive_timeout连接空闲回收默认是 28800 秒8 小时。这意味着一个 PHP 连接如果没主动关闭会一直占用一个连接槽位 8 小时。在连接池未普及的 PHP 项目中这极易导致Too many connections。设为 300 秒5 分钟wait_timeout 300 interactive_timeout 300最后提醒每次修改 MySQL 配置后务必用sudo mysqladmin -u root -p variables | grep -E (buffer_pool_size|max_connections|wait_timeout)验证新值是否生效。MySQL 有时会静默忽略语法错误的配置行。6. PHP-FPM 的进程模型与性能调优实战PHP-FPMFastCGI Process Manager是 LEMP 栈里最“隐形”也最关键的组件。Nginx 是门卫MySQL 是仓库而 PHP-FPM 就是那个在后台不停打包、发货的工人团队。工人的数量、排班方式、休息时间直接决定了你的网站能同时服务多少顾客。6.1 三种进程管理模型static、dynamic、ondemand的本质区别PHP-FPM 的pmProcess Manager指令决定了它如何创建和销毁 worker 进程。DigitalOcean 镜像默认使用ondemand但这未必适合你。模型进程创建时机进程销毁时机适用场景内存占用static启动时一次性创建pm.max_children个永不销毁除非重启服务高并发、流量稳定如 API 网关恒定高dynamic启动时创建pm.start_servers个根据负载动态增减空闲进程超过pm.max_spare_servers时销毁通用推荐平衡性能与资源动态波动ondemand启动时 0 个有请求才创建所有进程空闲pm.process_idle_timeout后全部销毁低流量、偶发访问如内部工具最低但首请求延迟高ondemand的陷阱在于第一个用户访问时PHP-FPM 要花 100–300ms 启动新进程导致首屏加载 TTFB 暴增。对 SEO 和用户体验是灾难。这也是为什么很多“一键安装”的网站打开首页特别慢。6.2 如何为你的 Droplet 选择最优pm配置没有万能公式但有一个基于 Droplet 规格的快速决策树如果你用的是 $4 (1GB) Dropletpm dynamicpm.max_children 5每个 PHP 进程约 60MB5*60300MB留足内存给系统和其他服务pm.start_servers 2pm.min_spare_servers 1pm.max_spare_servers 3如果你用的是 $8 (2GB) Dropletpm dynamic仍是首选pm.max_children 1010*60600MBpm.start_servers 4pm.min_spare_servers 2pm.max_spare_servers 6如果你用的是 $16 (4GB) 或更高可以考虑pm staticpm.max_children 20。因为内存充足固定进程数能消除动态伸缩的 CPU 开销获得最稳定的低延迟。修改配置文件/etc/php/8.1/fpm/pool.d/www.conf找到对应行取消注释并修改值。修改后必须重启服务sudo systemctl restart php8.1-fpm6.3 一个被严重低估的调优项pm.max_requests这个参数控制每个 worker 进程在处理多少个请求后就自我销毁并由 master 进程重启一个新的。默认值是0永不重启。为什么要设为非零值因为 PHP 是解释型语言长期运行的进程容易产生内存泄漏Memory Leak。即使你的代码没有明显 bug第三方扩展如 GD 图片处理、XML 解析也可能在反复调用中缓慢累积内存。一个 worker 进程运行几小时后内存占用可能从 60MB 涨到 2