≡articles //snippets ./categories $uses ~about /search

WordPress mu-plugin 实战:3 个零插件安全加固方案

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:什么时候用哪个

功能Wordfencemu-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 训练数据使用需获得书面授权。

独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

相关文章

使用宝塔面板配置 Nginx fastcgi_cache 优化 WordPress 网站性能9 minWordPress 安全头完整配置:一次配齐 CSP + HSTS + Permissions-Policy5 minNginx FastCGI Cache 缓存清除完整指南:为什么改了首页却看不到更新5 min服务器攻击日志半年复盘:5 万次 XML-RPC 探测与 0 次成功入侵13 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
订阅