Ubuntu22.04 LNMP生产搭建避坑指南
发布日期: 2026/07/26 阅读总量: 0

这破服务器又炸了——一个周五晚上的教训

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.188.13分钟低(apt upgrade)
编译安装1.26.18.3.845分钟高(手动编译)
apt + PPA1.26.18.3.85分钟

最终我选择添加官方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安装(无优化)10010000452221.35800.12%
按本文优化后100100001,23880.52100%
按本文优化 + OpCache + JIT100100001,87453.21450%

关键优化项贡献排序:

  1. PHP OpCache + JIT 开启(QPS提升约51%)
  2. MySQL innodb_buffer_pool_size 从默认128M改为4G(DB查询时间从12ms降到2ms)
  3. Nginx worker_connections从768改为4096,启用keepalive_requests(连接复用率提升40%)
  4. 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,防止频繁交换。