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
知识关联
- 前置知识:1.8:软件包管理 软件包管理、3.4:Nginx Web服务器 Nginx Web 服务器、3.6:MySQL 数据库 MySQL/MariaDB 数据库
- 后续影响:搭建好的环境可用于 4.9:CI/CD 持续部署 CI/CD 持续部署、4.14:运维故障案例集 运维故障案例集
- 对比技术:单机部署用本方案,分布式场景看 4.1:Ansible 自动化 Ansible 自动化;容器化部署看 Docker/K8s
原理讲解
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_limit | 128M | 256M-512M | 单请求最大内存,过小会导致 500 错误 |
upload_max_filesize | 2M | 64M(按业务) | 上传文件大小上限 |
post_max_size | 8M | ≥ upload_max_filesize | POST 请求体上限,必须 ≥ 上传上限 |
max_execution_time | 30 | 30-120 | 单请求最长执行秒数 |
date.timezone | 未设置 | Asia/Shanghai | 不设置会报时区警告 |
expose_php | On | Off | 隐藏 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_METHOD、HTTP_HOST、QUERY_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 Gateway | PHP-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-enabled | sites-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 |
练习题
- (概念)LAMP 和 LEMP 的区别是什么?分别适合什么场景?
- (概念)PHP-FPM 的
pm = dynamic和pm = ondemand有何区别? - (实操)在一台 Ubuntu 服务器上完整搭建 LEMP:安装 Nginx + MySQL + PHP-FPM;配置一个 server block 监听 example.test(本地 hosts);创建 PHP info 页面验证;安装 phpMyAdmin 并通过 HTTPS 访问。
- (实操)基于本章示例的 WordPress 部署,完成后配置:WP Super Cache 插件 + Nginx fastcgi_cache;将上传文件限制提升到 64MB;开启 PHP-FPM slowlog 找出耗时请求。
- (🔍 挑战)搭建一个 LEMP 环境后,用 ab 或 wrk 做压力测试(
ab -n 10000 -c 100 http://your-site/);调整 PHP-FPM 参数 pool,对比不同 pm 模式下的 QPS 和内存占用;记录调优前后的数据对比。
点击查看答案
- LAMP = Apache + MySQL + PHP(mod_php 嵌入),.htaccess 强大,兼容性优先。LEMP = Nginx + MySQL + PHP-FPM(独立进程),事件驱动高并发,性能优先。
pm = dynamic在 min 和 max 间动态调整子进程数,适合流量波动的场景。pm = ondemand有请求时才创建子进程,闲置一段时间后回收,最省内存但首次请求慢。- 安装 nginx + mysql-server + php8.3-fpm + php-mysql;配置 server block 监听 80;创建
/var/www/example.test/index.php含<?php phpinfo(); ?>;systemctl reload nginx并浏览器访问。 - 安装 WP Super Cache 插件启用 mod_rewrite 缓存;修改
/etc/php/8.3/fpm/php.ini中upload_max_filesize=64M和post_max_size=64M;PHP-FPM pool 配置slowlog = /var/log/php-slow.log+request_slowlog_timeout=5s。 - 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。
速查表
| 组件 | LEMP | LAMP |
|---|---|---|
| Web 服务器 | Nginx(事件驱动) | Apache(进程/线程) |
| PHP 处理方式 | fastcgi_pass → PHP-FPM socket | mod_php(嵌入 Apache) |
| .htaccess | 不支持(需在 nginx.conf 配置) | 原生支持 |
| 静态文件性能 | 优秀(直接响应,不经过 PHP) | 良好 |
| 高并发表现 | 优秀(内存占用稳定) | 一般(Apache 进程数随连接增长) |
学习路径建议
- 搭建好环境后,阅读 4.9:CI/CD 持续部署 CI/CD 接入部署流水线
- 了解安全加固可看 5.1:Linux 安全加固 Linux 安全加固
- 多服务器场景参考 4.1:Ansible 自动化 Ansible 自动化部署