WordPress 安全插件装了一大堆,Wordfence 拖慢后台,Limit Login Attempts 和别的插件冲突,WP Security 界面太复杂。其实很多安全功能用 WordPress mu-plugin 安全 方案就能实现,零额外 CPU 开销,不引入新界面。
这篇文章给你三个可以直接复制粘贴的 mu-plugin 代码,覆盖登录爆破、XML-RPC、安全头三个最需要的场景。所有方案都在 sodebug.com 的生产环境跑了一年多,不装 Wordfence 也能睡得着。
什么是 mu-plugin
mu-plugin 全称 Must-Use Plugin,放在 /wp-content/mu-plugins/ 目录下。和普通插件的区别:
- 自动启用,文件放进去就生效,不需要去后台点“启用”
- 无法在后台禁用,只有删文件才能关,安全性更高
- 加载优先级最高,在所有普通插件之前执行
- 不写数据库,不会在 wp_options 里塞十几行配置
- 不受插件更新影响,你自己的代码,更新 WordPress 不会覆盖
为什么不用普通插件做安全
安全插件有三个通病。第一,每个插件都有自己的后台界面、自己的数据库表、自己的更新周期,Wordfence 装了 20+ 张表。第二,安全插件之间互相打架很常见:两个登录保护插件同时 hook authenticate 过滤器,结果是两个都失效。第三,插件作者可能在某个版本把免费功能砍成付费,你的安全策略不应该依赖他人的商业决策。
WordPress mu-plugin 安全 方案的逻辑正好反过来:安全功能写成代码文件放在 mu-plugins 目录里,不依赖后台界面、不写数据库、不自动更新。你完全掌控代码,且 GitHub 可以直接追踪变更。参考 WordPress 官方文档 了解更多细节。
实战一:登录暴力破解限流
这是最需要的安全功能。原理很简单:同一个 IP 5 分钟内失败 5 次,封 15 分钟。不需要数据库,用 WordPress 内置的 Transients API,如果装了 Redis Object Cache,计数直接存在 Redis 里,比写数据库快得多。
<?php
// /wp-content/mu-plugins/login-limit.php
// 记录失败尝试
add_action('wp_login_failed', function($username) {
$ip = $_SERVER['REMOTE_ADDR'];
$key = 'login_failed_' . md5($ip);
$attempts = get_transient($key) ?: 0;
set_transient($key, $attempts + 1, HOUR_IN_SECONDS);
if ($attempts >= 4) {
set_transient('login_banned_' . md5($ip), 1, 15 * MINUTE_IN_SECONDS);
}
});
// 在 WordPress 验证密码之前拦截
add_filter('authenticate', function($user) {
$ip = $_SERVER['REMOTE_ADDR'];
if (get_transient('login_banned_' . md5($ip))) {
wp_die('登录尝试次数过多,请 15 分钟后再试。', 'Rate Limited', ['response' => 429]);
}
return $user;
}, 1);
// 成功登录后清除计数
add_action('wp_login', function() {
$ip = $_SERVER['REMOTE_ADDR'];
delete_transient('login_failed_' . md5($ip));
delete_transient('login_banned_' . md5($ip));
});
工作原理:三个钩子的链路
三个钩子搭成一条链路:wp_login_failed 记录失败次数 → authenticate 在验证密码之前查黑名单 → wp_login 成功后清空计数。用 get_transient / set_transient 而不是写 wp_options,因为 Transients 天然支持过期时间。如果装了 Redis Object Cache,这些数据存在 Redis 里,断电不丢,且查询延迟远低于数据库。
Cloudflare 代理后的真实 IP 处理
如果你用了 Cloudflare 代理,$_SERVER['REMOTE_ADDR'] 拿到的会是 Cloudflare 的 IP(全是同一个地址),所有用户共享一个计数器,一个用户被锁,全部用户跟着陪葬。需要在 Nginx 层加 set_real_ip_from 规则恢复真实访问者 IP,参考 Nginx FastCGI Cache 完整配置指南 中的配置方法。
实战二:XML-RPC 彻底禁用
XML-RPC 是 WordPress 密码爆破的主要入口,攻击者一次请求可以试几百个密码。Nginx 层封了 403 还不够,PHP 层也要封:
<?php
// /wp-content/mu-plugins/disable-xmlrpc.php
add_filter('xmlrpc_enabled', '__return_false');
if (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) {
status_header(403);
exit('XML-RPC is disabled.');
}
为什么单独 Nginx 封 XML-RPC 不够
Nginx 规则可能被意外覆盖,比如宝塔面板更新 Nginx 配置、新增站点时把自定义规则冲掉。如果有一天 Nginx 层封禁规则不小心被删了,PHP 层的 mu-plugin 是最后一道防线。而且 mu-plugin 里封 XML-RPC 只需 6 行代码,零维护成本。这个 WordPress mu-plugin 安全 设置推荐所有站点都启用。
实战三:安全头(CSP / Referrer-Policy / Permissions-Policy)
这可能是最被低估的安全功能,用几十行 PHP 加三个 HTTP 安全头,不装任何插件:
<?php
// /wp-content/mu-plugins/security-headers.php
add_action('send_headers', function() {
if (!is_admin()) {
header("Content-Security-Policy: default-src 'self'; " .
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " .
"style-src 'self' 'unsafe-inline'; " .
"img-src 'self' data: https:; " .
"font-src 'self'; " .
"frame-ancestors 'self';");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Permissions-Policy: camera=(), microphone=(), geolocation=()");
}
});
// 隐藏 WordPress 版本号
remove_action('wp_head', 'wp_generator');
// 阻止用户枚举(?author=1)
add_action('template_redirect', function() {
if (is_author() && !is_user_logged_in()) {
wp_redirect(home_url(), 301);
exit;
}
});
CSP 头做了什么
Content-Security-Policy 告诉浏览器:只加载我自己域名('self')的资源。如果有人通过评论或表单注入了 <script src="evil.com/hack.js">,浏览器直接拒绝执行,CSP 不让从 evil.com 加载任何脚本。这是防御 XSS 攻击的最后一道防线。
Referrer-Policy 和 Permissions-Policy
Referrer-Policy: strict-origin-when-cross-origin 控制用户从你的站跳转到外链时,referrer 头只暴露域名(sodebug.com)而不暴露完整路径(sodebug.com/wp-admin/...)。这防止了敏感路径信息通过 referrer 泄露给第三方站。
Permissions-Policy 禁用浏览器 API:摄像头(camera=())、麦克风(microphone=())、定位(geolocation=())。这些 API 在你的博客上没有任何合理用途,关掉可以防止恶意 JS 偷偷调用它们。
三个功能合并成一个文件
你不需要三个文件:把登录限流、XML-RPC 禁用、安全头合并到一个 wordpress-security.php 里更干净。部署只需要 4 条命令:
# 创建 mu-plugins 目录(如果不存在)
mkdir -p /www/wwwroot/你的域名.com/wp-content/mu-plugins
# 上传合并后的安全文件
cp wordpress-security.php /www/wwwroot/你的域名.com/wp-content/mu-plugins/
# 设置权限
chown www:www /www/wwwroot/你的域名.com/wp-content/mu-plugins/wordpress-security.php
chmod 644 /www/wwwroot/你的域名.com/wp-content/mu-plugins/wordpress-security.php
不需要点任何按钮,刷新网站,三个安全功能立刻生效。如果后续需要加新的安全规则,直接编辑这个文件,git commit && git push 即可,版本控制和部署都比插件干净得多。
mu-plugin vs Wordfence:什么时候用哪个
| 功能 | Wordfence | mu-plugin |
|---|---|---|
| 登录爆破防护 | ✅ | ✅(Transients API,走 Redis) |
| XML-RPC 禁用 | ✅ | ✅(PHP 层 + Nginx 双重) |
| 安全头(CSP 等) | ✅ | ✅ |
| 用户枚举防护 | ✅ | ✅ |
| 文件变更检测 | ✅ | ❌(mu-plugin 不需要,文件在 mu-plugins 目录里没人能动) |
| CPU 开销 | 高 | 几乎为零 |
| 数据库表 | 20+ 张 | 0 |
| 写日志量 | 海量 | 0 |
对于大多数个人博客和技术站点,Nginx 7 条安全规则 + mu-plugin 三个功能已经完全够用了。Wordfence 更适合需要文件完整性监控和实时威胁情报的大型商业网站,但它的代价是 20+ 张数据库表和持续的后台资源消耗。
结语
三个 WordPress mu-plugin 安全 方案覆盖了 90% 的个人博客安全需求:登录爆破限流、XML-RPC 完全禁用、CSP 安全头。加在一起不到 100 行 PHP,不写数据库、不吃 CPU、不引入新后台界面。
部署之后可以安心了,如果还想加固 Nginx 层,推荐看这两篇:宝塔 Nginx 限流配置速查 讲的是用 limit_req_zone 在 Nginx 层做速率限制,Nginx FastCGI Cache 完整配置指南 涵盖了 set_real_ip_from 和缓存安全。三层防护(Nginx + mu-plugin + CSP),不装 Wordfence 也睡得着。
© 2026 本文由 Charlie 原创撰写,发布于 sodebug.com。未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。