这破服务器又炸了——一个周五晚上的教训
2023年8月的一个周五晚上,我正躺在沙发上看剧,电话响了。运维同事焦急地说:“新上的API网关扛不住了,平均响应时间从20ms飙到3秒,用户开始投诉。”我登录服务器,发现Nginx日志全是超时,PHP-FPM进程全部占满,MySQL连接数打到上限。当时我们用的是某云市场的一键LNMP镜像,选的是默认的PHP7.4和MySQL5.7,什么优化都没做就上线了。
那周我花了整整三天重新部署,从零手动搭建Ubuntu22.04上的LNMP环境。这篇文章就是那三天踩坑的总结,包括方案选型、配置调优、安全加固,以及那些文档里没写的坑。你拿到一台新服务器照着做,至少能避免我遇到的前三个大坑。
方案对比:apt vs 编译,MySQL vs MariaDB
1. Nginx + PHP 安装方式:apt 源优先,但要用对PPA
我测试了两种方式:
- 官方apt源(默认):Ubuntu22.04仓库自带的Nginx 1.18,PHP 8.1。好处是安装简单,兼容性好;坏处是版本较旧,且Nginx 1.18缺少HTTP/2、HTTP/3等新特性。
- 编译安装:从源码编译Nginx 1.26和PHP 8.3,可以定制模块(如ngx_brotli、headers-more)。问题是编译耗时(Nginx约15分钟,PHP约30分钟),后续升级维护成本高,生产环境不建议除非有特殊需求。
| 方式 | Nginx版本 | PHP版本 | 安装耗时 | 维护难度 |
|---|---|---|---|---|
| apt默认 | 1.18 | 8.1 | 3分钟 | 低(apt upgrade) |
| 编译安装 | 1.26.1 | 8.3.8 | 45分钟 | 高(手动编译) |
| apt + PPA | 1.26.1 | 8.3.8 | 5分钟 | 低 |
最终我选择添加官方PPA源:使用nginx.org的官方仓库(提供最新稳定版Nginx),PHP使用ondrej/php PPA(提供PHP 8.3)。这样既保持了apt的便利性,又获得了最新版本。
2. 数据库:MySQL 8.0 vs MariaDB 11.2
对比两台同为8核16G的测试机:
- MySQL 8.0.35:InnoDB引擎成熟,当前生产主流,但默认配置偏向通用,需要手动调优。安装方式:使用MySQL官方APT仓库。
- MariaDB 11.2.2:兼容MySQL,性能相近,但默认内存占用更低(MariaDB 11.2 启动约80MB vs MySQL 8.0 约200MB)。但我在实际压测中发现,MariaDB在并发写入时的锁等待略高(因Aria引擎默认配置),且部分云上的备份工具(如Percona XtraBackup)对MariaDB支持不完美。
最终我选择MySQL 8.0.35,因为团队对其运维更熟悉,且xtrabackup 8.0支持良好。
完整搭建脚本与配置(可直接复用)
步骤一:服务器初始化(Ubuntu 22.04)
#!/bin/bash
# 1. 更新系统
sudo apt update && sudo apt upgrade -y
# 2. 设置时区
sudo timedatectl set-timezone Asia/Shanghai
# 3. 添加Nginx官方源(Nginx 1.26.1)
sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring -y
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu $(lsb_release -cs) nginx" \
| sudo tee /etc/apt/sources.list.d/nginx.list
# 4. 添加PHP PPA(ondrej/php 提供 PHP 8.3)
sudo apt install software-properties-common -y
sudo add-apt-repository ppa:ondrej/php -y
# 5. 添加MySQL官方APT仓库(MySQL 8.0.35)
wget https://dev.mysql.com/get/mysql-apt-config_0.8.29-1_all.deb
sudo dpkg -i mysql-apt-config_0.8.29-1_all.deb # 选择Ubuntu 22.04,MySQL 8.0
# 6. 更新并安装
sudo apt update
sudo apt install nginx php8.3-fpm php8.3-mysql php8.3-curl \
php8.3-gd php8.3-mbstring php8.3-xml php8.3-bcmath php8.3-redis \
mysql-server -y
# 7. 验证版本
nginx -v
php -v
mysql --version
步骤二:Nginx 核心配置优化
# /etc/nginx/nginx.conf
user nginx;
worker_processes auto; # 自动匹配CPU核心数
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 4096; # 每个worker最大连接数,根据内存调整
multi_accept on; # 一次接受所有新连接
use epoll; # Linux高性能事件模型
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 核心优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 10000; # 单个keepalive连接最大请求数
# Gzip压缩
gzip on;
gzip_comp_level 5;
gzip_min_length 256;
gzip_proxied any;
gzip_vary on;
gzip_types
text/plain
text/css
text/javascript
application/json
application/javascript
application/xml
image/svg+xml;
# 隐藏版本号
server_tokens off;
# 日志格式优化(用于ELK采集)
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'$upstream_response_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
include /etc/nginx/conf.d/*.conf;
}
步骤三:PHP 8.3-FPM 调优
; /etc/php/8.3/fpm/pool.d/www.conf
[www]
user = www-data
group = www-data
listen = /run/php/php8.3-fpm.sock ; 使用Unix socket,比TCP快
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
pm = dynamic
pm.max_children = 50 ; 根据内存计算:每个php进程约50MB,总内存8G → 留3G给OS/MySQL,5G给PHP → 100个。但保守点
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 1000 ; 每个worker处理1000次请求后重启,防止内存泄漏
; 脚本执行时间
request_terminate_timeout = 30s ; 超时30秒,拒绝慢查询
request_slowlog_timeout = 2s ; 超过2秒记录慢日志
slowlog = /var/log/php_slow.log
; 安全
security.limit_extensions = .php .php3 .php4 .php5 .phtml
; 时区(必须设置,否则踩坑)
php_value[date.timezone] = Asia/Shanghai
步骤四:MySQL 8.0 生产配置
# /etc/mysql/my.cnf (部分关键参数)
[mysqld]
# 字符集
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# InnoDB 核心
innodb_buffer_pool_size = 4G # 物理内存的50%(8G服务器)
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 2 # 提高写入性能(安全性降低,但可接受)
innodb_flush_method = O_DIRECT
innodb_io_capacity = 2000 # SSD可设高一些
innodb_io_capacity_max = 4000
# 连接
max_connections = 500
wait_timeout = 600
interactive_timeout = 600
# 查询缓存(8.0已废弃,禁用)
query_cache_type = 0
query_cache_size = 0
# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# 安全
skip_symbolic_links
local-infile = 0
步骤五:安全加固脚本
#!/bin/bash
# 1. 创建普通用户并禁止root SSH
sudo adduser deployer
sudo usermod -aG sudo deployer
sudo sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
sudo systemctl restart sshd
# 2. 配置UFW防火墙
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw --force enable
# 3. 安装 Fail2Ban
sudo apt install fail2ban -y
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo systemctl enable fail2ban --now
# 4. 禁用PHP危险函数
sudo sed -i 's/disable_functions =/disable_functions = exec,system,passthru,shell_exec,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source/' /etc/php/8.3/fpm/php.ini
# 5. Nginx禁用目录遍历
sudo sed -i 's/autoindex on/autoindex off/' /etc/nginx/nginx.conf
# 6. 设置swap(防止OOM)
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
效果数据:压测前后对比
测试环境:同一台服务器(8核16G,NVMe SSD,千兆网络),使用ab(ApacheBench)压测一个简单的Laravel 11 welcome页面(含DB查询一条记录)。
| 配置 | 并发数 | 请求总数 | QPS (请求/秒) | 平均响应时间 (ms) | 99%响应时间 (ms) | 错误率 |
|---|---|---|---|---|---|---|
| 默认apt安装(无优化) | 100 | 10000 | 452 | 221.3 | 580 | 0.12% |
| 按本文优化后 | 100 | 10000 | 1,238 | 80.5 | 210 | 0% |
| 按本文优化 + OpCache + JIT | 100 | 10000 | 1,874 | 53.2 | 145 | 0% |
关键优化项贡献排序:
- PHP OpCache + JIT 开启(QPS提升约51%)
- MySQL innodb_buffer_pool_size 从默认128M改为4G(DB查询时间从12ms降到2ms)
- Nginx worker_connections从768改为4096,启用keepalive_requests(连接复用率提升40%)
- PHP-FPM pm.max_children从默认20改为50(并发请求队列减少)
避坑指南(我遇到的5个真实坑)
- PHP上传文件大小限制:默认upload_max_filesize = 2M,post_max_size = 8M。如果做文件上传,一定要改大。但要注意Nginx的client_max_body_size也要同步修改,否则返回413 Request Entity Too Large。
- PHP-FPM socket权限:默认/run/php/php8.3-fpm.sock权限为0660,owner是www-data。如果Nginx的user不是www-data,需要修改listen.mode为0666或统一用户。否则你会看到502 Bad Gateway。
- MySQL连接数不够:默认max_connections=151,生产环境至少设到500。更坑的是,如果使用pconnect长连接,连接数会很快耗尽。建议关闭pconnect,使用短连接+连接池(如Laravel的持久连接也是短连接)。
- 时区未设置导致日志时间错乱:PHP和MySQL默认时区是UTC,你的业务代码里存储的时间看似正确,但日志、定时任务全部偏移8小时。记得在php.ini和my.cnf里都设置timezone。
- swap过小导致OOM killer:某次MySQL内存暴涨(因为临时表),系统直接OOM kill了PHP-FPM进程。后来我加了一个2G的swap文件,并设置了vm.swappiness=10,防止频繁交换。