Nginx 系列实战(三):LNMP 完整搭建,动静分离的学习总结

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-FPM
  • SCRIPT_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

改动配置后的固定套路:

  1. 改 Nginx:nginx -tsystemctl reload nginx
  2. 改 PHP-FPM / MySQL 配置 → 重启对应服务
  3. 每改一层,用 curl 验证一层,不要攒到最后一起猜

六、排障思路与解决方案(专题)

排障通用思路

遇到问题不要乱猜,按下面的顺序缩小范围:

  1. 看现象:是 403、502、500,还是空白页?
  2. 看日志:/var/log/nginx/error.log/var/log/php-fpm/error.log
  3. 确认服务和端口:PHP-FPM 在 9000 吗?MySQL 在跑吗?
  4. 命令行绕过中间层直测:php /data/lnmp/index.php 直接跑脚本,绕开 Nginx
  5. 修复后重新 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 远程登录、定期备份数据库
相关推荐
Jae den1 小时前
服务器 CPU 核心越多越好吗?
运维·服务器
先吃饱再说1 小时前
从本地跑通到公网访问:前后端项目部署实战
运维·全栈
帷幕落秋1 小时前
一文吃透 Nginx 虚拟主机:原理 + 双站点实战 + SELinux 排错
运维·自动化运维
mengge.cloud1 小时前
0909华为云网络类服务实践小白实验教程
linux·运维·服务器·网络·华为云·php
智恒百亿1 小时前
CSDN 文章|八卡 RTX5090 服务器深度实测:256GB 显存池,能否流畅跑通 70B 大模型微调?
运维·服务器
阿明61 小时前
基础编辑工具使用【Linux】
linux·运维·服务器
Tinyundg1 小时前
关于Termux的基本使用——安卓手机爆改Linux服务器?
linux·运维·服务器·智能手机·个人开发
AugustSkys2 小时前
PostgreSQL 完全指南:特性对比 + 安装部署 + 配置管理 + psql 实战 + pgAdmin 图形化
运维·postgresql·开源数据库·pgadmin
weixin_446260852 小时前
PromptMatic:通过多阶段提示优化提升大模型的指令遵循能力
运维·服务器