LNMP 完整搭建实战(案例三)
前两篇分别学了 Nginx 虚拟主机(内容提供方)和反向代理(转交员)。本篇把整条生产链路补齐:LNMP = Linux + Nginx + MySQL + PHP,用一台干净的 CentOS 7 服务器完整搭建,并跑通 "浏览器 → Nginx → PHP-FPM → MySQL" 的动静分离架构。
一、案例需求
搭建 www.lnmpdemo.com 网站,要求:
- 静态文件(HTML/CSS/图片)由 Nginx 直接返回
.php动态请求由 Nginx 转交 PHP-FPM 解析- PHP 脚本能连接 MySQL,把数据库里的数据查询出来并显示到页面上
二、LNMP 架构与请求流程
| 组件 | 作用 | 默认端口 |
|---|---|---|
| Linux | 操作系统,提供运行环境 | - |
| Nginx | Web 服务器:接收请求、处理静态文件、转发动态请求 | 80 |
| MySQL | 数据库:存储和管理数据 | 3306 |
| PHP-FPM | PHP 解释器:解析动态脚本 | 9000 |
一次完整请求的流动:
text
浏览器
↓
Nginx:80
├── 静态文件? → Nginx 自己直接返回
└── .php 请求? → PHP-FPM:9000 解析脚本
└── 脚本需要数据 → MySQL:3306 查询
↓
查询结果逐层返回,拼成 HTML 给浏览器
关键认知:Nginx 本身不会解析 PHP 。它接到 .php 请求后,通过 FastCGI 协议把任务交给 PHP-FPM,和"反向代理转发给后端"是同一个思想,只是协议更专业。
三、环境准备
1. 准备一台干净的 CentOS 7
bash
cat /etc/os-release
ip addr
2. 更换 yum 源(CentOS 7 已停止维护)
CentOS 7 官方源 2024 年停服,mirrorlist.centos.org 已无法访问,必须换成仍在维护的存档镜像(本文用阿里云):
bash
cp -a /etc/yum.repos.d /etc/yum.repos.d.bak
sed -i 's|^mirrorlist=|#mirrorlist=|g' /etc/yum.repos.d/CentOS-Base.repo
sed -i 's|^#baseurl=http://mirror.centos.org/centos/$releasever|baseurl=https://mirrors.aliyun.com/centos-vault/7.9.2009|g' /etc/yum.repos.d/CentOS-Base.repo
yum clean all
yum makecache
顺手安装 EPEL(扩展软件源,后续可能用到):
bash
yum install -y epel-release
四、LNMP 搭建全流程
第 1 段:安装 Nginx
bash
yum install -y nginx
systemctl start nginx
systemctl enable nginx
systemctl status nginx
curl http://127.0.0.1
看到 active (running),curl 返回 HTML 欢迎页,入口就绪。
第 2 段:安装 MySQL 8
添加 MySQL 官方 yum 源(国内慢可换阿里云镜像 https://mirrors.aliyun.com/mysql-repo/mysql80-community-release-el7-3.noarch.rpm):
bash
rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm
安装服务端(如遇 GPG 签名问题,见文末"排障专题"第 4 条):
bash
yum install -y mysql-community-server
systemctl start mysqld
systemctl enable mysqld
MySQL 8 第一次启动会为 root 生成随机临时密码,从日志里取:
bash
grep 'temporary password' /var/log/mysqld.log
登录并修改 root 密码(密码需 8 位以上,含大小写字母、数字、特殊符号):
bash
mysql -uroot -p
sql
ALTER USER 'root'@'localhost' IDENTIFIED BY '你的强密码';
EXIT;
第 3 段:创建测试数据库和专用账号
bash
mysql -uroot -p
sql
CREATE DATABASE lnmp_test CHARACTER SET utf8mb4;
CREATE USER 'lnmp_user'@'127.0.0.1' IDENTIFIED WITH mysql_native_password BY 'Lnmp@123456';
CREATE USER 'lnmp_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Lnmp@123456';
GRANT ALL PRIVILEGES ON lnmp_test.* TO 'lnmp_user'@'127.0.0.1';
GRANT ALL PRIVILEGES ON lnmp_test.* TO 'lnmp_user'@'localhost';
FLUSH PRIVILEGES;
USE lnmp_test;
CREATE TABLE demo (
id INT AUTO_INCREMENT PRIMARY KEY,
info VARCHAR(100)
);
INSERT INTO demo (info) VALUES ('LNMP 链路测试成功');
SELECT * FROM demo;
EXIT;
两个要点:
utf8mb4是完整 UTF-8,能存中文和 emoji- 账号必须指定
mysql_native_password认证插件,否则老版 PHP 5.4 无法连接(详见排障专题第 6 条)
第 4 段:安装 PHP 与 PHP-FPM
bash
yum install -y php php-fpm php-mysqlnd
php -v
把 PHP-FPM 的运行用户从 apache 改成 nginx:
bash
sed -i 's/^user = apache/user = nginx/' /etc/php-fpm.d/www.conf
sed -i 's/^group = apache/group = nginx/' /etc/php-fpm.d/www.conf
grep -E '^(user|group|listen)' /etc/php-fpm.d/www.conf
确认输出:
text
user = nginx
group = nginx
listen = 127.0.0.1:9000
启动并验证:
bash
systemctl start php-fpm
systemctl enable php-fpm
ss -tlnp | grep 9000
9000 端口在监听,PHP-FPM 就绪。
第 5 段:创建站点目录与测试文件
bash
mkdir -p /data/lnmp/static
echo '<h1 style="color:green">这是 Nginx 直接返回的静态页面</h1>' > /data/lnmp/static/static.html
创建连接 MySQL 的 PHP 测试页:
bash
cat > /data/lnmp/index.php <<'EOF'
<?php
$conn = new mysqli('127.0.0.1', 'lnmp_user', 'Lnmp@123456', 'lnmp_test');
if ($conn->connect_error) {
die('数据库连接失败: ' . $conn->connect_error);
}
$result = $conn->query('SELECT info FROM demo WHERE id = 1');
$row = $result->fetch_assoc();
echo '<h1>LNMP 链路测试成功</h1>';
echo '<p>MySQL 返回:' . $row['info'] . '</p>';
$conn->close();
?>
EOF
给目录打 SELinux 标签并授权:
bash
chcon -Rt httpd_sys_content_t /data/lnmp
chown -R nginx:nginx /data/lnmp
第 6 段:配置 Nginx(动静分离)
bash
cat > /etc/nginx/conf.d/lnmp.conf <<'EOF'
server {
listen 80;
server_name www.lnmpdemo.com;
root /data/lnmp;
index index.php index.html;
location / {
try_files $uri $uri/ =404;
}
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
EOF
两个 location 就是"动静分离"的实现:
location /→ 静态请求,Nginx 直接按root找文件location ~ \.php$→ 正则匹配.php结尾的请求,经 FastCGI 转给 PHP-FPMSCRIPT_FILENAME→ 告诉 PHP-FPM 要解析的脚本完整路径
第 7 段:域名、放行与加载
bash
echo "127.0.0.1 www.lnmpdemo.com" >> /etc/hosts
setsebool -P httpd_can_network_connect 1
nginx -t
systemctl reload nginx
第 8 段:验证动静两条路
bash
curl http://www.lnmpdemo.com/static/static.html
curl http://www.lnmpdemo.com/index.php
验证结果:
- 静态请求:直接返回绿色"这是 Nginx 直接返回的静态页面",没有经过 PHP
- 动态请求:返回
LNMP 链路测试成功+MySQL 返回:LNMP 链路测试成功,走完了 Nginx → PHP-FPM → MySQL 全链路
五、完整链路复盘
最终服务器上的角色与端口:
| 组件 | 端口 | 状态验证命令 |
|---|---|---|
| Nginx | 80 | curl http://127.0.0.1 |
| PHP-FPM | 9000 | `ss -tlnp |
| MySQL | 3306 | systemctl status mysqld |
改动配置后的固定套路:
- 改 Nginx:
nginx -t→systemctl reload nginx - 改 PHP-FPM / MySQL 配置 → 重启对应服务
- 每改一层,用 curl 验证一层,不要攒到最后一起猜
六、排障思路与解决方案(专题)
排障通用思路
遇到问题不要乱猜,按下面的顺序缩小范围:
- 看现象:是 403、502、500,还是空白页?
- 看日志:
/var/log/nginx/error.log、/var/log/php-fpm/error.log - 确认服务和端口:PHP-FPM 在 9000 吗?MySQL 在跑吗?
- 命令行绕过中间层直测:
php /data/lnmp/index.php直接跑脚本,绕开 Nginx - 修复后重新 curl,直到链路通
问题 1:yum 报 Could not resolve host: mirrorlist.centos.org
- 现象:执行
yum install报无法解析 mirrorlist - 原因:CentOS 7 已停止维护,官方镜像列表服务退役,域名已不可用
- 解决:更换为阿里云存档源(见"环境准备")
- 验证:
yum makecache成功
问题 2:网页 403 Forbidden
- 现象:文件存在、权限看起来正常,但 Nginx 拒绝读取
- 日志:
"/data/lnmp/..." is forbidden (13: Permission denied) - 原因:SELinux 不允许 Nginx 读取新建目录(缺少
httpd_sys_content_t标签) - 解决:
bash
chcon -Rt httpd_sys_content_t /data/lnmp
systemctl reload nginx
- 验证:curl 能访问静态页面
问题 3:访问 .php 返回 502 Bad Gateway
- 现象:
curl http://www.lnmpdemo.com/index.php返回 502 - 日志:
connect() failed (111: Connection refused) while connecting to upstream, upstream: "fastcgi://127.0.0.1:9000" - 原因:Nginx 想连 9000 端口,但 PHP-FPM 没装或没启动
- 排查:
bash
systemctl status php-fpm
ss -tlnp | grep 9000
- 解决:安装并启动 PHP-FPM(见第 4 段)
- 验证:9000 端口监听后,curl 返回 PHP 内容而不是 502
问题 4:安装 MySQL 报 GPG 公钥错误
- 现象:
Public key for mysql-community-common... is not installed - 原因:MySQL 源包的 GPG 密钥未被系统识别
- 解决(练习环境):关闭该源的 gpgcheck
bash
sed -i 's/^gpgcheck=1/gpgcheck=0/' /etc/yum.repos.d/mysql-community.repo
yum install -y mysql-community-server
- 说明:生产服务器应保留签名校验,并手动导入官方公钥修复,而不是跳过
问题 5:PHP 连 MySQL 报 Server sent charset unknown to the client
- 现象:PHP 页面输出
数据库连接失败: Server sent charset unknown to the client - 原因:MySQL 8 默认排序规则
utf8mb4_0900_ai_ci太新,PHP 5.4 客户端不认识 - 解决:让 MySQL 使用老客户端认识的排序规则
bash
sed -i '/^\[mysqld\]/a character-set-server=utf8mb4\ncollation-server=utf8mb4_general_ci' /etc/my.cnf
systemctl restart mysqld
- 验证:重新 curl,不再报 charset 错误
问题 6:PHP 连 MySQL 报 authentication method unknown ... caching_sha2_password
- 现象:
text
The server requested authentication method unknown to the client [caching_sha2_password]
- 原因:MySQL 8 默认认证插件是
caching_sha2_password,PHP 5.4 不认识;即使账号已改成mysql_native_password,服务器握手时宣告的默认插件仍可能让老客户端放弃 - 解决:账号和服务器默认插件都改成老插件
sql
-- 账号层:创建/修改账号时显式指定
CREATE USER 'lnmp_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'Lnmp@123456';
bash
# 服务器层:修改默认认证插件
sed -i '/^\[mysqld\]/a default-authentication-plugin=mysql_native_password' /etc/my.cnf
systemctl restart mysqld
- 验证:
bash
mysql -uroot -p -e "SELECT user, host, plugin FROM mysql.user WHERE user='lnmp_user';"
php /data/lnmp/index.php
问题 7:页面 500 或空白,但看不到任何报错
- 现象:curl 返回 HTTP 500,响应头有
X-Powered-By: PHP/5.4.16,页面内容为空 - 原因:PHP 发生致命错误,但
display_errors默认关闭,错误被吞掉 - 排查:用命令行直接跑脚本,错误会显示在屏幕
bash
php -l /data/lnmp/index.php # 语法检查
php /data/lnmp/index.php # 直接执行,显示真实错误
- 本例的真相:mysqli 连接失败后,代码还继续调用
fetch_assoc(),触发Call to a member function fetch_assoc() on a non-object - 解决:先修好连接(问题 5、6),再给脚本加上失败判断,避免连锁崩溃
七、小结
- LNMP 的核心不是"装四个软件",而是理解请求如何在四层之间流转:Nginx 收请求 → 静态自己处理 / PHP 转 PHP-FPM → PHP 查 MySQL → 结果逐层返回
- 动静分离 =
location /(静态)与location ~ \.php$(动态转发)的分工 - 老环境(Linux + 老 PHP)对接 MySQL 8,要特别注意字符集排序规则和认证插件两座"隐形的墙"
- 排错永远先看日志、再查端口、最后用命令行绕开中间层直测
- 正式环境建议:MySQL 跑
mysql_secure_installation加固、禁止 root 远程登录、定期备份数据库