做 WordPress 优化也有一段时间了,前前后后折腾了不少主题和插件,踩过的坑比吃过的盐还多。写这个系列,主要是记录自己从“能用就行”到“稍微讲究一点”的转变过程,里面会涉及很多实际操作层面的东西,包括改代码、调数据库、碰服务器配置这些。如果你手上正好有个 WordPress 站点,觉得后台响应慢、前端加载半天转圈、或者三天两头被各种扫描骚扰,那这篇内容应该能给你一些思路。
我这边的环境不算复杂,一台小内存云服务器,装了宝塔面板,跑的是 Nginx + PHP 7.4(后来升级到 8.1) + MySQL,用的是某个轻量级主题,没有用臃肿的页面构建器,但即便是这样,首页在优化前也得加载将近 3 秒才出完整内容,后台打开文章列表有时候能转好几圈。后面对照着排查,问题一个接一个浮出来。整个过程谈不上什么高深技术,但每一步都是实际动手调过的,记录下来希望能给你省点时间。
1. 优化前的体检:别急着动手,先搞清楚瓶颈在哪
很多人一提到 WordPress 优化,第一反应就是装个缓存插件,或者换个“号称速度飞快”的主题。但说实话,不先做一次系统体检,很多优化都是盲目的。我自己第一次做优化就是上来就装缓存,结果缓存开了反而页面错乱,折腾半天发现是插件冲突,白费功夫。
所以我强烈建议,正式开始之前先做一轮体检。不是让你用什么收费检测工具,那些工具看看热闹可以,真正要排查还是得自己来。
1.1 服务器底层环境的硬指标
我用的服务器是很普通的 2 核 2G 配置,系统是最小化安装的,没有装桌面环境。跑 WordPress 这种动态站,CPU 一般不是瓶颈,内存反倒容易吃紧。PHP 进程、MySQL、Nginx,这三个是常驻内存的大头,2G 内存如果同时开 MySQL、Redis、还有一堆常驻插件,很容易就捉襟见肘。
检查内存使用很简单,登录服务器跑free -h,看一下 used 和 available 的差值,如果 available 长期低于 200M,就该考虑优化一下常驻进程了。我一开始就是 available 只剩 100 多 M,系统Swap 咔咔作响,页面能不慢吗。
另外PHP版本也是一个容易被忽略的点。我之前用的是 PHP 7.4,WordPress 核心程序在 7.4 上跑着没问题,但性能其实还可以压一压。后来升级到 8.1,实测同一个页面,TTFB(首字节时间)从原来的 650ms 左右降到了 400ms 上下,提升非常直观。这里的原理不复杂,PHP 8 的 JIT 以及大量内部优化让解释执行的效率提升了不少,尤其是遇到一些纯计算类的逻辑,差距更明显。
1.2 数据库和插件的健康度评估
数据库往往是 WordPress 变慢的一个隐形杀手。用了半年以上的站点,如果不做清理,wp_options 表里的 transient 记录、自动草稿、修订版本这些垃圾数据会越积越多。我见过一个朋友的站,内容才几百篇文章,wp_postmeta 表却有十几万条记录,就是各种插件反复读写留下的。
一开始体检,别只看数据库体积大小,要看具体表的情况。在 phpMyAdmin 或者命令行里执行下面这个语句:
SHOW TABLE STATUS FROM `你的数据库名`;重点看Data_length和Rows两个字段。如果发现 wp_options 表的 Rows 异常大,好几千甚至上万,那说明 transient 清理的问题已经很严重了。
插件方面,我当时装了差不多 16 个插件,这在很多人眼里不算多,但里面有几个功能重复了。比如我既用了某个 SEO 插件,又用了另一个瘦身类插件,两者都加载了自己的脚本文件。插件只要启用,它的 PHP 代码就会被执行,哪怕你这个页面根本用不上它的功能。所以体检的时候顺便理一下插件清单,把那些功能重叠、半年没更新过的停用,就算完全不删,也能减小一些渲染负担。
2. 前端性能优化:速度提升的关键手段
体检完了,就得动手了。前端优化是用户感知最明显的一块。后端再快,如果前端一堆请求排队,用户照样觉得卡。我这一轮优化有几个关键操作,可以说是立竿见影。
2.1 页面静态化缓存:最核心的一步
熟悉 WordPress 的朋友应该都知道,一个页面请求过来,服务器要执行 PHP 脚本,查数据库,拼 HTML,最后才返回给用户。这个过程每个请求都重复一遍,毫无必要。缓存插件的思路就是把这些动态生成的 HTML 存一份,下次请求直接返回静态文件就行,绕开 PHP 和 MySQL。
我用的是缓存插件来生成静态页面缓存。很多人喜欢用市面上流行的几个,比如 W3 Total Cache、WP Super Cache 或者 LiteSpeed Cache,看你的服务器环境。我用的是 Nginx,所以有个更轻量的方案:直接在 Nginx 层面做 fastcgi_cache。这样连 PHP 进程都不需要频繁启动了,静态文件由 Nginx 直接返回。
配置思路是这样的,先创建一个缓存路径:
mkdir -p /tmp/wp_cache chmod 777 /tmp/wp_cache然后在 Nginx 配置文件的 http 块里加上:
fastcgi_cache_path /tmp/wp_cache levels=1:2 keys_zone=wpcache:100m inactive=60m max_size=500m; fastcgi_cache_key "$scheme$request_method$host$request_uri";再到 server 块的 location ~ .php$ 里面,加上:
fastcgi_cache wpcache; fastcgi_cache_valid 200 301 302 60m; fastcgi_cache_use_stale error timeout updating invalid_header http_500 http_502 http_503; add_header X-WP-Cache $upstream_cache_status;这样配置好之后,重新加载 Nginx。然后怎么判断有没有生效呢?用浏览器开发者工具看响应头里面的X-WP-Cache,如果显示 HIT,说明命中了缓存;显示 MISS 的话就是没命中,需要检查一下路径权限和配置是否加载成功。
这个方案我用下来还是很稳的。唯一要注意的是,页面静态化之后,如果文章更新了,缓存不会立刻失效。解决方法是安装一个缓存清理插件,然后在主题的 functions.php 里挂一个 hook,更新文章的时候自动清理对应页面的缓存:
add_action('save_post', 'my_clear_wp_cache_on_save', 10, 1); function my_clear_wp_cache_on_save($post_id) { // 调用缓存插件清理缓存的接口 if (function_exists('wp_cache_clear')) { wp_cache_clear(); } }这里有个小技巧:可以注册一个 WP-CLI 命令去清理缓存,配合定时任务在后台跑。比每次手动清理方便多了。
2.2 图片和静态文件压缩
前端加载速度的大头,除了脚本,就是图片。我平时发文章会配一些截图和插图,之前都是直接上传原图,一张截图经常有 2 到 3 M,等于每次请求要下载好几 M 数据,服务器带宽不大,非常吃亏。
首先做的是暴力降体积:所有图片先本地处理一遍,宽度控制在 1200px 以内,用压缩工具把质量压到 72% 左右,基本上肉眼看起来没啥区别,体积能缩一半以上。这一步能确定基础体积不大。
然后利用 WordPress 自带的功能,上传新图片时,可以注册一个过滤器强制使用新的压缩逻辑。比如通过image_quality这个钩子把 JPEG 质量调低:
add_filter('jpeg_quality', 'my_jpeg_quality'); function my_jpeg_quality($quality) { return 72; }好处是以后上传图片不用手动处理了,所有 JPEG 图片自动用 72% 的质量保存,同时建议禁用原图的额外尺寸生成。WordPress 默认会生成一堆不同尺寸的缩略图,多占空间不说,很多时候还用不上。
然后是懒加载。现在最新的 WordPress 版本默认支持图片懒加载了,但如果你用的是老版本,或者主题自带懒加载,那就没必要额外装插件了。懒加载的原理是浏览器先不加载src属性的图片,等到用户滚动到图片位置才发请求,这样首屏加载的请求数会少很多。
我自己写了一段简单的小脚本放在主题底部,专门为老浏览器做一个 fallback,确保不会被不支持懒加载的特性拖累:
<script> var lazyLoad = function() { var imgs = document.querySelectorAll('img[data-lazy-src]'); for (var i = 0; i < imgs.length; i++) { if (imgs[i].getBoundingClientRect().top < window.innerHeight + 100) { imgs[i].src = imgs[i].getAttribute('data-lazy-src'); imgs[i].removeAttribute('data-lazy-src'); imgs[i].classList.add('loaded'); } } }; window.addEventListener('scroll', lazyLoad); window.addEventListener('resize', lazyLoad); window.addEventListener('load', lazyLoad); </script>实际使用下来,首屏图片请求数从 12 个降到 5 个左右,滚动到后面才逐步加载其他图片,体感明显轻快了不少。
另外,CSS 和 JS 的压缩合并。WordPress 加载资源时经常一加载就是一堆,每个都是单独请求。我的做法是:
- 尽量启用主题自带的资源优化,如果主题没有,就手动把关。
- 不轻易用通用的 CSS/JS 合并插件,因为合并操作有时候会破坏文件顺序,导致样式错乱。我见过不少站点为了合并资源,结果页面布局全崩了,与其冒险,不如在主题加载脚本时去掉一些不需要的文件。
在 functions.php 里可以这么做:
// 移除非必要的样式 add_action('wp_enqueue_scripts', 'my_dequeue_scripts', 100); function my_dequeue_scripts() { if (!is_page('contact')) { // 例如移除联系页面才需要的脚本 wp_dequeue_style('contact-style'); } }这个逻辑就是按页面类型精确控制资源加载,能用原生 API 就尽量不搞全局合并。稳定是第一位的。
2.3 本地字体与图标库优化
你可能没留意,很多主题会默认从第三方加载 Google 字体,每次请求都要跨域去拉资源,服务器在国内的话那速度简直惨不忍睹。这个一定要处理掉。
我建议的做法是:直接把字体文件下载到本地,写好 @font-face 规则。如果不想手动下载,也可以改一下主题源码,把字体加载的 URL 从 Google Fonts 的域名改成本站路径。甚至更极简的方案:直接用系统默认字体,省事省心。
我目前用的是本地托管字体方案,一个字体文件才几十 K 到一两百 K,可选路径很多,自己从免费字体库找几个合适的放上去就行:
@font-face { font-family: 'MyLocalFont'; src: url('../fonts/MyLocalFont.woff2') format('woff2'); font-weight: 400; font-display: swap; }图标库也是一样。图标如果是放在 JS 或者字体文件里,体积会有上百 K。我是尽量避免这种集成的图标库,能内联的 SVG 图标就直接内联,用起来也方便,不占额外请求。
3. 服务器与数据库层面的改造
前端优化做完,页面加载速度已经提升不少了,但仔细观察会发现,后台登录和文章编辑偶尔还是会卡一下。这就得往服务器和数据库层面深入。
3.1 PHP 配置调优
PHP 的配置直接影响 WordPress 的执行效率。先从最基本的php.ini说起,我调整的关键几个参数:
memory_limit = 256M max_execution_time = 300 post_max_size = 64M upload_max_filesize = 64M opcache.enable = 1 opcache.memory_consumption = 128 opcache.max_accelerated_files = 10000这里着重说下 OPcache。OPcache 可以把 PHP 编译后的字节码缓存到共享内存里,下次请求同一个文件时就不用重新解析了。这个对 WordPress 这种文件数量庞大的程序来说帮助非常巨大,强烈建议开启。
然后,我是在宝塔面板里面开启的 OPcache,也可以在命令行运行php -i | grep opcache查看当前的配置。实测开启之后,首页 TTFB 又降了 100ms 左右,后台响应也顺畅了很多。
不过要注意,OPcache 会缓存文件的旧版本,如果你改了 PHP 代码,经常出现改动不生效的情况。一般 OPcache 的 validate_timestamps 默认是检查文件时间戳的,改了文件会自动失效。但如果你为了性能把这个检查关掉了,那每次改完代码都要手动清一下缓存。
3.2 MySQL 查询和索引优化
WordPress 的数据库查询,很多时候是慢查询的重灾区。优化前可以开启 MySQL 的慢查询日志,看看哪些 SQL 语句执行时间特别长。用宝塔面板或者命令行都能查看到。
我先简单说下怎么看:
SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 2;然后等一段时间,再去看慢查询日志文件。通常你会发现,慢查询大多集中在wp_postmeta和wp_options表,原因就是这两个表的查询条件经常用不到索引。
对于使用自定义字段比较多的插件,查wp_postmeta的时候很慢。有的插件查询的时候直接用meta_key和meta_value做过滤,而默认索引并没有覆盖到meta_value这种大字段,查询自然慢。这种一般不建议直接加索引,如果插件确实需要频繁按 meta_value 查询,那么可以针对性优化。
常见的优化思路:做一次数据库的完整清理和重建表。比如把 wp_postmeta 表进行一次OPTIMIZE TABLE:
OPTIMIZE TABLE wp_postmeta;这个操作会把表重新整理一遍,空间回收,索引重组,确实能感受到一些细微变化。但要注意,表很大或者访问高峰期,这个操作有可能锁表,建议在低峰期做。
另外,合理使用 Memcached 或者 Redis 做对象缓存。WordPress 的很多数据查询是可以缓存的,比如菜单、小工具、站点设置。用 Redis 做对象缓存,可以让相同查询直接返回缓存结果,不用每次访问数据库。我后来配置好了 Redis 缓存之后,后台响应速度明显提升,数据库的压力也小了很多。
配置 Redis 对象缓存的思路,首先要安装 PHP 的 Redis 扩展,然后在wp-config.php中添加:
define('WP_REDIS_HOST', '127.0.0.1'); define('WP_REDIS_PORT', 6379); define('WP_REDIS_DATABASE', 0);最后再装一个 Redis 对象缓存插件来接通 WordPress 和 Redis。注意一点,Redis 的缓存过期策略要设置好,不然更新文章之后,老的缓存数据还会蹦出来。
3.3 主题文件结构瘦身
很多主题虽然看起来功能丰富,但代码冗余严重。我的做法是,不追求功能最全的主题,而是选一个比较干净的基础主题,然后通过子主题来加功能。这样做的好处是:升级父主题时不会丢失自定义修改,代码结构也清晰。
子主题的结构非常简单,标准化的配置:
- 子主题目录: 一个 style.css(写主题信息和引用父主题)
- 子主题 functions.php:加自己的功能代码
- 自定义模板文件
在 style.css 里,头部信息要写对:
/* Theme Name: My Sub Theme Template: twentytwentyfour */然后在 functions.php 里引入父主题的样式:
add_action('wp_enqueue_scripts', 'my_sub_enqueue_styles', 20); function my_sub_enqueue_styles() { wp_enqueue_style('parent-style', get_template_directory_uri() . '/style.css'); wp_enqueue_style('child-style', get_stylesheet_uri(), array('parent-style')); }用子主题的好处很实际,父主题升级的时候,不会把你的修改冲掉。之前直接用改主题文件的方式,升个级,自己改的代码全没了,特别头疼。
而且子主题的方式也方便排查问题。如果某个页面出现异常,先切回父主题测试,如果父主题正常,那就是子主题的代码有问题,缩小排查范围,不用整站抓瞎。
4. 安全加固与日常维护
WordPress 广受欢迎,也意味着它是被扫描攻击的重灾区。优化到后面,安全问题反而成了我最关注的一部分。你可能觉得,一个小站谁会来攻击?但现实是,大量的自动化扫描脚本全天候在扫你,它们的攻击方式是撒网式的,碰到防护薄弱的就尝试进。
4.1 隐藏版本号与文件权限收紧
第一步,把所有能暴露 WordPress 版本信息的地方隐藏起来。版本信息暴露了,攻击者就能针对性查找已知漏洞。
在 functions.php 中添加:
remove_action('wp_head', 'wp_generator'); add_filter('the_generator', '__return_empty_string');另外,后台登录页面是暴力破解的重要目标。我的做法是直接修改默认登录路径,避免使用标准的/wp-login.php。有很多插件能干这事,但如果你不想多装插件,也可以自己写 apply_filters 或者直接在服务器层面重写。
更关键的是文件权限设置。很多朋友把整个网站目录都设为 777 权限,这是非常危险的。我建议的安全权限参考:
| 路径/文件 | 建议权限 |
|---|---|
| 网站根目录 | 755 |
| wp-admin 目录 | 750 |
| wp-content 目录 | 750 |
| wp-config.php 文件 | 600 |
| uploads 目录 | 750 |
这个权限配置既能让服务器运行用户正常读写,又能防止其他人通过目录遍历获取文件内容。
4.2 拦截垃圾扫描与恶意请求
Nginx 层面可以做一定的访问控制,比如按 IP 段来限制后台访问。不过我的做法是用脚本定时分析访问日志,自动拉黑异常 IP。这个有点难度,但对于自动化攻击很有效。
简化的思路是这样的:用 crontab 定时跑一个脚本,统计五分钟内的日志访问频率,如果同一个 IP 请求了超过 120 次并且大部分是 404,就把它加入 iptables 封禁。
#!/bin/bash # 封禁逻辑简化版 LOG_FILE="/var/log/nginx/access.log" awk '{print $1}' $LOG_FILE | sort | uniq -c | sort -rn | head -n 20 > /tmp/temp_ips.txt while read count ip; do if [ $count -gt 120 ]; then iptables -A INPUT -s $ip -j DROP fi done < /tmp/temp_ips.txt这个脚本实际使用下来,能挡住很多无脑扫描流量。我印象很深,启用第一周就封了好几百个 IP,后台的无效访问日志立刻少了很多。不过要注意,一定要设置白名单,把自己的 IP 加进去,不然可能会把自己封掉。
4.3 备份策略的常态化
无论做了多少优化和加固,都不能保证万无一失,一旦出错,备份是最可靠的退路。
我的备份方式是多重组合:每天自动备份数据库,每周打包整个站点文件。数据库备份用 WP-CLI 的命令:
wp db export /backup/www_`date +%Y%m%d`.sql文件备份用 tar:
tar -czf /backup/wwwfiles_`date +%Y%m%d`.tar.gz /www/wwwroot/你的域名然后把备份文件同步到另一个存储空间,防止同一台服务器故障导致备份也丢了。
这里说下教训,我之前在一台机器上做过备份,结果那台机器硬盘坏道,备份全没了。后来改用异地备份,心里踏实多了。备份这个事儿,平时用不上,关键时候能救命,一点懒都不能偷。
5. 常见问题排查实录
优化过程中遇到的问题说实话不比解决的问题少。下面这几个是印象最深的,每一个都让人在深夜的屏幕前沉默了很久。记录下来算是给后来人排雷了。
5.1 缓存开启后页面错乱或样式丢失
这个情况很简单,启用页面缓存后第一次访问正常,第二次就开始样式丢失了。排查发现是缓存插件在压缩 CSS 文件时,把相对路径解析错了。浏览器加载的 CSS 路径变成了一些奇怪的地址。
解决办法:检查 CSS 文件是不是用了相对路径。如果是,把主题的 CSS 引用方式改一下,用绝对路径。或者更省事的方式:直接不用插件自带的 CSS 压缩功能,靠服务器层面 Gzip 来减少传输体积。
Gzip 是 Nginx 提供的压缩,它对 CSS 和 JS 的压缩率非常可观,并且不涉及路径解析的问题。限制一下:
gzip on; gzip_types text/plain application/javascript text/css application/json; gzip_min_length 1024;开启后,实测 CSS 和 JS 文件的传输体积能减少 70% 以上,并且没有路径错乱的问题,比插件压缩稳妥很多。
5.2 修改 DNS 切换后后台登录不了
我调整过几次域名解析,有时候发现切到新服务器之后,登录后台直接跳回登录页,登录成功后马上又回到登录界面,循环往复。
经典原因就是 WP 数据库里存的 siteurl 和 home 仍然是旧域名,WordPress 本来就有这个机制。解决办法有两个:
第一,用 WP-CLI 修改:
wp option update siteurl 'https://新域名.com' wp option update home 'https://新域名.com'第二,直接改数据库:
UPDATE wp_options SET option_value = 'https://新域名.com' WHERE option_name = 'siteurl'; UPDATE wp_options SET option_value = 'https://新域名.com' WHERE option_name = 'home';但要留意,如果站点有多个域名或者做了多语言处理,这个操作可能会影响其他逻辑。改动前先完整备份,改完清一下缓存。
5.3 后台部分页面 500 错误
这个很让人抓狂。优化某天,后台打开某些页面,提示 500 错误,刷新一下可能又好,过一会儿又出问题。
排查思路,先看 PHP 错误日志。日志文件一般在/var/log/php-fpm.log或者类似路径。如果日志里面有:
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted就是内存超限。可以临时把memory_limit调高,看看是否解决问题。如果调高就正常,说明某个插件确实很耗内存,那就要考虑是哪个插件的问题了。
还有一种情况是,这个错误跟 Redis 对象缓存的序列化有关。缓存的数据结构在某些取值情况下会被破坏,导致反序列化失败。这种情况清理一下 Redis 缓存,检查缓存键的命名冲突,一般能解决。
5.4 图片上传成功但访问 404
在做了大量前端优化后,我突然发现之前上传的一些图片访问不了,报 404。排查到最后,发现是 Nginx 的 rewrite 规则没有正确匹配到 uploads 下的文件路径。
因为 WordPress 的 URL 形态比较特殊,Nginx 配置需要正确处理文件存在性检查。比较稳妥的配置写法:
location / { try_files $uri $uri/ /index.php?$args; }加上这个之后,图片文件存在的就直接返回文件,不存在才走 WordPress 的规则,问题就消失了。这个细节看起来小,但一旦出问题是真让人头大。
6. 这轮优化的最终体会与下一步方向
这一轮优化做完,站点整体状态可以说脱胎换骨。首页从原来的 3 秒多降到 800ms 左右,后台操作明显跟手了,扫描攻击日志也少了很多。要说最大的感受就是:优化这件事不能盲目跟风,得搞清楚自己的瓶颈在哪里。有些站点缺的是缓存,有些是数据库太烂,还有些是主题本身太笨重,照搬别人的方案不一定合适。
我个人在实际操作中的体会是,WordPress 优化的核心思路可以归结为一句话:减少不必要的工作。能走静态不走动态,能走内存不走数据库,能本地加载不去远程拉取。这句话听起来简单,但每一步执行起来都需要你对自己的站点足够了解。尤其是插件和主题的取舍,稍微有点舍不得,结果可能就是拖慢整站速度。
下一轮优化我计划做几个方向:一是把部分接口改成动静分离,结合当前服务器环境做更深度的配置;二是建立一个系统化监控提醒,争取在出现性能波动时第一时间发现自己变了什么,而不是等到用户来问“你站是不是崩了”才察觉。这条路还得慢慢走,先把这一轮的经验沉淀下来,也算给系列开个篇。
最后分享一个小技巧:有条件的话,尽量多接触不同环境下的 WordPress 站点,各种奇怪的问题见得多了,回头处理自己的站时,很多看起来无解的毛病,其实心里早就有数了。