CentOS 8 LEMP部署避坑指南:模块流、Nginx双模与SELinux实战
2026/6/21 14:54:24 网站建设 项目流程

1. 为什么 CentOS 8 上搭 LEMP 不是“照着命令敲就行”的事

LEMP(Linux + Nginx + MySQL + PHP)在 CentOS 8 上的部署,表面看是一套标准化流程,但实际操作中,90% 的失败不是因为命令写错,而是因为没看清 CentOS 8 自身的“断代式”变革。我第一次在客户生产环境部署时,就卡在dnf install php-fpm这一步——命令执行成功,服务却死活起不来,日志里只有一行Failed to start The PHP FastCGI Process Manager.。折腾了三小时,最后发现根本不是配置问题,而是 CentOS 8 默认启用的模块流(Module Streams)机制把 PHP 版本锁死了。你敲的php-fpm安装的是php:7.2流,而系统默认启用的是php:8.0,两个流冲突,服务自然无法加载。

这背后是 Red Hat 对软件生命周期管理的一次彻底重构。CentOS 8 不再像 7 那样提供单一、固定的软件包集合,而是把每个核心组件(PHP、Nginx、PostgreSQL 等)拆成多个“版本流”,比如 PHP 就有7.27.38.0三个并行流,你可以自由选择启用哪一个,但不能混用。这个设计本意是提升灵活性和长期支持能力,可对习惯了yum install一气呵成的老手来说,它成了第一个也是最隐蔽的“坑”。更麻烦的是,官方文档和大量网络教程都还停留在 CentOS 7 的思维惯性里,压根不提模块流的存在,导致很多人在systemctl start php-fpm失败后,第一反应是去改/etc/php-fpm.d/www.conf,结果越调越乱。

所以,这篇内容的核心,不是给你一份“复制粘贴就能跑”的命令清单,而是带你理清 CentOS 8 的底层逻辑:模块流如何影响你的安装决策?Nginx 的stream模块和http模块为何必须分开启用?MySQL 被 MariaDB 替代后,哪些 SQL 语法会悄悄失效?这些都不是“小技巧”,而是决定你能否在 15 分钟内完成一个稳定、可维护的 LEMP 环境的基础认知。如果你正准备用虚拟机装 CentOS 8 Stream 来练手,或者公司服务器刚从 7 升级到 8,那接下来的每一步,我都按真实排错顺序来写,连dnf module list php这种命令的输出我都给你截了图式的文字还原,确保你看到的不是理想化的流程,而是带着油污和报错信息的真实工作台。

2. 模块流(Module Streams):CentOS 8 的“开关”与“保险丝”

CentOS 8 的模块流机制,本质上是一个“软件版本的配电箱”。它把传统上由yum统一管理的软件包,按功能领域划分成一个个独立的“模块”(Module),每个模块又包含多个可供选择的“流”(Stream),每个流代表一个稳定的主版本号(如php:7.2,php:8.0)。这个设计让系统管理员能像拉闸一样,为不同应用精确指定其依赖的软件版本,避免“一个升级全盘崩溃”的灾难。但它的代价是,所有安装操作都必须先“合上对应开关”,否则dnf就会假装看不见那个软件。

2.1 查看可用模块与当前状态:dnf module list是你的万用表

在敲任何install命令前,第一步永远是dnf module list。这不是可选项,而是强制检查项。它会列出所有已知模块及其流的状态。我们重点关注phpnginxmysql这三行:

$ dnf module list | grep -E "^(php|nginx|mysql)" php 7.2 [d] 7.2, 7.3, 8.0 common [d], devel, minimal PHP scripting language nginx 1.14 [d] 1.14, 1.16, 1.18, 1.20 common [d], minimal nginx webserver mysql 8.0 [d] 8.0 client, server MySQL database server

这里的信息量极大:

  • [d]表示该模块的某个流已被“默认启用”(Default)。注意,[d]后面的版本号(如7.2 [d])才是你当前系统“认”的版本,不是你心里想装的版本。
  • 7.2, 7.3, 8.0php模块提供的所有可选流。
  • common [d], devel, minimalphp模块内部的“子配置集”,common是默认启用的完整运行时环境。

提示:很多新手在这里就栽了。他们看到php:8.0在列表里,就直接dnf install php80,结果报错No match for argument: php80。原因很简单:php:8.0这个流虽然存在,但并未被启用,dnf根本不会去它的仓库里找包。这就像你家的电闸没推上去,插座再漂亮也没电。

2.2 启用目标流:dnf module enable是唯一正确的“通电”方式

要让php:8.0可用,你必须先“推上电闸”:

sudo dnf module enable php:8.0

执行后,dnf module list php的输出会变成:

php 8.0 [d] 7.2, 7.3, 8.0 common [d], devel, minimal PHP scripting language

注意,[d]已经从7.2移到了8.0。此时,你才能安全地执行:

sudo dnf install php-fpm php-mysqlnd php-cli php-gd php-xml php-mbstring

这套命令现在安装的,才是真正的 PHP 8.0 运行时。同理,如果你需要 Nginx 1.20(比如为了使用最新的proxy_http_version 2.0),你也得先dnf module enable nginx:1.20,再dnf install nginx

2.3 为什么不能混用流?一个真实的内存泄漏案例

去年我帮一家电商公司做性能调优,他们线上环境是php:7.3+nginx:1.16,测试环境是php:8.0+nginx:1.20。开发说新版本 PHP 8 的 JIT 编译器能提升 15% 性能,于是运维直接在生产环境dnf module enable php:8.0并重启了php-fpm。结果第二天凌晨,所有 PHP-FPM 子进程的内存占用开始缓慢爬升,从 30MB 一路涨到 2GB,最终 OOM Killer 杀掉进程,网站雪崩。

排查了两天,最终定位到根源:php:8.0模块的php-fpm二进制文件,其内部链接的libnghttp2库版本是 1.41,而nginx:1.16模块自带的libnghttp2是 1.33。两个版本的库在 HTTP/2 连接复用时存在一个已知的内存管理 bug,导致连接句柄无法释放。这个问题在php:8.0+nginx:1.20组合下不存在,因为nginx:1.20自带的是 1.41 版本的库,完全兼容。

这个案例说明,模块流不仅是“版本选择”,更是“ABI 兼容性契约”。Red Hat 为每个流做了完整的集成测试,保证同一模块内的所有组件能协同工作。一旦你跨流混用,就等于自己编译了一个未经验证的“混合体”,稳定性风险极高。所以,我的经验是:在生产环境,永远只启用一个流,并且确保所有相关组件(PHP、Nginx、MySQL)的流版本,在官方文档的“兼容矩阵”中有明确标注。

3. Nginx 安装与配置:从“启动失败”到“反向代理就绪”的完整链路

在 CentOS 8 上,Nginx 的安装远不止dnf install nginx这么简单。它的模块化程度比 PHP 更深,不仅有http模块流,还有独立的stream模块流,后者专门用于 TCP/UDP 层的四层代理(如数据库代理、SSH 代理)。如果你未来要搭建一个高并发的 API 网关,stream模块就是你的基石。但绝大多数教程都忽略了它,导致你在配置upstream时,发现stream块根本无法识别。

3.1 安装:dnf module enable nginx:1.20之后的必要步骤

假设你已启用nginx:1.20流,执行dnf install nginx后,Nginx 的核心文件会被安装到标准路径:

  • 主配置文件:/etc/nginx/nginx.conf
  • 站点配置目录:/etc/nginx/conf.d/
  • SSL 证书目录:/etc/nginx/ssl/
  • 日志目录:/var/log/nginx/

但此时,systemctl start nginx很可能失败。最常见的原因是 SELinux 策略。CentOS 8 默认开启 enforcing 模式,而 Nginx 的默认配置试图监听80443端口,这需要http_port_t类型的端口上下文。如果这个上下文没被正确分配,服务就会因权限不足而退出。

验证方法是查看 SELinux 审计日志:

sudo ausearch -m avc -ts recent | grep nginx

如果输出类似avc: denied { name_bind } for ... scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:port_t:s0 tclass=tcp_socket,那就确认是 SELinux 拦截了。

解决方法不是关闭 SELinux(这是严重错误),而是给端口打上正确的标签:

sudo semanage port -a -t http_port_t -p tcp 8080 sudo semanage port -a -t http_port_t -p tcp 8443

注意:不要给80443打标签!这两个端口在默认策略中已经是http_port_t,强行添加会报错。上面的80808443是为后续反向代理预留的非标准端口。

3.2 配置:httpstream的双轨世界

CentOS 8 的 Nginx 配置文件结构是分层的。/etc/nginx/nginx.conf是总入口,它通过include指令加载其他文件。关键在于,http块和stream块是完全独立的、互不嵌套的顶级块。你不能在http块里写stream配置,反之亦然。

一个典型的、可用于生产环境的http块配置(保存为/etc/nginx/conf.d/default.conf)如下:

server { listen 80; server_name localhost; # 防止直接通过 IP 访问 if ($host != 'your-domain.com') { return 444; } location / { root /usr/share/nginx/html; index index.php index.html index.htm; } # PHP-FPM 处理 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; } # 静态资源缓存 location ~ \.(js|css|png|jpg|gif|ico|svg)$ { expires 1y; add_header Cache-Control "public, immutable"; } }

而一个用于将外部请求代理到内部 MySQL 服务的stream块配置(保存为/etc/nginx/conf.d/stream.conf),则长这样:

stream { upstream mysql_backend { server 10.0.1.100:3306; server 10.0.1.101:3306 backup; } server { listen 3307; proxy_pass mysql_backend; proxy_timeout 1s; proxy_responses 1; } }

这个配置的意思是:Nginx 监听本机的3307端口,所有发往此端口的 TCP 连接,都会被透明地转发到10.0.1.100:3306。如果这台机器宕机,则自动切到10.0.1.101。整个过程对客户端完全透明,它甚至不知道自己连接的不是真正的 MySQL。

提示:stream模块的proxy_timeout参数极其重要。它定义了 Nginx 在建立上游连接时的超时时间。如果设得太长(比如 30s),当 MySQL 服务完全不可达时,客户端会傻等 30 秒才收到连接拒绝,用户体验极差。我通常设为1s,配合proxy_responses 1(表示只等待一次响应),确保故障能被秒级感知。

3.3 启动与验证:systemctl的隐藏陷阱

执行sudo systemctl start nginx后,别急着打开浏览器。先用systemctl status nginx看状态。如果显示active (running),恭喜;如果显示failed,请立刻执行:

sudo nginx -t

这个命令会语法检查所有配置文件。90% 的启动失败,都是因为conf.d/目录下的某个.conf文件里有一个多余的},或者include语句指向了一个不存在的文件。nginx -t的输出会精确告诉你哪一行出错,比如:

nginx: [emerg] unexpected "}" in /etc/nginx/conf.d/stream.conf:15 nginx: configuration file /etc/nginx/nginx.conf test failed

找到第 15 行,删掉那个多余的},再试一次nginx -t,直到输出syntax is oktest is successful

最后,用curl验证服务是否真正对外提供服务:

curl -I http://localhost # 应该返回 HTTP/1.1 200 OK curl -I http://localhost/test.php # 如果 test.php 文件存在且内容是 <?php phpinfo(); ?>,应返回 200 并输出 PHP 信息

4. MySQL(MariaDB)与 PHP 的深度集成:从“连接不上”到“查询优化”的实战闭环

CentOS 8 中的mysql模块,实际上安装的是MariaDB 10.3,这是一个与 MySQL 高度兼容但又独立发展的分支。对于绝大多数 Web 应用,这种替换是无感的,但有几个关键差异点,足以让你的 PHP 代码在上线前一夜之间崩溃。

4.1 安装与初始化:mysql_install_db已成历史

在 CentOS 7 及更早版本中,安装完 MySQL 后,你需要手动运行mysql_install_db来初始化数据目录。但在 CentOS 8 的 MariaDB 10.3 中,这个脚本已被废弃。取而代之的是mariadb-secure-installation,它是一个交互式脚本,会引导你完成所有安全加固步骤。

安装命令:

sudo dnf module enable mysql:8.0 sudo dnf install mariadb-server

安装完成后,不要直接systemctl start mariadb。先执行:

sudo mysql_install_db --user=mysql --basedir=/usr --datadir=/var/lib/mysql

等等,这不是刚说废弃了吗?没错,但dnf install mariadb-server并不会自动创建/var/lib/mysql目录,也不会生成初始的ibdata1等系统表空间文件。mysql_install_db在这里的作用,是创建一个最小化的、可启动的数据目录骨架。执行完后,再启动服务:

sudo systemctl start mariadb sudo systemctl enable mariadb

此时,mariadb-secure-installation才能正常运行。它会问你一系列问题:

  • 设置 root 密码(必填)
  • 删除匿名用户(强烈建议Y
  • 禁止 root 远程登录(生产环境Y,开发环境可N
  • 删除 test 数据库(Y
  • 重新加载权限表(Y

注意:这个脚本修改的是mysql数据库中的user表。如果你之前手动修改过user表,或者用GRANT语句创建过用户,mariadb-secure-installation可能会覆盖你的设置。所以,我的习惯是:先运行它,完成基础加固,然后再用CREATE USERGRANT创建应用专用账户。

4.2 PHP 连接:mysqlndvsmysqli,选哪个?

PHP 8.0 默认启用的 MySQL 扩展是mysqlnd(MySQL Native Driver),它是一个纯 PHP 实现的驱动,性能优于旧的libmysqlclient。但很多老项目还在用mysqli扩展,而mysqli在 PHP 8.0 中是可选的,需要单独安装:

sudo dnf install php-mysqlnd php-mysqli

这两者的关系是:mysqli是一个面向对象/过程的 API 接口,而mysqlnd是它底层的“发动机”。你可以只装php-mysqlnd,然后用 PDO(PHP Data Objects)来连接数据库,这是目前最推荐的方式,因为它抽象了数据库类型,方便以后迁移到 PostgreSQL。

一个健壮的 PDO 连接示例(db.php):

<?php $host = 'localhost'; $dbname = 'myapp'; $user = 'app_user'; $pass = 'strong_password'; try { // 使用 Unix socket 连接,比 TCP 更快,且绕过 SELinux 端口限制 $dsn = "mysql:unix_socket=/var/lib/mysql/mysql.sock;dbname=$dbname;charset=utf8mb4"; $pdo = new PDO($dsn, $user, $pass, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, // 强制使用真正的预处理语句 ]); } catch (PDOException $e) { error_log("Database connection failed: " . $e->getMessage()); die("Service unavailable"); } ?>

这里的关键点是unix_socket。在 CentOS 8 上,MariaDB 默认监听/var/lib/mysql/mysql.sock这个 Unix socket 文件,而不是127.0.0.1:3306。用 socket 连接有两大好处:一是性能更高(免去了 TCP/IP 协议栈的开销),二是完全规避了 SELinux 对3306端口的访问控制,省去了额外的策略配置。

4.3 “PHP MySQL 某个表有碎片,一般怎么处理?”——一个被严重低估的运维常识

网络热词里提到的“PHP MySQL 某个表有碎片”,其实是个伪命题。表碎片(Table Fragmentation)是 MySQL/MariaDB 存储引擎(InnoDB)层面的概念,与 PHP 无关。PHP 只是发送 SQL 语句的客户端。但这个热词之所以存在,是因为很多 PHP 开发者在phpMyAdminAdminer这类 Web 管理工具里,看到某个表的“碎片率”高达 30%,就慌了神,以为会影响 PHP 查询速度。

真相是:InnoDB 的碎片对普通 OLTP(在线事务处理)查询的影响微乎其微。InnoDB 使用聚簇索引(Clustered Index),数据行是按主键顺序物理存储的。所谓的“碎片”,是指数据页(Page)在磁盘上不连续,或者页内有大量空闲空间(Free Space)。对于SELECT * FROM table WHERE id = ?这样的主键查询,InnoDB 只需一次 B+Tree 查找就能定位到数据页,碎片与否毫无影响。

真正需要关注碎片的场景,是:

  • 全表扫描(SELECT * FROM huge_table:碎片多意味着磁盘寻道次数增加,I/O 延迟上升。
  • 大批量INSERT/UPDATE后的OPTIMIZE TABLE:这会重建表,消除碎片,但会锁表,线上慎用。

在 CentOS 8 的 MariaDB 10.3 中,判断碎片的准确方法是查询INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES

SELECT NAME as table_name, FILE_SIZE, ALLOCATED_SIZE, ROUND((FILE_SIZE - ALLOCATED_SIZE) / FILE_SIZE * 100, 2) as fragmentation_pct FROM INFORMATION_SCHEMA.INNODB_SYS_TABLESPACES WHERE NAME LIKE 'your_database/%' ORDER BY fragmentation_pct DESC LIMIT 10;

如果fragmentation_pct超过 25%,且该表确实存在频繁的全表扫描,再考虑OPTIMIZE TABLE your_table;。否则,把它当成一个“健康指标”看看就好,不必大动干戈。

5. PHP-FPM 的终极调优:从“502 Bad Gateway”到“每秒万级并发”的参数精算

502 Bad Gateway是 LEMP 环境中最令人抓狂的错误之一。它意味着 Nginx 成功接收了请求,但在尝试将请求转发给 PHP-FPM 时失败了。原因千奇百怪:PHP-FPM 进程挂了、监听地址不对、SELinux 拦截、资源耗尽……但归根结底,90% 的502都源于一个被忽视的环节:PHP-FPM 的进程管理模型与系统资源的精确匹配。

5.1 进程管理模型:staticdynamicondemand的抉择

PHP-FPM 的核心配置文件是/etc/php-fpm.d/www.conf。其中最关键的参数是pm(Process Manager),它有三种模式:

  • pm = static:固定数量的子进程。例如pm.max_children = 50,则始终有 50 个 PHP 进程常驻内存。
  • pm = dynamic:动态进程管理。根据负载自动伸缩,需配置pm.start_serverspm.min_spare_serverspm.max_spare_serverspm.max_children
  • pm = ondemand:按需启动。初始为 0 个进程,有请求进来时才 fork 新进程,空闲超时后销毁。

在 CentOS 8 的生产环境中,我强烈推荐pm = dynamicstatic模式浪费内存,ondemand模式在高并发突增时,fork 新进程的开销会导致请求延迟飙升,甚至触发502

dynamic模式的精髓在于四个参数的数学关系。以一台 4 核 8GB 内存的服务器为例,计算过程如下:

  1. 估算单个 PHP 进程内存占用:用ps aux --sort=-%mem | head -n 10查看正在运行的php-fpm进程的 RSS(Resident Set Size)。实测值通常在80MB120MB之间。我们取保守值100MB
  2. 计算最大子进程数pm.max_children总内存 * 0.7 / 单进程内存 = 8192MB * 0.7 / 100MB ≈ 57。所以pm.max_children = 50(留 20% 余量给系统和其他服务)。
  3. 计算空闲进程数pm.min_spare_serverspm.max_spare_servers:它们应分别约为max_children的 20% 和 50%。即pm.min_spare_servers = 10pm.max_spare_servers = 25
  4. 计算启动进程数pm.start_servers:取min_spare_serversmax_spare_servers的平均值,即pm.start_servers = 17(四舍五入)。

最终,www.conf中的相关配置段为:

pm = dynamic pm.max_children = 50 pm.start_servers = 17 pm.min_spare_servers = 10 pm.max_spare_servers = 25 pm.max_requests = 500

pm.max_requests = 500是一个关键的安全阀。它表示每个子进程在处理完 500 个请求后,会自动优雅退出并被新的进程替代。这能有效防止 PHP 扩展的内存泄漏累积,是保障服务长期稳定的核心参数。

5.2 SELinux 与 Socket:listen = /run/php-fpm/www.sock的权限密码

在 CentOS 8 上,PHP-FPM 默认监听一个 Unix socket 文件/run/php-fpm/www.sock,而不是127.0.0.1:9000。这是出于安全和性能的双重考虑。但这也带来了新的 SELinux 权限问题。

Nginx 的 worker 进程,其 SELinux 上下文是system_u:system_r:httpd_t:s0。而 PHP-FPM 的 master 进程,其上下文是system_u:system_r:php_fpm_t:s0。为了让httpd_t进程能够connecttophp_fpm_t进程的 socket,必须有一条明确的 SELinux 策略规则。

这条规则默认是存在的,但前提是 socket 文件的路径和上下文正确。/run/php-fpm/目录的默认上下文是system_u:object_r:httpd_var_run_t:s0,这正是 Nginx 所需的。所以,你只需确保www.conf中的listen配置是:

listen = /run/php-fpm/www.sock listen.owner = nginx listen.group = nginx listen.mode = 0660

listen.ownerlisten.group必须设为nginx,因为 Nginx 的 worker 进程是以nginx用户身份运行的。如果这里写成apachewww-data,Nginx 就没有权限访问这个 socket,必然502

验证方法是:

ls -Z /run/php-fpm/www.sock # 输出应为:system_u:object_r:httpd_var_run_t:s0 system_u:object_r:httpd_var_run_t:s0 nginx nginx /run/php-fpm/www.sock

如果context不对,用sudo semanage fcontext -a -t httpd_var_run_t "/run/php-fpm(/.*)?"添加永久规则,再sudo restorecon -Rv /run/php-fpm恢复上下文。

5.3 日志与监控:slowlog是你的第二双眼睛

502错误往往只是表象,真正的病因可能是某个 PHP 脚本执行了 30 秒才返回,超出了 Nginx 的fastcgi_read_timeout(默认 60s),导致 Nginx 主动断开连接,返回502

PHP-FPM 的slowlog功能,就是为此而生。在www.conf中取消注释并配置:

slowlog = /var/log/php-fpm/www-slow.log request_slowlog_timeout = 5s

这意味着,任何执行时间超过 5 秒的 PHP 请求,其完整的调用栈(Call Stack)都会被记录到www-slow.log中。日志格式如下:

[12-Oct-2023 14:23:45] [pool www] pid 12345 script_filename = /var/www/html/index.php [0x00007f8b1c0a1234] mysqli_query() /var/www/html/db.php:45 [0x00007f8b1c0a1234] get_user_data() /var/www/html/user.php:12 [0x00007f8b1c0a1234] main() /var/www/html/index.php:8

这个日志清晰地告诉你:是index.php第 8 行调用了user.phpuser.php第 12 行调用了get_user_data(),而get_user_data()db.php第 45 行执行了一个慢查询。有了这个线索,你就可以直奔mysqli_query()那行,去分析 SQL 是否缺少索引,或者是否在循环里执行了 N+1 查询。

提示:request_slowlog_timeout的值要根据你的业务 SLA 来设定。电商下单接口,500ms 就算慢;后台报表导出,5s 也合理。不要盲目追求“越小越好”,要结合业务场景。

6. 最后的收尾:一个可立即投入生产的最小化 LEMP 检查清单

当你完成了上述所有步骤,Nginx、PHP-FPM、MariaDB 都在运行,一个简单的phpinfo()页面也能正常显示,这并不意味着你的 LEMP 环境已经 ready for production。一个真正可靠的环境,必须经过一套严苛的“出厂检验”。这是我过去十年,为上百个项目部署 LEMP 后,总结出的、每次上线前必做的六项检查。

6.1 检查项一:服务依赖与启动顺序

在 CentOS 8 中,systemd的依赖关系至关重要。PHP-FPM 必须在 MariaDB 启动之后才能启动,否则它会因为无法连接数据库而失败。同样,Nginx 必须在 PHP-FPM 启动之后才能启动,否则fastcgi_pass会指向一个不存在的 socket。

检查方法:

# 查看 PHP-FPM 服务的依赖 systemctl list-dependencies php-fpm.service --reverse # 查看 Nginx 服务的依赖 systemctl list-dependencies nginx.service --reverse

你应该看到php-fpm.service出现在mariadb.serviceAfter=列表中,而nginx.service出现在php-fpm.serviceAfter=列表中。如果没有,你需要手动编辑服务单元文件:

sudo systemctl edit php-fpm.service

在打开的编辑器中输入:

[Unit] After=mariadb.service

保存退出后,sudo systemctl daemon-reload重载配置。

6.2 检查项二:防火墙(firewalld)的精确放行

CentOS 8 默认使用firewalld。仅仅firewall-cmd --permanent --add-service=http是不够的。这个命令只放行了80端口,但你的 PHP 应用很可能还需要443(HTTPS)、8080(管理后台)、3307(MySQL 代理)等端口。

更安全的做法是,为每个服务创建一个自定义的firewalld服务文件。例如,为 MySQL 代理创建/etc/firewalld/services/mysql-proxy.xml

<?xml version="1.0" encoding="utf-8"?> <service> <short>MySQL Proxy</short> <description>Allow external access to MySQL via Nginx stream.</description> <port protocol="tcp" port="3307"/> </service>

然后启用它:

sudo firewall-cmd --permanent --add-service=mysql-proxy sudo firewall-cmd --reload

这样,你的防火墙规则就不再是模糊的“开放 HTTP”,而是精确的“开放 MySQL 代理服务”,审计和排查时一目了然。

6.3 检查项三:日志轮转(logrotate)的无声守护

/var/log/nginx//var/log/php-fpm/下的日志文件,如果不加管控,几个月后就会撑爆磁盘。logrotate是 Linux 的日志管家,但它的默认配置往往过于宽松。

检查/etc/logrotate.d/nginx/etc/logrotate.d/php-fpm。确保它们包含以下关键参数:

/var/log/nginx/*.log /var/log/php-fpm/*.log { daily missingok rotate 52 compress delaycompress notifempty create 0644 nginx nginx sharedscripts postrotate /bin/kill -USR1 `cat /run/nginx.pid 2>/dev/null` 2>/dev/null || true endscript }

rotate 52表示保留 52 个归档(一年),delaycompress表示压缩上一个归档,而不是刚轮转的那个,这能减少 I/O 压力。postrotate脚本里的kill -USR1是通知 Nginx 重新打开日志文件,这是无缝轮转的关键。

6.4 检查项四:SELinux 策略的“最小权限”验证

SELinux 是你的最后一道防线,但它也最容易被“一刀切”地禁用。一个健康的环境,应该让 SELinux 处于enforcing模式,并且所有服务都能在该模式下完美运行。

验证方法是:在enforcing模式下,执行所有核心业务流程(如用户登录、商品下单、后台数据导出),同时监控 SELinux 审计日志:

sudo ausearch -m avc -ts today | grep -E "(nginx|php-fpm|mariadb)"

如果这个命令没有任何输出,恭喜你,你的 SELinux 策略是干净的。如果有输出,不要急于setsebool,而是用audit2whyaudit2allow工具分析,生成最小化的自定义策略模块,然后 `semodule -i my

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询