1. 为什么这套组合至今仍是自建站点的稳妥选择
把时间拨回到几年前,如果你问一个刚接触独立建站的人该用什么方案,十有八九会听到“LNMP”这个词。Linux + Nginx + MySQL + PHP,这四个字母的组合几乎撑起了早期互联网上相当一部分动态站点的运行底座。而WordPress作为内容管理系统的代表,恰好把这四者串联成了一个完整的故事。今天各种容器化、面板化工具层出不穷,但我依然认为,手动在CentOS 7上把MySQL、Nginx、PHP 7和WordPress一步步跑通,是理解Web服务运行逻辑最扎实的一条路径。
这套方案的核心价值在于“可控”。用面板工具确实快,点几下鼠标站点就起来了,但一旦遇到性能瓶颈、权限报错、版本冲突,你往往不知道从哪里下手。而手动部署的过程,本质上是在亲手搭建一条从用户请求到页面渲染的完整链路:Nginx负责接客和分发,PHP负责动态处理,MySQL负责数据存取,WordPress则是在这套地基上盖起来的房子。你清楚每一块砖的位置,出了问题就能顺着链路往回找。
这篇文章适合谁看?如果你手里有一台CentOS 7的服务器,想从零搭一个属于自己的WordPress站点,并且希望搞明白每一步背后的道理,那这篇内容就是为你准备的。我会把安装顺序、参数配置、权限设置、常见报错都拆开讲清楚,尽量做到你照着做就能跑通,同时知道为什么要这么做。需要说明的是,文中涉及的具体版本号和路径是基于常见实践给出的参考,实际部署时你可以根据官方源的最新情况做微调。
在正式开始之前,先明确一个部署顺序上的原则:先装数据库,再装PHP,然后装Nginx,最后部署WordPress。这个顺序不是随便定的。MySQL作为数据层,独立于Web服务,先装好可以避免后续PHP扩展编译时找不到数据库头文件;PHP作为中间层,需要和Nginx通过FastCGI通信,先装PHP再配置Nginx,能让Nginx的配置文件一次写对;WordPress作为应用层,依赖前面三者全部就绪,放最后最合理。很多人一上来就急着下载WordPress,结果环境没配好,页面报个数据库连接错误,反而不知道从哪查起。
2. 系统准备与软件源的那些门道
2.1 最小化安装之后必须先做的几件事
拿到一台刚装好CentOS 7的服务器,不管是云主机还是本地虚拟机,第一件事不是急着装软件,而是把系统的基础状态调整好。最小化安装的系统往往缺少很多常用工具,网络配置也可能不是最优状态。我习惯先做这么几件事:更新系统、安装常用工具、配置时区、检查防火墙状态。
更新系统用yum update -y,这一步会拉取所有已安装软件包的最新版本。有人觉得这一步浪费时间,但实测下来,很多莫名其妙的依赖冲突都是因为系统包太旧导致的。更新完之后装几个必备工具:yum install -y wget vim net-tools lsof。wget用来下载软件包,vim用来编辑配置文件,net-tools提供netstat命令方便查端口,lsof能看文件被哪个进程占用,这几个在后面排查问题时都会用到。
时区设置经常被忽略,但WordPress的发布时间、日志时间都依赖系统时区。用timedatectl set-timezone Asia/Shanghai把时区设成东八区,然后timedatectl确认一下。防火墙方面,CentOS 7默认用的是firewalld,我建议先把它关掉或者放行必要端口。如果是内网测试环境,直接systemctl stop firewalld && systemctl disable firewalld最省事;如果是公网环境,那就老老实实放行80和443端口,别图省事把防火墙全关了。
提示:关闭防火墙和SELinux只建议在测试环境或内网环境操作。公网服务器请务必保留防火墙,并通过
firewall-cmd按需放行端口。
SELinux是CentOS上另一个容易让人抓狂的东西。它经常导致Nginx读不了文件、PHP写不了目录。我一般会把它设成permissive模式,也就是只记录不拦截,这样既保留了审计日志,又不会影响部署。命令是setenforce 0临时生效,要永久生效就编辑/etc/selinux/config,把SELINUX=enforcing改成SELINUX=permissive,重启后生效。
2.2 软件源的选择:为什么我不推荐直接用默认源
CentOS 7自带的yum源里,PHP版本停留在5.4,MySQL的替代品MariaDB版本也比较旧。WordPress现在对PHP版本的要求越来越高,5.4早就不能满足需求了。所以我们需要引入额外的软件源来获取PHP 7和较新的MySQL版本。
PHP 7我推荐用Remi源,这是社区里维护得比较活跃的第三方源,版本更新及时,扩展也齐全。MySQL官方源则提供了MySQL 5.7和8.0的社区版。这里有个细节要注意:CentOS 7的默认源里其实有MariaDB,而MariaDB和MySQL在安装时会有包冲突。所以正确的做法是先用yum remove把系统里可能存在的MariaDB相关包清理干净,再安装MySQL官方源。
具体操作上,先安装EPEL源作为基础,因为Remi源依赖它:
yum install -y epel-release yum install -y https://rpms.remirepo.net/enterprise/remi-release-7.rpm yum install -y yum-utils然后启用Remi的PHP 7.4仓库(这是目前兼容性和稳定性都比较好的版本):
yum-config-manager --enable remi-php74MySQL这边,去官方下载社区版的repo包安装:
yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm装完之后可以用yum repolist看看源是否都生效了。这里有个经验:如果你发现yum install的时候提示某个包在多个源里都有,可以用--enablerepo参数明确指定从哪个源装,避免装错版本。
2.3 安装顺序背后的依赖逻辑
前面说了先数据库、再PHP、后Nginx的顺序,这里展开讲讲为什么。MySQL安装的时候会创建mysql用户和用户组,并且初始化数据目录。如果你先装了PHP,PHP的mysqlnd扩展在编译时可能会去探测系统里的MySQL客户端库,如果这时候MySQL还没装,探测就会失败,导致PHP连数据库的扩展缺失。虽然可以后面补装,但多一道手续。
Nginx放后面装,是因为Nginx的配置文件里要指定PHP-FPM的监听地址。如果PHP还没装,你写Nginx配置的时候就得凭记忆写,装完PHP再回来改,容易漏。先把PHP装好,确认php-fpm服务能起来、监听在哪个端口或socket上,再去写Nginx的配置,一次就能写对。
WordPress放最后,是因为它需要前面三个都正常。你可以先用一个简单的phpinfo.php文件测试PHP和Nginx是否连通,再用一个测试脚本验证PHP能不能连上MySQL,全部通过之后再部署WordPress,这样出问题的时候排查范围就小很多。
3. MySQL的安装与安全初始化
3.1 安装MySQL 8.0并启动服务
源配置好之后,安装MySQL就一条命令:
yum install -y mysql-community-server这个包会同时装上mysql-community-client、mysql-community-common等依赖。安装完成后,启动服务并设置开机自启:
systemctl start mysqld systemctl enable mysqldMySQL 8.0和5.7有一个很大的不同:首次启动时会自动生成一个临时root密码,并且密码策略默认要求比较高。临时密码可以用这条命令找出来:
grep 'temporary password' /var/log/mysqld.log拿到临时密码后,用mysql -uroot -p登录,然后立刻修改密码。这里要注意,MySQL 8.0默认的密码验证插件是caching_sha2_password,而PHP 7.4的mysqlnd扩展是支持这个插件的,所以不用像以前那样改成mysql_native_password。但如果你用的是更老的PHP版本,可能就需要改插件了。
修改密码的命令是:
ALTER USER 'root'@'localhost' IDENTIFIED BY '你的强密码';密码里最好包含大小写字母、数字和特殊符号,长度12位以上。这不是形式主义,公网服务器上弱密码被暴力破解是分分钟的事。
3.2 安全初始化脚本到底改了什么
MySQL自带一个mysql_secure_installation脚本,很多人跑一遍就完事,但不知道它具体做了什么。我建议至少跑一遍,并且理解每一步的含义。这个脚本会依次问你几个问题:是否设置密码验证插件、是否修改root密码、是否删除匿名用户、是否禁止root远程登录、是否删除test数据库、是否重新加载权限表。
删除匿名用户这一条特别重要。MySQL安装后默认会创建一个空用户名的匿名账户,允许任何人从本地登录。虽然它只能访问test库,但留着就是个隐患。禁止root远程登录也是同理,root只允许从本机登录,需要远程管理的话应该另建一个权限受限的账户。
test数据库默认任何用户都能访问,删掉它避免误用。最后重新加载权限表让所有修改立即生效。这几步做完,MySQL的基础安全水位就上来了。
3.3 为WordPress单独建库建用户的必要性
直接用root账户跑WordPress是很多教程里的做法,但这其实是个坏习惯。WordPress的代码是公开的,万一某个插件有漏洞导致数据库配置泄露,root权限就等于把整个数据库服务器交出去了。正确的做法是创建一个只对WordPress库有权限的专用账户。
操作如下:
CREATE DATABASE wordpress DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER 'wpuser'@'localhost' IDENTIFIED BY '另一个强密码'; GRANT ALL PRIVILEGES ON wordpress.* TO 'wpuser'@'localhost'; FLUSH PRIVILEGES;字符集选utf8mb4而不是utf8,是因为utf8mb4才是真正的完整UTF-8实现,能存储emoji等四字节字符。WordPress从4.2版本开始就推荐用utf8mb4了。排序规则用utf8mb4_unicode_ci,它对多语言的支持更好。
这里有个细节:wpuser的主机部分写的是localhost,意味着这个账户只能从本机连接。如果WordPress和MySQL不在同一台机器上,才需要改成具体的IP或%。同机部署的情况下,localhost是最安全的。
4. PHP 7.4的安装与FPM配置调优
4.1 装哪些扩展,为什么一个都不能少
PHP本体装起来简单,yum install -y php php-fpm就完事了。但WordPress运行需要一系列扩展,缺一个就可能出现白屏、上传失败、图片处理异常等问题。我列一个必备清单:
| 扩展名 | 作用 | 缺失后的典型症状 |
|---|---|---|
| php-mysqlnd | 连接MySQL数据库 | 安装时提示无法连接数据库 |
| php-gd | 图像处理 | 上传图片无法生成缩略图 |
| php-mbstring | 多字节字符串处理 | 中文乱码、截断异常 |
| php-xml | XML解析 | 部分主题和插件报错 |
| php-json | JSON数据处理 | REST API不可用 |
| php-zip | 压缩包处理 | 无法在线安装插件主题 |
| php-curl | HTTP请求 | 无法调用外部接口 |
| php-opcache | 字节码缓存 | 性能下降,但功能正常 |
安装命令可以一次性写全:
yum install -y php php-fpm php-mysqlnd php-gd php-mbstring php-xml php-json php-zip php-curl php-opcache装完之后用php -m看看扩展列表,确认上面这些都在。如果某个扩展没出现,检查是不是源没启用对,或者包名拼错了。
4.2 php-fpm的进程管理参数怎么调
php-fpm的配置文件在/etc/php-fpm.d/www.conf,里面有几个参数直接决定了站点的并发能力。默认配置是给低配机器用的,稍微有点流量就会不够。
pm参数决定进程管理模式,有三个可选值:static、dynamic、ondemand。static是固定数量进程,适合内存充足且流量稳定的场景;dynamic是动态增减,适合大多数场景;ondemand是来了请求才创建进程,省内存但响应慢。我一般用dynamic。
pm.max_children是最关键的参数,它决定同时能处理多少个PHP请求。这个值不能拍脑袋定,要根据服务器内存算。每个PHP进程大概占用30到50MB内存,假设服务器有2GB内存,留给PHP 1GB,那max_children设20到30比较稳妥。设太大了内存不够会触发OOM,设太小了并发请求排队。
pm.start_servers、pm.min_spare_servers、pm.max_spare_servers这三个是dynamic模式下的配套参数,分别控制启动时的进程数、最少空闲进程数、最多空闲进程数。一般设成max_children的25%、25%、75%左右。
还有一个pm.max_requests,表示每个进程处理多少个请求后自动重启。设成500左右可以避免PHP进程长时间运行导致的内存泄漏累积。这个参数对稳定性帮助很大,建议不要设成0(不限制)。
4.3 让php-fpm和Nginx顺畅通信的两种方式
php-fpm和Nginx之间通过FastCGI协议通信,有两种连接方式:TCP端口和Unix socket。默认配置用的是TCP,监听在127.0.0.1:9000。Unix socket则是用一个文件来通信,比如/run/php-fpm/www.sock。
两种方式各有优劣。TCP方式配置简单,跨机器也能用,但走网络协议栈有一点点开销。Unix socket只在本机通信,性能略好,而且可以通过文件权限控制访问,安全性更高。同机部署的情况下我推荐用Unix socket。
改法是在www.conf里把listen = 127.0.0.1:9000改成listen = /run/php-fpm/www.sock,然后确保listen.owner、listen.group、listen.mode设置正确。通常设成nginx用户和组,权限660。改完重启php-fpm,用ls -l /run/php-fpm/www.sock确认文件生成了,属主属组对了,再去配Nginx。
注意:如果SELinux处于enforcing模式,Unix socket的创建和访问可能会被拦截。这也是我前面建议把SELinux设成permissive的原因之一。
5. Nginx的配置与WordPress伪静态规则
5.1 安装Nginx并理解它的配置文件结构
Nginx的安装同样简单:
yum install -y nginx systemctl start nginx systemctl enable nginx装完之后访问服务器IP,应该能看到Nginx的默认欢迎页。如果看不到,先检查防火墙和SELinux,再检查Nginx是否真的在监听80端口。
Nginx的配置文件结构是分层的。主配置文件/etc/nginx/nginx.conf里通过include指令引入了/etc/nginx/conf.d/*.conf和/etc/nginx/default.d/*.conf。我习惯在conf.d目录下为每个站点建一个独立的配置文件,比如wordpress.conf,这样管理起来清晰,不会把主配置文件改得乱七八糟。
主配置文件里有一个server块是默认的,监听80端口,根目录在/usr/share/nginx/html。我们要做的是新建一个server块,把根目录指向WordPress的安装目录,并且配置PHP处理规则。
5.2 server块里每一行配置的用意
一个能跑WordPress的server块大概长这样:
server { listen 80; server_name your-domain.com www.your-domain.com; root /var/www/wordpress; index index.php index.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ \.php$ { fastcgi_pass unix:/run/php-fpm/www.sock; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } location ~ /\.ht { deny all; } }逐行解释一下。listen 80是监听端口,如果配了SSL证书就再加一个listen 443 ssl。server_name写域名,没有域名就写IP或者下划线_表示匹配所有。root是站点根目录,后面部署WordPress的时候要把文件放这里。index指定默认首页文件。
location /里的try_files是WordPress伪静态的关键。它的逻辑是:先找有没有对应的真实文件,没有就找目录,还没有就把请求交给index.php处理。这样/2024/01/hello-world/这样的固定链接才能正确路由到WordPress的入口文件,而不是返回404。
location ~ \.php$匹配所有以.php结尾的请求,交给php-fpm处理。fastcgi_pass指定通信地址,这里用的是Unix socket,和前面php-fpm的配置对应。SCRIPT_FILENAME告诉php-fpm要执行哪个文件,$document_root就是上面的root路径。
location ~ /\.ht是禁止访问.htaccess等隐藏文件。虽然Nginx不读.htaccess,但WordPress目录里可能有这类文件,禁止访问能避免配置信息泄露。
5.3 伪静态规则配错会导致什么现象
伪静态规则是WordPress部署里最容易出问题的地方。如果try_files没配或者配错了,典型现象是:首页能打开,但点进任何文章、页面都返回404。这是因为WordPress的固定链接依赖URL重写,Nginx不认识这些路径,就当成不存在的文件处理了。
还有一种情况是后台能进,前台文章打不开。这通常是因为try_files里漏了?$args,导致查询参数丢失。WordPress有些功能依赖URL里的查询字符串,丢了就会异常。
排查的时候,可以先在WordPress后台把固定链接改成“朴素”模式(也就是带?p=123这种),如果朴素模式能访问,说明PHP和数据库都没问题,问题就出在伪静态规则上。改回“文章名”模式,然后检查Nginx配置里的try_files那一行。
另外,改完Nginx配置一定要用nginx -t测试语法,通过了再systemctl reload nginx。直接restart会中断现有连接,reload则是平滑加载。如果nginx -t报错,根据提示的行号去改,别硬重启,否则Nginx起不来,整个站点都挂了。
6. WordPress部署与权限设置的坑
6.1 下载解压与目录归属
WordPress的下载很简单:
cd /var/www wget https://wordpress.org/latest.tar.gz tar -xzf latest.tar.gz mv wordpress /var/www/wordpress解压出来的目录默认属主是root。Nginx和php-fpm运行的用户通常是nginx或者apache,如果目录属主是root且权限是755,php-fpm就没法往wp-content/uploads里写文件,上传图片会失败。
正确的做法是把整个WordPress目录的属主改成php-fpm的运行用户。先确认php-fpm跑在哪个用户下,看www.conf里的user和group配置,通常是nginx或者apache。然后:
chown -R nginx:nginx /var/www/wordpress find /var/www/wordpress -type d -exec chmod 755 {} \; find /var/www/wordpress -type f -exec chmod 644 {} \;目录755、文件644是Web服务的标准权限。目录需要执行权限才能进入,文件不需要执行权限。这样设置既保证了php-fpm能读写,又不会给其他用户过多权限。
6.2 wp-config.php里那几个关键常量
WordPress的配置文件需要手动创建。把wp-config-sample.php复制成wp-config.php,然后填入数据库信息:
define('DB_NAME', 'wordpress'); define('DB_USER', 'wpuser'); define('DB_PASSWORD', '你设置的密码'); define('DB_HOST', 'localhost'); define('DB_CHARSET', 'utf8mb4'); define('DB_COLLATE', '');DB_HOST写localhost的时候,PHP会优先用Unix socket连接MySQL,比TCP快一点。如果写127.0.0.1就是走TCP。两者都可以,但要注意MySQL用户的主机部分要和这里对应。
还有一组安全密钥需要填,去WordPress的密钥生成页面拿,或者用curl命令生成:
curl -s https://api.wordpress.org/secret-key/1.1/salt/把输出的八行define语句粘贴到配置文件里。这组密钥用于加密登录cookie,每个站点都应该不一样,泄露了会导致登录状态被伪造。
$table_prefix默认是wp_,可以改成别的,但没必要,改了对安全性的提升微乎其微,反而增加记忆负担。
6.3 上传目录权限与SELinux的纠缠
即使目录属主和权限都设对了,如果SELinux是enforcing模式,php-fpm往wp-content/uploads写文件还是会被拦截。日志里会看到avc: denied的记录。这就是为什么我前面建议把SELinux设成permissive。
如果你坚持要用enforcing模式,那就需要给WordPress目录打上正确的SELinux上下文标签:
chcon -R -t httpd_sys_rw_content_t /var/www/wordpress/wp-contenthttpd_sys_rw_content_t表示允许Web服务读写这个目录。但要注意,chcon是临时的,系统重启或者文件重新标记后会失效。永久生效需要用semanage fcontext配合restorecon,操作起来更复杂。对于大多数个人站点,permissive模式已经足够安全,没必要在这上面耗太多时间。
7. 部署完成后的验证与常见故障排查
7.1 从浏览器到数据库的完整验证链路
部署完成后,不要急着庆祝,按顺序验证一遍。第一步,浏览器访问服务器IP或域名,应该看到WordPress的安装向导页面。如果看到的是Nginx默认页,说明server_name或者root配错了。如果看到502 Bad Gateway,说明Nginx连不上php-fpm,检查socket路径和权限。
第二步,填写站点信息,创建管理员账户。如果这一步报“无法连接数据库”,检查wp-config.php里的数据库信息,以及MySQL用户的主机限制。可以在服务器上用mysql -uwpuser -p wordpress测试一下能不能登录。
第三步,登录后台,发一篇文章,上传一张图片。文章能发布说明数据库写入正常,图片能上传说明目录权限正常。这三步都过了,基本部署就成功了。
7.2 502、404、403三类报错的快速定位
502 Bad Gateway是最常见的。原因通常是php-fpm没启动、socket路径不对、或者socket权限不对。排查顺序:systemctl status php-fpm看服务状态,ls -l /run/php-fpm/www.sock看socket是否存在且权限正确,tail -f /var/log/nginx/error.log看具体报错。
404 Not Found分两种。一种是访问任何页面都404,那可能是root路径配错了,Nginx找不到文件。另一种是首页正常但文章404,那就是伪静态规则的问题,检查try_files。
403 Forbidden通常是权限问题。Nginx没有权限读取站点目录,或者目录下没有index文件且不允许列目录。检查目录权限和SELinux状态。
7.3 日志文件才是排查问题的第一现场
很多人遇到问题喜欢在网上搜,但最快的办法其实是看日志。Nginx的错误日志在/var/log/nginx/error.log,php-fpm的日志在/var/log/php-fpm/www-error.log,MySQL的日志在/var/log/mysqld.log。这三个日志覆盖了从请求接入到数据存取的全链路。
看日志有个技巧:用tail -f实时跟踪,然后在浏览器里复现问题,日志里立刻就会打出对应的错误信息。比如502的时候,Nginx日志会写connect() to unix:/run/php-fpm/www.sock failed,这就直接指向了socket问题,不用瞎猜。
WordPress自己的调试日志也很有用。在wp-config.php里把WP_DEBUG设成true,WP_DEBUG_LOG也设成true,错误会写到wp-content/debug.log。插件冲突、主题报错这类问题,看这个日志最直接。不过调试完记得关掉,否则日志文件会越来越大,而且错误信息暴露在前台有安全风险。
8. 上线前值得再花十分钟做的几件事
站点能跑起来只是第一步,上线前还有几项配置能显著提升安全性和稳定性。第一件是配置HTTPS。现在有免费证书可以用,申请下来后在Nginx里加一个443的server块,把证书路径填进去,然后把80端口的请求301重定向到443。WordPress后台的站点地址也要从http改成https,否则会出混合内容警告。
第二件是限制WordPress后台的访问来源。如果只是自己用,可以在Nginx里给/wp-admin和/wp-login.php加一个IP白名单,只允许自己的IP访问。这样即使密码泄露,攻击者也进不了后台。配置方式是在对应的location块里加allow 你的IP; deny all;。
第三件是设置数据库自动备份。WordPress的内容全在数据库里,丢了就全没了。可以用mysqldump配合crontab每天凌晨备份一次,备份文件存到另一个目录或者远程存储。命令大概是mysqldump -uwpuser -p密码 wordpress > /backup/wordpress-$(date +%F).sql,然后写个脚本清理七天前的旧备份。
第四件是调整PHP和Nginx的上传大小限制。默认的PHP上传限制是2MB,现在随便一张图片都可能超过。改php.ini里的upload_max_filesize和post_max_size,比如都设成64MB,然后Nginx的client_max_body_size也要相应调整,否则大文件会被Nginx直接拦掉。
这几件事做完,一个基本可用的WordPress站点就算正式上线了。整个过程走下来,你会发现最耗时间的往往不是安装本身,而是各种权限、路径、配置的细节调试。但正是这些细节,构成了你对Web服务运行机制的真实理解。下次再遇到类似的环境搭建,你就能凭经验快速定位问题,而不是从头开始搜教程。