3.14 LAMP/LEMP Web 环境完整搭建

预计阅读时间:14 分钟

📖 目录

学习目标

学完本章后,你将能够:

  • 在一台全新的 Linux 服务器上完整搭建 LAMP(Apache + MySQL + PHP)或 LEMP(Nginx + MySQL + PHP)环境
  • 配置 Nginx 虚拟主机(server block)和 Apache 虚拟主机(VirtualHost)
  • 调优 PHP-FPM 进程管理和 OPcache 以获得最佳性能
  • 为 Web 站点配置 HTTPS(Let's Encrypt)
  • 部署 WordPress 等真实 PHP 应用并排查常见问题

核心知识

  • LAMP — Linux + Apache + MySQL + PHP,传统 Web 栈,.htaccess 强大,适合兼容性优先场景
  • LEMP — Linux + Nginx + MySQL + PHP,Nginx 事件驱动架构,高并发性能更好
  • PHP-FPM — FastCGI Process Manager,PHP 的进程管理器,替代 mod_php 独立运行
  • OPcache — PHP 字节码缓存,将编译后的 PHP 脚本缓存在内存中,可提升 2-3 倍性能
  • 虚拟主机(Virtual Host) — 同一台服务器上托管多个域名网站
  • fastcgi_pass — Nginx 将 PHP 请求转发给 PHP-FPM 监听的 socket

知识关联

原理讲解

Web 栈的请求流程

一个用户访问 PHP 站点的完整请求路径:
浏览器 → DNS → Nginx/Apache(监听 80/443)→ PHP-FPM(处理 PHP)→ MySQL(查询数据)→ PHP 渲染 HTML → 返回浏览器

传统 LAMP 用 mod_php 将 PHP 嵌入 Apache 进程,请求不需要额外的进程间通信,配置简单但在高并发下 Apache 进程数会暴涨。LEMP 的 Nginx 只负责静态资源和请求代理,PHP 请求通过 fastcgi 协议转发给独立的 PHP-FPM 进程池处理——Nginx 本身占用的内存极少,可轻松支撑数千并发连接。

LAMP vs LEMP 选型

  • 兼容性优先→LAMP:Apache 的 .htaccess 让应用开发者可以自行覆盖配置,WordPress 生态中有大量插件依赖 .htaccess
  • 性能优先→LEMP:Nginx 事件驱动的架构在高并发下表现更好,内存占用远低于 Apache
  • 运维习惯:用 Apache 多年且团队熟悉的团队可继续用 LAMP;新项目推荐 LEMP

LAMP vs LEMP 性能对比

同样硬件下用 ab(Apache Bench)压静态页与 PHP 页,差距明显。以下数据基于 2 核 4G 内存、Ubuntu 24.04、默认配置测得(压测方法见 4.6:Linux 性能调优):

测试项LAMP(Apache 2.4 + mod_php)LEMP(Nginx 1.24 + PHP-FPM)
静态文件 QPS(100 并发)约 5,000-8,000约 20,000-35,000
PHP 动态页 QPS约 400-600约 450-700(瓶颈在 PHP 本身)
1000 并发下内存占用1.5-2.5GB(进程数随连接暴涨)200-400MB(worker 数量固定)
峰值并发支撑数百连接后开始排队数万连接(事件驱动)

结论:PHP 动态请求两者差距不大(瓶颈都在 PHP 解释器),差距集中在静态资源和连接承载能力上。纯 PHP 应用选型不必纠结性能;静态资源多、并发连接多的站点(图片站、接口网关、动静分离架构)选 LEMP 优势明显。

示例代码

安装 LEMP(推荐)

# 安装 Nginx + MySQL + PHP-FPM(Ubuntu 24.04)
sudo apt update  # 输出: Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
sudo apt install nginx mysql-server php8.3-fpm php8.3-mysql \
  php8.3-curl php8.3-mbstring php8.3-xml php8.3-zip php8.3-gd  # 输出: Setting up nginx (1.24.0) ...

# 验证各组件状态
sudo systemctl status nginx mysql php8.3-fpm  # 输出: ● nginx.service - A high performance web server
nginx -t  # 输出: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok

配置 Nginx 虚拟主机

# /etc/nginx/sites-available/example.com
server {
    listen 80;
    server_name example.com www.example.com;
    root /var/www/example.com;
    index index.php index.html;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

# 启用站点
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo systemctl reload nginx

安装 LAMP(备选)

sudo apt install apache2 mysql-server php8.3 libapache2-mod-php8.3 php8.3-mysql
sudo a2enmod rewrite  # 输出: Enabling module rewrite.
sudo systemctl restart apache2

# Apache 虚拟主机(/etc/apache2/sites-available/example.com.conf)
# 
#     ServerName example.com
#     DocumentRoot /var/www/example.com
# 
sudo a2ensite example.com  # 输出: Enabling site example.com.
sudo systemctl reload apache2

MySQL 初始化与安全加固

# 首次安装后立即加固
sudo mysql_secure_installation
# 依次回答:设置密码强度 → 移除匿名用户 → 禁止 root 远程登录 → 删除 test 库 → 刷新权限

# 创建业务专用账号(绝不使用 root 跑应用)
APP_PASS=$(openssl rand -base64 18)
echo "app 用户密码: $APP_PASS"   # 打印出来,保存到密码管理器
sudo mysql -e "CREATE DATABASE app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
sudo mysql -e "CREATE USER 'app'@'localhost' IDENTIFIED BY '$APP_PASS';"
sudo mysql -e "GRANT ALL PRIVILEGES ON app.* TO 'app'@'localhost';"
sudo mysql -e "FLUSH PRIVILEGES;"

# 只监听本机(Web 与数据库同机时,禁止 3306 暴露公网)
# /etc/mysql/mysql.conf.d/mysqld.cnf
#   bind-address = 127.0.0.1
sudo systemctl restart mysql

PHP-FPM 池配置详解

pm 三种模式对应三种流量特征,选错会导致"内存不足 OOM"或"高峰期大量请求排队":

模式进程管理方式优点缺点适用
static固定启动 pm.max_children 个进程,不增不减无创建/销毁开销,响应最稳定低流量时也占满内存流量平稳、内存充足
dynamic在 start/min/max 之间动态伸缩(默认)兼顾峰值与空闲峰值瞬间进程创建偏慢多数生产环境(推荐)
ondemand有请求才创建进程,空闲超时后回收最省内存,空闲时近零占用突发流量下创建风暴,首请求慢低流量/内存紧张的小机器
# /etc/php/8.3/fpm/pool.d/www.conf —— 多站建议各自建池,故障相互隔离
[blog]                              # 池名
user = blog                         # 为每个站点建独立系统用户
group = blog
listen = /run/php/php8.3-fpm-blog.sock
pm = dynamic
pm.max_children = 30                # 估算:4G 内存扣除系统与 MySQL 后,单进程约 40MB
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 1000              # 防内存泄漏:处理 1000 次请求后重建
request_slowlog_timeout = 5s
slowlog = /var/log/php8.3-fpm-blog-slow.log
php_admin_value[memory_limit] = 256M

php.ini 关键参数(/etc/php/8.3/fpm/php.ini):

参数默认值生产建议说明
memory_limit128M256M-512M单请求最大内存,过小会导致 500 错误
upload_max_filesize2M64M(按业务)上传文件大小上限
post_max_size8M≥ upload_max_filesizePOST 请求体上限,必须 ≥ 上传上限
max_execution_time3030-120单请求最长执行秒数
date.timezone未设置Asia/Shanghai不设置会报时区警告
expose_phpOnOff隐藏 X-Powered-By 头,防指纹探测
# 修改后重载生效(无需重启 Nginx)
php8.3-fpm -t                  # 先检查配置语法
sudo systemctl reload php8.3-fpm

PHP-FPM 调优

# /etc/php/8.3/fpm/pool.d/www.conf 关键参数
pm = dynamic                          # 动态进程管理
pm.max_children = 50                  # 最大子进程数(由内存决定,每个约 30-50MB)
pm.start_servers = 10                 # 启动时创建的子进程数
pm.min_spare_servers = 5              # 空闲最少进程数
pm.max_spare_servers = 15             # 空闲最多进程数
pm.max_requests = 500                 # 每个子进程处理 500 次请求后重启(防内存泄漏)

# OPcache 配置(/etc/php/8.3/fpm/conf.d/10-opcache.ini)
opcache.enable=1
opcache.memory_consumption=128        # OPcache 共享内存大小(MB)
opcache.max_accelerated_files=10000   # 最多缓存的文件数

Nginx 反向代理 PHP 的细节:location 与 fastcgi_params

Nginx 通过 FastCGI 协议把 PHP 请求交给 PHP-FPM。搞清楚 location 匹配规则和 fastcgi_params 的作用,是排查"PHP 文件被下载""POST 数据为空"这类问题的前提。

location 写法匹配规则优先级
= /exact精确匹配1(最高)
^~ /prefix前缀匹配,命中后不再查正则2
~ \.php$正则匹配(~* 不区分大小写)3
/通用前缀,兜底4(最低)
# 完整 PHP server block 逐行拆解
server {
    listen 80;
    server_name example.com;
    root /var/www/example.com/public;      # 指向 public 目录,防止源码泄漏
    index index.php;

    location / {
        # 先找真实文件 → 再找目录 → 交给 index.php 处理(伪静态核心)
        try_files $uri $uri/ /index.php?$query_string;
    }

    # 安全要点:只让真实存在的 .php 文件交给 FPM,
    # 防止 /uploads/evil.php 这类构造路径被直接执行
    location ~ \.php$ {
        try_files $uri =404;               # 文件不存在直接 404,防任意代码执行
        include fastcgi_params;            # 注入 HTTP 头、请求方法等 CGI 变量
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  # 关键行
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_read_timeout 300;          # 长任务调大,默认 60s 易 504
    }

    # 禁止上传目录执行任何脚本(^~ 前缀匹配优先于正则 location,
    # 放在正则段之后仍能拦截 /uploads/ 下的所有请求)
    location ^~ /uploads/ {
        deny all;
    }

    # 隐藏文件一律拒绝
    location ~ /\. {
        deny all;
    }
}

# 检查配置并重载
sudo nginx -t   # 输出: nginx: configuration file /etc/nginx/nginx.conf test is successful
sudo systemctl reload nginx

fastcgi_params 里定义的 REQUEST_METHODHTTP_HOSTQUERY_STRING 等变量会随请求传给 PHP,Laravel、WordPress 等框架都依赖它们获取请求信息。出现 502 时先 curl -v 看 FastCGI 错误详情,再用 tail -f /var/log/nginx/error.log 定位。

配置 HTTPS(Let's Encrypt)

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com  # 输出: Congratulations! You have successfully enabled HTTPS
# 证书自动续期(systemd timer,无需额外配置)
sudo certbot renew --dry-run  # 输出: Congratulations, all renewals succeeded

部署 WordPress

# 创建数据库
sudo mysql -e "CREATE DATABASE wordpress CHARACTER SET utf8mb4;"
sudo mysql -e "CREATE USER 'wpuser'@'localhost' IDENTIFIED BY 'strong_password';"
sudo mysql -e "GRANT ALL ON wordpress.* TO 'wpuser'@'localhost';"

# 部署代码
cd /var/www/example.com
sudo wget https://wordpress.org/latest.tar.gz  # 输出: Saving to: 'latest.tar.gz'
sudo tar xzf latest.tar.gz --strip-components=1
sudo chown -R www-data:www-data .
# 浏览器访问 https://example.com 完成安装向导

WordPress 实战:伪静态与缓存优化

# 1) 后台设置"固定链接"(如 /archives/123.html)后,Nginx 必须支持伪静态
# 上文 try_files $uri $uri/ /index.php?$query_string; 已覆盖,无需额外 rewrite
# 对比:Apache 需要在 .htaccess 写 RewriteRule —— LAMP/LEMP 配置差异的典型例子

# 2) Nginx fastcgi_cache:http 块中配置缓存区
# /etc/nginx/nginx.conf 的 http {} 内:
fastcgi_cache_path /var/cache/nginx/wpcache levels=1:2 keys_zone=wpcache:100m
                inactive=60m max_size=1g;
fastcgi_cache_key "$scheme$request_method$host$request_uri";

# server 块中启用(登录用户、后台、动态接口不缓存)
location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    fastcgi_cache wpcache;
    fastcgi_cache_valid 200 60m;          # 200 响应缓存 60 分钟
    add_header X-Fastcgi-Cache $upstream_cache_status;   # 暴露缓存命中状态,便于验证
    fastcgi_cache_bypass $cookie_comment_author_$cookie_wordpress_logged_in;
    fastcgi_no_cache  $cookie_comment_author_$cookie_wordpress_logged_in;
}

# 3) 上传目录放开写权限,但禁止执行脚本(见上一节配置)
sudo chown -R www-data:www-data /var/www/example.com/wp-content/uploads

# 4) 验证缓存生效:连续 curl 两次,第二次带 HIT 标记
curl -I http://example.com/ | grep -i "x-fastcgi-cache"
# 输出: X-Fastcgi-Cache: MISS
#        X-Fastcgi-Cache: HIT

# 5) 可选:Redis 对象缓存插件(Redis Object Cache)把页面/会话缓存
#    放到 Redis(见 ch24),配合 WP-CLI 批量清理:
#    wp redis flush

# 6) 加固 wp-config.php:禁用文件编辑、启用调试日志
#    在 wp-config.php 末尾追加:
#    define('DISALLOW_FILE_EDIT', true);
#    define('WP_DEBUG', true);
#    define('WP_DEBUG_LOG', true);   # 日志写到 wp-content/debug.log

常见错误

错误表现根因解决
Nginx 返回 502 Bad GatewayPHP-FPM 未运行或 socket 路径不匹配systemctl status php8.3-fpm;检查 fastcgi_pass 路径是否与 php-fpm 监听一致
PHP 文件被下载而非执行Nginx 缺少 fastcgi 配置确保 server block 中有 location ~ \.php$
WordPress 一直提示"建立数据库连接时出错"MySQL 未启动或密码错误systemctl status mysql;验证 wp-config.php 中的凭据
上传文件大小受限PHP upload_max_filesize / post_max_size 太小修改 php.ini 对应值后 systemctl reload php8.3-fpm
HTTPS 配置后网站重定向循环Nginx 和 PHP 应用都在做 HTTP→HTTPS 重定向只保留 Nginx 层面的 redirect,关掉应用层的 HTTPS 强制

502/504 故障排查手册

# ===== 502 Bad Gateway:Nginx 连不上 PHP-FPM =====
# Step 1: FPM 活着吗?
systemctl status php8.3-fpm        # dead/failed → systemctl restart php8.3-fpm
# Step 2: socket 路径对上吗?
grep "listen" /etc/php/8.3/fpm/pool.d/www.conf     # 如 /run/php/php8.3-fpm.sock
grep "fastcgi_pass" /etc/nginx/sites-enabled/*     # 两边的 socket 必须一致
# Step 3: 看 Nginx 错误日志
tail -20 /var/log/nginx/error.log
# 输出: connect() to unix:/run/php/php8.3-fpm.sock failed (13: Permission denied)
#        → FPM 的 socket 权限问题:在池配置中设置 listen.owner/listen.group 匹配 Nginx 用户
#        (chmod /run/php 是临时的,systemd 重启后目录权限会被重置,不是可靠方案)
# 输出: ... (111: Connection refused)
#        → FPM 没监听 socket:检查 FPM 是否以错误的 pool 启动

# ===== 504 Gateway Timeout:PHP 执行超时 =====
# Step 1: 是脚本本身慢吗?看 FPM 慢日志
tail -50 /var/log/php8.3-fpm-slow.log   # 命中 request_slowlog_timeout 的请求
# Step 2: 调大两层超时
#   nginx:  fastcgi_read_timeout 300;
#   php:    max_execution_time = 300
# Step 3: 定位外部依赖——脚本在等外部 API/数据库时无限挂起,
#         上游调用必须设置超时与重试,别让请求无限等待

最佳实践

实践原理示例
区分 sites-available 和 sites-enabledsites-available 存所有配置,sites-enabled 存启用的临时下线站点只需 rm /etc/nginx/sites-enabled/example.com && nginx -s reload
定期轮换 MySQL 密码Web 应用数据库密码写入配置文件,泄露风险高mysql_secure_installation 加固 + 强随机密码
PHP-FPM 开启慢日志定位执行慢的 PHP 脚本slowlog = /var/log/php-slow.log; request_slowlog_timeout = 5s
Nginx 开启 gzip静态资源压缩减少带宽gzip on; gzip_types text/plain text/css application/json application/javascript;
使用 OPcache 预编译和 validate避免部署后用户访问到旧 PHP 缓存opcache.validate_timestamps=1; opcache.revalidate_freq=2

练习题

  1. (概念)LAMP 和 LEMP 的区别是什么?分别适合什么场景?
  2. (概念)PHP-FPM 的 pm = dynamicpm = ondemand 有何区别?
  3. (实操)在一台 Ubuntu 服务器上完整搭建 LEMP:安装 Nginx + MySQL + PHP-FPM;配置一个 server block 监听 example.test(本地 hosts);创建 PHP info 页面验证;安装 phpMyAdmin 并通过 HTTPS 访问。
  4. (实操)基于本章示例的 WordPress 部署,完成后配置:WP Super Cache 插件 + Nginx fastcgi_cache;将上传文件限制提升到 64MB;开启 PHP-FPM slowlog 找出耗时请求。
  5. (🔍 挑战)搭建一个 LEMP 环境后,用 ab 或 wrk 做压力测试(ab -n 10000 -c 100 http://your-site/);调整 PHP-FPM 参数 pool,对比不同 pm 模式下的 QPS 和内存占用;记录调优前后的数据对比。
点击查看答案
  1. LAMP = Apache + MySQL + PHP(mod_php 嵌入),.htaccess 强大,兼容性优先。LEMP = Nginx + MySQL + PHP-FPM(独立进程),事件驱动高并发,性能优先。
  2. pm = dynamic 在 min 和 max 间动态调整子进程数,适合流量波动的场景。pm = ondemand 有请求时才创建子进程,闲置一段时间后回收,最省内存但首次请求慢。
  3. 安装 nginx + mysql-server + php8.3-fpm + php-mysql;配置 server block 监听 80;创建 /var/www/example.test/index.php<?php phpinfo(); ?>systemctl reload nginx 并浏览器访问。
  4. 安装 WP Super Cache 插件启用 mod_rewrite 缓存;修改 /etc/php/8.3/fpm/php.iniupload_max_filesize=64Mpost_max_size=64M;PHP-FPM pool 配置 slowlog = /var/log/php-slow.log + request_slowlog_timeout=5s
  5. ab 压测前记录 QPS 和内存。调整 pm.max_children 从 10 逐步增加到 50,观察 QPS 变化曲线和内存增幅。dynamic 模式在低并发时更省内存,ondemand 更激进但首次请求延迟高。记录并对比调优前后数据。

学习检查点

学完本章后,请检验自己是否掌握以下内容:

检查项自测问题验证方法
概念理解能用自己的话解释 LAMP 和 LEMP 架构的区别尝试向他人讲解
命令操作能不查文档完成 Web 服务器、数据库、PHP 环境安装配置在终端实际执行
原理掌握能说出 Nginx 与 Apache 在处理 PHP 请求上的区别画出流程图
故障排查能独立排查 PHP 页面空白、数据库连接错误、权限问题模拟故障并修复
最佳实践能说明为什么需要为生产环境配置 OPcache 和数据库连接池对比不同方案

本章总结

LAMP 和 LEMP 是 Linux Web 服务器最常见的两种架构。LEMP(Nginx + PHP-FPM)凭借其事件驱动的架构和更小的内存占用,已成为现代 PHP 应用的首选部署方案。LAMP(Apache + mod_php)则在兼容性和 .htaccess 支持方面仍有不可替代的优势——WordPress 生态中大量插件依赖 .htaccess 规则。无论选择哪种方案,核心都是理解请求的完整路径:Web 服务器接收请求 → 静态文件直接响应 → PHP 请求转发给 PHP-FPM → PHP 执行脚本并查询数据库 → 返回 HTML

速查表

组件LEMPLAMP
Web 服务器Nginx(事件驱动)Apache(进程/线程)
PHP 处理方式fastcgi_pass → PHP-FPM socketmod_php(嵌入 Apache)
.htaccess不支持(需在 nginx.conf 配置)原生支持
静态文件性能优秀(直接响应,不经过 PHP)良好
高并发表现优秀(内存占用稳定)一般(Apache 进程数随连接增长)

学习路径建议

延伸阅读

常见问题

LAMP 和 LEMP 哪个性能更好?
LEMP(Linux + Nginx + MySQL/MariaDB + PHP-FPM)通常优于 LAMP(Apache + mod_php)。Nginx 处理静态文件和并发连接更强,PHP-FPM 独立进程池管理比 mod_php 资源隔离好。Apache 的 .htaccess 有便利性(目录级配置),但 Nginx 禁止 .htaccess 执行反而更安全。
PHP-FPM 进程数设多少合适?
pm.max_children 计算公式:可用内存 / 每个 PHP 进程平均内存。若服务器 4GB 内存,每进程约 30-50MB,则 max_children ≈ 80-130。pm = dynamic 是通用推荐(动态调整子进程数),pm.start_servers 设为 min_spare_servers 和 max_spare_servers 的中间值。
WordPress 网站在 LEMP 上怎么优化?
① 安装 Nginx FastCGI Cache 或 Redis Object Cache 插件;② 启用 PHP OPcache(opcache.enable=1, memory_consumption=128);③ 用 Nginx 直接提供静态文件(CSS/JS/图片),绕过 PHP 处理;④ 数据库启用查询缓存(query_cache_type=ON);⑤ 安装 WP Rocket 或 W3 Total Cache 缓存插件。
↑ 回到顶部