
1. 为什么我会推荐 LNMP/LNAMP一套论坛架构背后的选型考量先说个场景。我之前帮一个朋友做社区论坛他最早是直接用宝塔面板点两下装好了环境结果在跑 Discuz!也就是常说的“社区动力”的时候访问量一大就卡静态资源加载慢伪静态规则怎么配都不生效最后连附件目录都被人扫了一遍。后来我把他这套拆了重搭改成标准的 LNMP 架构前端 Nginx 扛请求PHP 走 FastCGI数据库独立配置瞬间就清爽了。这个项目标题里的“LNMP/LNAMP 架构部署实战”其实就是干这件事从零开始把 Linux Nginx MySQL PHP 这套组合搭起来再进一步按动静分离的思路优化最后把 Discuz 论坛完整跑通。为什么非要聊“动静分离”因为 Discuz 这类论坛程序有个典型特点页面请求里真正需要 PHP 动态计算的只有首页、帖子列表、帖子详情、用户中心那一小部分但图片、CSS、JS、附件、头像这些静态资源占了 70% 以上的流量。如果让 PHP 对每个静态文件都走一遍脚本解析服务器 CPU 白烧并发能力直接腰斩。动静分离的核心思路就是静态请求交给 Nginx 直接读磁盘返回动态请求才进入 PHP 解释器各干各的活。LNAMP 则是 LNMP 的一个变种Nginx 作为 80 端口入口后端再挂一个 Apache 专门处理 PHP适合那些依赖 .htaccess 或者某些扩展兼容性要求高的老程序——Discuz 老版本对 Apache 的 rewrite 规则适配更成熟所以 LNAMP 在旧项目里依然有一席之地。这篇文章适合谁两类人最值得看一是刚接触服务器部署、想在 CentOS 7 上把 Discuz 论坛跑起来的新手照着步骤能完整落地二是已经会用面板但想搞清楚底层逻辑、想自己做架构优化的运维我会把配置背后的“为什么这么做”也讲清楚。整篇文章基于 CentOS 7 Nginx MySQL/MariaDB PHP-FPM 组合实测环境为 2 核 4G 的云服务器Discuz! X3.5 版本全部命令可复现。2. CentOS 7 基础环境准备软件源、防火墙与 SELinux 三个坑2.1 更换软件源并安装 EPELCentOS 7 官方源里的软件版本老得吓人默认 PHP 是 5.4MySQL 是 5.6 的 MariaDB 分支PHP 5.4 连 Discuz X3.5 的最低要求都满足不了。所以第一步不是急着装软件而是先把源换掉。我的做法是# 备份并替换为阿里云源清华、腾讯源都行选离你服务器近的 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo # 安装 EPEL 扩展源 yum install -y epel-release # 安装 Remi 源用于获取新版 PHP rpm -Uvh https://mirrors.aliyun.com/remi/enterprise/remi-release-7.rpm装完 Remi 源之后要用yum --enablereporemi-php74指定版本安装。Discuz X3.5 官方要求 PHP 7.0 以上我推荐 PHP 7.4兼容性和性能平衡最好PHP 8.x 在部分老插件上会有兼容问题论坛绕不开生态和插件没必要冒这个险。装 PHP 时别贪多按 Discuz 的实际需求来yum --enablereporemi-php74 install -y php-fpm php-mysqlnd php-gd php-mbstring php-xml php-json php-curl php-zip php-redis这里我特意把php-mysqlnd单独列出来因为 Discuz 的数据库连接走的是 mysqli 扩展mysqlnd 驱动在新版本 PHP 里支持更好字符集处理也更省心。php-gd是头像缩略图、验证码生成必需的漏装会出现验证码白屏、上传图片失败的状况。2.2 防火墙和 SELinux新手最冤枉的“装好了打不开”CentOS 7 默认防火墙是 firewalld装完 Nginx 后本机访问没问题但外网死活打不开页面十有八九是 80 端口没放行systemctl start firewalld firewall-cmd --permanent --add-port80/tcp firewall-cmd --permanent --add-port8080/tcp firewall-cmd --reloadLNAMP 架构里我习惯把 Apache 监听在 8080 端口不直接暴露公网但防火墙里也要先放行便于测试调通之后再收紧。SELinux 是另一个大坑。CentOS 7 默认 enforcing 模式下Nginx 访问站点目录会被拒绝表现为 403 而不是 404很多人排查半天以为是权限配错。两条路省事做法setenforce 0临时关闭再改/etc/selinux/config把SELINUXenforcing改成disabled永久生效正规做法用chcon -R -t httpd_sys_content_t /data/wwwroot/discuz给目录打安全上下文标签。正规做法更稳妥但对新手不友好如果这台服务器只跑一个论坛我建议直接禁用 SELinux在云安全组和防火墙层面做隔离更实用。这个取舍就是真实业务场景里的常态——安全策略要匹配运维能力一步到位但管不住反而更危险。2.3 目录规划别把站点文件丢在 /root 下我见过不少人把 Discuz 解压到/root或者/tmp里然后给 PHP-FPM 配了一堆奇怪路径。我的规划是/data/wwwroot/discuz # Discuz 程序文件 /data/wwwlogs/nginx # Nginx 访问日志/错误日志 /data/wwwlogs/php # PHP-FPM 慢日志程序路径不要带空格和中文后续 rewrite 规则、备份脚本处理起来都省事。创建完成之后记得把属主交给 Nginx 用户mkdir -p /data/wwwroot/discuz chown -R nginx:nginx /data/wwwroot/discuzNginx 的 worker 进程默认以 nginx 用户运行PHP-FPM 默认以apache用户运行Remi 包里的默认配置如果两者不一致会产生“PHP 能写文件但 Nginx 读不到”的诡异问题。我后面会专门讲到怎么把 PHP-FPM 的 user 改成和 Nginx 一致。3. Nginx 与 PHP 的两种协作方式LNMP 直连 FastCGI 和 LNAMP 反代 Apache3.1 LNMPNginx 直接通过 FastCGI 协议和 PHP-FPM 通信LNMP 模式里 Nginx 拿到动态请求后直接通过fastcgi_pass扔给 PHP-FPM 监听端口。Discuz 的 Nginx 配置核心是location块里的匹配顺序和 fastcgi 参数传递我先把完整配置贴出来再逐行解释server { listen 80; server_name www.example.com; root /data/wwwroot/discuz; index index.php index.html; # 静态资源文件直接返回磁盘内容不进入 PHP location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico|css|js|txt|woff|woff2|ttf|eot|svg)$ { expires 30d; access_log off; try_files $uri 404; } # Discuz 伪静态规则核心 location / { try_files $uri $uri/ /index.php?$query_string; } # PHP 请求统一交给 FastCGI location ~ \.php($|/) { root /data/wwwroot/discuz; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; fastcgi_param PATH_INFO $fastcgi_path_info; } location ~ .*\.(sql|bak|zip|tar|gz)$ { deny all; } }这段配置里最容易被忽略的是location ~ \.php($|/)这个写法。Discuz 的伪静态地址是forum.php、thread-123-1-1.html这类格式纯 HTML 后缀的请求被try_files转给 index.php 处理时URI 里可能不带.php但 FastCGI 拿到SCRIPT_FILENAME必须是真实的 PHP 文件路径。我见过很多新手直接抄网上配置结果伪静态地址打开全部 500就是因为$document_root$fastcgi_script_name拼出来的路径不存在。用($|/)的好处是同时兼容 Discuz 某些插件生成的带 PATH_INFO 的 URL这类 URL 长这样/plugin.php?idxxx:yyy如果不加(/|$)匹配就会失败。3.2 LNAMPNginx 当门卫Apache 干重活LNAMP 的协作逻辑完全不一样Nginx 监听 80 端口遇到\.php的请求用proxy_pass转发到 Apache 的 8080 端口由 Apache 加载 mod_php 或者通过 php-fpm 执行脚本。为什么有 LNMP 了还要 LNAMP原因很现实Discuz 老版本以及其他老 PHP 程序依赖.htaccess里的RewriteRule做伪静态LNMP 模式下.htaccess完全不生效所有规则都要翻译成 Nginx 的 rewrite 语法翻译过程中正则写法稍有出入就会出现“规则失效”、“模块不可用”的兼容问题。而 LNAMP 保留了 Apache 的目录级配置能力Discuz 后台的“伪静态设置”能直接生成.htaccess上传就能用。代价是多一层代理动态请求多一跳转发但在低并发论坛场景下这层损耗微不足道。Apache 端核心配置在/etc/httpd/conf/httpd.conf里去掉两句注释Listen 8080 LoadModule proxy_module modules/mod_proxy.so LoadModule proxy_http_module modules/mod_proxy_http.so LoadModule rewrite_module modules/mod_rewrite.so同时还要让 Discuz 的.htaccess生效Directory /data/wwwroot/discuz AllowOverride All Require all granted /DirectoryNginx 端配置反而简短server { listen 80; server_name www.example.com; root /data/wwwroot/discuz; # 静态文件由 Nginx 直接返回 location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico|css|js|txt|woff|woff2|ttf|eot|svg)$ { expires 30d; access_log off; } location ~ \.php($|/) { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有个必须处理的细节proxy_set_header X-Real-IP $remote_addr。Discuz 会读取用户 IP 来记录发帖来源、封禁恶意账号如果 Nginx 代理到 Apache 时不传真实 IPApache 看到的全是127.0.0.1论坛所有会员的 IP 都会显示成本机后台“禁止 IP”功能直接失效。3.3 PHP-FPM 的并发模型到底该开多少 workerPHP-FPM 进程管理的核心参数是pm、pm.max_children、pm.start_servers。以我实测的 2 核 4G 为例pm dynamic pm.max_children 20 pm.start_servers 5 pm.min_spare_servers 3 pm.max_spare_servers 8 pm.max_requests 1000max_children不是越大越好每个 PHP-FPM 进程平均占用内存约 20-30MBDiscuz 装有缓存插件后更高20 个进程就是 600MB 左右还要给 MySQL 留内存4G 机器开 20 基本是上限。pm.max_requests 1000这个参数很多人会漏掉它的作用是让进程处理完 1000 个请求后自动重启防止 PHP 脚本里的内存泄漏长期累积导致机器被拖垮。Discuz 这类长驻程序跑了两三个月后突然变卡先看这里。4. Discuz 论坛部署全流程下载、权限、安装向导与伪静态4.1 获取程序包并解压到站点目录Discuz 官方提供的安装包是一个discuz_x3.5_SC_UTF8.zip的压缩包。下载后在本地解压再上传或者在服务器上直接用 unzip 解压都行。注意一定要上传upload目录里的内容到站点根目录而不是把整个压缩包解压出来套一层目录——这个问题我遇到不下十次解压完没有install/目录看得到但安装向导就是 404就是因为文件路径多了一层 upload。上传完成后执行cd /data/wwwroot/discuz chown -R nginx:nginx /data/wwwroot/discuz find /data/wwwroot/discuz -type d -exec chmod 755 {} \; find /data/wwwroot/discuz -type f -exec chmod 644 {} \;4.2 安装向导前必须知道的权限清单Discuz 安装程序会对一系列目录做可写检查这些目录需要 PHP 进程有写入权限chmod -R 777 /data/wwwroot/discuz/config chmod -R 777 /data/wwwroot/discuz/data chmod -R 777 /data/wwwroot/discuz/uc_server chmod -R 777 /data/wwwroot/discuz/uc_client chmod -R 777 /data/wwwroot/discuz/template这套 777 权限是 Discuz 官方安装说明的标准做法但也是安全争议最大的地方。我的经验是安装阶段先放开安装完成后立刻收回一部分——config目录可以改回 644data目录里的附件上传目录保持可写但不要让整个目录暴露可写权限。Discuz 官方术语叫“社区动力”后台还有一个“文件权限检查”工具安装完之后跑一遍按照提示把多余的写权限去掉即可。别图省事一直挂着 777服务器被人当肉鸡的案例大多从这类权限漏洞起手。4.3 安装向导和数据库初始化浏览器访问http://你的IP/install/进入安装界面后需要填写数据库信息。我先在 MySQL 里建好库和账号mysql -uroot -p CREATE DATABASE discuz DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER discuzlocalhost IDENTIFIED BY 强密码; GRANT ALL PRIVILEGES ON discuz.* TO discuzlocalhost; FLUSH PRIVILEGES;字符集选utf8mb4而不是utf8这个决定在论坛跑起来之后几乎没法改。utf8mb4 支持完整的 Emoji 表情和生僻字现在手机端发帖到处都是 Emoji如果你用了老的 utf8 编码用户一发表情帖子就报错或者变成问号。Discuz 安装向导默认会给落后配置我建议全部手动指定 utf8mb4。安装完成后务必删除install/index.php或者整个install目录这是 Discuz 安装流程特别强调了多次的不删除的话任何人访问安装页面都能重新覆盖初始化你的论坛数据。4.4 伪静态Discuz 的终极 URL 美化配置Discuz 后台“全局 → SEO 设置 → 论坛 URL 静态化”把各项全勾选之后程序会生成对应的规则。LNMP 环境下你在后台看不到可下载的.htaccess直接用我前面给出的 Nginx 配置里的 rewrite 部分。实际上 Discuz 的 Nginx 伪静态规则核心可以从官方nginx.conf适配关键一条是rewrite ^([^\.]*)/topic-(.)\.html$ $1/portal.php?modtopictopic$2 last; rewrite ^([^\.]*)/article-([0-9])-([0-9])\.html$ $1/portal.php?modarticleaid$2page$3 last; rewrite ^([^\.]*)/forum-(\w)-([0-9])\.html$ $1/forum.php?modforumfid$2page$3 last; rewrite ^([^\.]*)/thread-([0-9])-([0-9])-([0-9])\.html$ $1/forum.php?modviewthreadtid$2extrapage\%3D$4page$3 last; rewrite ^([^\.]*)/group-([0-9])-([0-9])\.html$ $1/forum.php?modgroupfid$2page$3 last; rewrite ^([^\.]*)/space-(username|uid)-(.)\.html$ $1/home.php?modspace$2$3 last; rewrite ^([^\.]*)/([a-z])-(.)\.html$ $1/$2.php?$3 last;这套规则放到 Nginx 的server块里放在location /的try_files之前。重写后thread-123-1-1.html能正常打开搜索引擎收录的 URL 也更友好。验证伪静态是否生效的方式很简单后台开启后随便找一个帖子地址看 URL 是不是变成thread-xxxx-x-x.html格式如果变成forum.php?modviewthreadtidxxxx且?还原了说明 rewrite 没加载去检查 Nginx 配置语法nginx -t。改造配置之后强制systemctl reload nginx注意是 reload 不是 restartreload 平滑加载不打断在线用户。5. 动静分离实战从配置层面把静态流量和动态流量彻底分流5.1 动静分离的本质让每类请求只走最合适的处理链路“动静分离”这四个字听起来高大上说白了就是给不同扩展名的请求安排不同处理者。一个论坛首页的完整 HTML 内容只在动态部分占一次 PHP 解析但页面里的 logo、CSS、JS、用户头像、表情包图片可能同时被几十个用户请求这些资源的内容几乎不会变让 PHP 反复解析就是浪费。Nginx 处理静态文件的效率比 PHP 高出一两个数量级它用 sendfile 系统调用直接把内核缓冲区的文件内容发送到 socket几乎不走用户态拷贝。在静态文件量较大的论坛站点动静分离做不做体现在并发承载力上可能是 500 并发直接掉到 80 的差距。5.2 静态资源缓存策略和 location 匹配优先级Nginx 的 location 匹配有优先级规则精确匹配最高然后是^~前缀匹配接下来是正则匹配~最后是普通前缀匹配。这意味着下面这段配置的匹配顺序是有讲究的location /favicon.ico { log_not_found off; access_log off; } location ^~ /data/ { expires 30d; access_log off; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|flv|ico|css|js|txt|woff|woff2|ttf|eot|svg)$ { expires 30d; access_log off; add_header Cache-Control public, max-age2592000; }我把/data/目录用^~单独圈出来因为 Discuz 的附件、头像、表情都储存在data/attachment下用^~匹配可以确保正则块不会先被其他规则抢走。expires 30d会自动给响应头加上Expires和Cache-Control浏览器第二次访问时静态资源直接走本地缓存不回源服务器。但这里有个陷阱如果论坛经常更换模板或升级版本CSS/JS 文件的 URL 不变浏览器缓存一直用旧版本用户会反馈“后台改了设置但前台看不到变化”。应对方案是 URL 上加版本号参数比如把style.css引用改成style.css?v20250101版本升级时手动改这个参数即可。5.3 动态请求识别哪些请求必须交给 PHP哪些不该很多配置把动静分离简化成“扩展名是静态的就直接返回否则全部进 PHP”这其实不对。Discuz 里有两个特殊的动态目录api/和plugin.php。api/目录下的接口比如微信登录回调、第三方登录虽然文件名是.php但有的是纯 JSON 接口有的是数据同步接口必须让 PHP 处理。我的动态规则写法是在静态规则不命中之后再由 PHP 兜底location ~ .*\.php$ { root /data/wwwroot/discuz; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; fastcgi_pass 127.0.0.1:9000; }还有一类请求容易被忽略静态目录下的恶意 PHP 文件。攻击者常往data/attachment里上传伪装成 jpg 的 PHP 马图片马的原理就是文件头伪装成图片但内容含 PHP 代码如果静态规则不够严谨被上传的马一旦能被访问并执行等于直接拿到服务器权限。我的做法是禁止/data/目录下的脚本执行location ~* ^/data/.*\.(php|pl|py|cgi)$ { deny all; }这条规则很小但在安全上非常关键。实测中攻击扫描基本每天都有拦截规则加不加被攻破的概率差别明显。5.4 动静分离后的性能基线一次真实的压测对比配置完成后我用 Apache 自带的 ab 工具压了一轮同一台 2 核 4G 服务器Discuz 首页未开启动静分离纯 LNMP 全动态处理时100 并发下 QPS 大概在 130 左右开启动静分离后纯静态请求的 QPS 直接到 1000 以上首页整体请求 QPS 也提到接近 400。这个提升不是玄学是 Nginx 静态文件处理能力和 PHP 进程池之间的量级差异。压测命令参考ab -n 2000 -c 100 http://www.example.com/注意压测时不要打生产环境建议在配置调整前和调整后各压一次对比Requests per second和Time per request两个指标。如果压测结果不升反降优先怀疑是不是 Nginx 开启了限制连接数的模块或者防火墙 rule 太多导致丢包。6. 上线前必须做的一轮加固从数据库参数到文件权限的清单式自查6.1 MySQL/MariaDB 的关键参数调整Discuz 的数据表数量多单表数据量增长快MySQL 默认配置里有两个参数基本需要动max_allowed_packet默认只有 4MB备份恢复大数据时容易报“Packet too large”调成 64MB 更稳innodb_buffer_pool_size建议设为物理内存的 50%-70%4G 内存的机器我设成 2G论坛热点数据基本能常驻内存减少磁盘 IO。[mysqld] max_allowed_packet 64M innodb_buffer_pool_size 2G skip-name-resolveskip-name-resolve的作用是让 MySQL 不做反向 DNS 解析省去每次连接都解析主机名的延迟。这对 PHP-FPM 本机连接影响不大但对远程数据库连接有收益。注意开了之后MySQL 用户授权表里的 host 字段就不能再用域名了要用 IP 或127.0.0.1。6.2 PHP 危险函数禁用和上传限制Discuz 本身对 PHP 有一份安全建议列表核心是禁用exec、system、shell_exec、passthru这类命令执行函数。在/etc/php.ini里找到disable_functions改成disable_functions passthru,exec,system,chroot,chgrp,shell_exec,popen,proc_open,ini_alter,dl,openlog,syslog,readlink,symlink,popepassthru同时把上传大小和超时时间调到位方便后续在后台传 PDF 附件、升级包upload_max_filesize 50M post_max_size 60M max_execution_time 300改完 PHP 配置后执行systemctl restart php-fpm才会生效。这一步很多新手会漏因为 PHP 配置不像 Nginx 有reload的平滑机制PHP-FPM 的配置必须 restart。别在生产高峰期重启这是我现在写进操作 checklist 的一条铁律。6.3 目录权限收敛从“能跑”到“跑得安全”安装阶段用的 777 权限在正式上线前要做一轮收敛。我按 Discuz 实际运行需求整理的最小权限分配如下目录/文件权限建议说明config/config_global.php644属主 nginx数据库密码等重要配置不给写权限data/755属主 nginx日志、缓存需要可写但不给其他用户写权限data/attachment/755属主 nginx附件上传目录必须有写权限uc_server/data/755属主 nginxUCenter 数据目录install/删除或改名安装脚本必须移除template/755属主 nginx模板文件不需要可写除非后台要装修模板收敛操作完成后去 Discuz 后台“工具 → 文件权限检查”再跑一次看到“全部通过”就可以。这一步做完最直接的感受是php -r var_dump(is_writable(/data/wwwroot/discuz/config));返回 false但论坛日常功能完全正常——这就对了运行不需要的权限就应该全部关掉。6.4 备份策略论坛数据丢不得自动化才是王道很多论坛站长死在“忘了备份”上——升级插件失败、误删数据表、被入侵后清库没有备份神仙也救不了。我习惯用两条线数据库每天凌晨全量备份程序文件每周压缩一次保留最近 7 份。#!/bin/bash # /data/backup/backup.sh BACKUP_DIR/data/backup DATE$(date %Y%m%d%H%M) mysqldump -udiscuz -p密码 discuz --single-transaction --default-character-setutf8mb4 $BACKUP_DIR/discuz_$DATE.sql tar czf $BACKUP_DIR/discuz_files_$DATE.tar.gz -C /data/wwwroot discuz find $BACKUP_DIR -name *.sql -mtime 7 -delete find $BACKUP_DIR -name *.tar.gz -mtime 7 -delete配合 crontab 每天固定时间执行crontab -e 0 3 * * * /data/backup/backup.sh /dev/null 21--single-transaction参数让 mysqldump 在 InnoDB 引擎下做一致性备份备份过程中不会锁表论坛在线用户无感知——这比不加参数直接 dump 要安全得多。备份文件最好再同步一份到对象存储或者另一台机器反正别和数据库放在同一块磁盘上否则磁盘挂了备份一起没。最后再分享一点我的实操体会这套 LNMP/LNAMP 架构我前后搭过不下十次踩的坑基本都写在上面了。如果非要提炼一条最值的经验那就是“配置要精确到参数而不是复制粘贴”。网上到处都是抄来抄去的 Discuz 配置但每台服务器内存不同、PHP 版本不同、插件装的数量不同盲目套用等于埋雷。我自己的习惯是每次装完环境第一件事先压一次测记录基线数据再改任何一个参数改完再压一次用数据验证而不是靠感觉。论坛这种长尾业务一次性把地基打牢后面运营起来会省下大量心力——尤其是动静分离这块早做和晚做的成本天差地别一旦数据量大了再改架构迁移和兼容问题会让头发白一半。希望这篇实战记录能让你少走几趟弯路。