1. 为什么 CentOS 8 上搭 LEMP 不是“照着命令敲就行”的事LEMPLinux 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.2、7.3、8.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 StreamsCentOS 8 的“开关”与“保险丝”CentOS 8 的模块流机制本质上是一个“软件版本的配电箱”。它把传统上由yum统一管理的软件包按功能领域划分成一个个独立的“模块”Module每个模块又包含多个可供选择的“流”Stream每个流代表一个稳定的主版本号如php:7.2,php:8.0。这个设计让系统管理员能像拉闸一样为不同应用精确指定其依赖的软件版本避免“一个升级全盘崩溃”的灾难。但它的代价是所有安装操作都必须先“合上对应开关”否则dnf就会假装看不见那个软件。2.1 查看可用模块与当前状态dnf module list是你的万用表在敲任何install命令前第一步永远是dnf module list。这不是可选项而是强制检查项。它会列出所有已知模块及其流的状态。我们重点关注php、nginx和mysql这三行$ 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.0是php模块提供的所有可选流。common [d], devel, minimal是php模块内部的“子配置集”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.3nginx:1.16测试环境是php:8.0nginx: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.0nginx: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 的默认配置试图监听80和443端口这需要http_port_t类型的端口上下文。如果这个上下文没被正确分配服务就会因权限不足而退出。验证方法是查看 SELinux 审计日志sudo ausearch -m avc -ts recent | grep nginx如果输出类似avc: denied { name_bind } for ... scontextsystem_u:system_r:httpd_t:s0 tcontextsystem_u:object_r:port_t:s0 tclasstcp_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注意不要给80和443打标签这两个端口在默认策略中已经是http_port_t强行添加会报错。上面的8080和8443是为后续反向代理预留的非标准端口。3.2 配置http与stream的双轨世界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 ok和test 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. MySQLMariaDB与 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 --usermysql --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 USER和GRANT创建应用专用账户。4.2 PHP 连接mysqlndvsmysqli选哪个PHP 8.0 默认启用的 MySQL 扩展是mysqlndMySQL Native Driver它是一个纯 PHP 实现的驱动性能优于旧的libmysqlclient。但很多老项目还在用mysqli扩展而mysqli在 PHP 8.0 中是可选的需要单独安装sudo dnf install php-mysqlnd php-mysqli这两者的关系是mysqli是一个面向对象/过程的 API 接口而mysqlnd是它底层的“发动机”。你可以只装php-mysqlnd然后用 PDOPHP 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;charsetutf8mb4; $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 开发者在phpMyAdmin或Adminer这类 Web 管理工具里看到某个表的“碎片率”高达 30%就慌了神以为会影响 PHP 查询速度。真相是InnoDB 的碎片对普通 OLTP在线事务处理查询的影响微乎其微。InnoDB 使用聚簇索引Clustered Index数据行是按主键顺序物理存储的。所谓的“碎片”是指数据页Page在磁盘上不连续或者页内有大量空闲空间Free Space。对于SELECT * FROM table WHERE id ?这样的主键查询InnoDB 只需一次 BTree 查找就能定位到数据页碎片与否毫无影响。真正需要关注碎片的场景是全表扫描SELECT * FROM huge_table碎片多意味着磁盘寻道次数增加I/O 延迟上升。大批量INSERT/UPDATE后的OPTIMIZE TABLE这会重建表消除碎片但会锁表线上慎用。在 CentOS 8 的 MariaDB 10.3 中判断碎片的准确方法是查询INFORMATION_SCHEMA.INNODB_SYS_TABLESPACESSELECT 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 进程管理模型static、dynamic与ondemand的抉择PHP-FPM 的核心配置文件是/etc/php-fpm.d/www.conf。其中最关键的参数是pmProcess Manager它有三种模式pm static固定数量的子进程。例如pm.max_children 50则始终有 50 个 PHP 进程常驻内存。pm dynamic动态进程管理。根据负载自动伸缩需配置pm.start_servers、pm.min_spare_servers、pm.max_spare_servers和pm.max_children。pm ondemand按需启动。初始为 0 个进程有请求进来时才 fork 新进程空闲超时后销毁。在 CentOS 8 的生产环境中我强烈推荐pm dynamic。static模式浪费内存ondemand模式在高并发突增时fork 新进程的开销会导致请求延迟飙升甚至触发502。dynamic模式的精髓在于四个参数的数学关系。以一台 4 核 8GB 内存的服务器为例计算过程如下估算单个 PHP 进程内存占用用ps aux --sort-%mem | head -n 10查看正在运行的php-fpm进程的 RSSResident Set Size。实测值通常在80MB到120MB之间。我们取保守值100MB。计算最大子进程数pm.max_children总内存 * 0.7 / 单进程内存 8192MB * 0.7 / 100MB ≈ 57。所以pm.max_children 50留 20% 余量给系统和其他服务。计算空闲进程数pm.min_spare_servers和pm.max_spare_servers它们应分别约为max_children的 20% 和 50%。即pm.min_spare_servers 10pm.max_spare_servers 25。计算启动进程数pm.start_servers取min_spare_servers和max_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 500pm.max_requests 500是一个关键的安全阀。它表示每个子进程在处理完 500 个请求后会自动优雅退出并被新的进程替代。这能有效防止 PHP 扩展的内存泄漏累积是保障服务长期稳定的核心参数。5.2 SELinux 与 Socketlisten /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 0660listen.owner和listen.group必须设为nginx因为 Nginx 的 worker 进程是以nginx用户身份运行的。如果这里写成apache或www-dataNginx 就没有权限访问这个 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 是否缺少索引或者是否在循环里执行了 N1 查询。提示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.service的After列表中而nginx.service出现在php-fpm.service的After列表中。如果没有你需要手动编辑服务单元文件sudo systemctl edit php-fpm.service在打开的编辑器中输入[Unit] Aftermariadb.service保存退出后sudo systemctl daemon-reload重载配置。6.2 检查项二防火墙firewalld的精确放行CentOS 8 默认使用firewalld。仅仅firewall-cmd --permanent --add-servicehttp是不够的。这个命令只放行了80端口但你的 PHP 应用很可能还需要443HTTPS、8080管理后台、3307MySQL 代理等端口。更安全的做法是为每个服务创建一个自定义的firewalld服务文件。例如为 MySQL 代理创建/etc/firewalld/services/mysql-proxy.xml?xml version1.0 encodingutf-8? service shortMySQL Proxy/short descriptionAllow external access to MySQL via Nginx stream./description port protocoltcp port3307/ /service然后启用它sudo firewall-cmd --permanent --add-servicemysql-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而是用audit2why和audit2allow工具分析生成最小化的自定义策略模块然后 semodule -i my