本文约 5,637 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。
引言:全开和全关,都是错的
WordPress 的自动更新有三种态度:全开、全关、选择性更新。大多数站长在前两种之间摇摆——一开始全开,某次自动更新后站点崩了,于是全关;过段时间发现某个插件的严重漏洞没打补丁,又想全开。
这两种做法各自有明确的代价。全开的风险是:插件作者发布了一个有 bug 的版本,你的站点在半夜自动装上,第二天早上才发现前台异常。这类事故在 WordPress 生态里并不罕见,尤其是那些版本发布前没有充分测试的中小插件。全关的风险则更隐蔽也更严重:你失去了安全补丁的自动通道。前面 插件漏洞查询 里说过,高危漏洞从披露到被大规模自动化利用往往只有几天窗口,靠人工每周登录后台点更新,很可能错过。
正确的做法是第三种:建立白名单,只让经过评估的插件自动更新。WordPress 从 5.8 起在后台插件列表页提供了每个插件单独的「启用自动更新」开关,但那是在界面上逐个点。如果要在代码层统一控制、并且纳入版本管理,就应该用钩子来实现。
原理分析:自动更新的判定链路
WordPress 的自动更新由 WP_Automatic_Updater 类驱动,它在 wp_maybe_auto_update()(挂在 admin_init 的定时任务上)里执行。判定某个插件是否该自动更新时,会经过三层过滤:
第一层:全局开关。 automatic_updater_disabled 过滤器返回 true 则完全关闭;常量 AUTOMATIC_UPDATER_DISABLED 定义为 true 同理。另外文件系统的可写性、是否处于版本控制目录下(有 .git 的目录会被跳过)也会影响。
第二层:类型开关。 auto_update_plugin、auto_update_theme、auto_update_translation、auto_update_core 四个过滤器分别控制四类更新。其中 auto_update_plugin 的签名是:
apply_filters( 'auto_update_plugin', $update, $item )
$update 是布尔值或 null(null 表示「未决定,交给后台设置」),$item 是本次待更新项的对象,包含 slug、plugin、new_version 等属性。
第三层:后台的逐个开关。 WordPress 5.8 之后,插件列表页可以为单个插件开启自动更新,这个设置存在 wp_options 的 auto_update_plugins 数组里,并可通过 plugins_auto_update_enabled 过滤器整体控制该功能是否启用。
关键点在于过滤器的返回值语义:
| 返回值 | 含义 |
|---|---|
true |
强制自动更新 |
false |
强制不自动更新 |
null |
不做决定,沿用后台逐个开关与默认策略 |
因为 false 是「强制」,所以白名单写法的正确姿势是:命中白名单返回 true,命中黑名单返回 false,其余返回 null。如果你对不在名单里的插件返回了 false,就相当于把后台的逐个开关给废掉了。
保姆级实操
第一步:建立插件分级清单
先把站点上的插件分成三级,写在代码里而不是脑子里:
- A 级(可信、自动更新):官方目录、作者活跃、你自己读过更新日志的插件。比如安全类和缓存类。
- B 级(观察、手动更新):业务核心插件、做过二次开发的插件、版本更新可能改变输出结构的多语言与商城插件。
- C 级(永不自动更新):商业插件需要授权密钥的(自动更新可能失败)、已停止维护的老插件、你自己改过源码的插件。
第二步:写白名单代码
放到 mu-plugins 里最合适,因为它是站点基础设施配置,不该被后台停用:
<?php
/**
* Plugin Name: 自动更新策略
* Description: 插件自动更新白名单 / 黑名单。
*/
defined( 'ABSPATH' ) || exit;
add_filter( 'auto_update_plugin', 'mmf_auto_update_policy', 10, 2 );
function mmf_auto_update_policy( $update, $item ) {
// 白名单:只对这些插件开启自动更新(用插件的目录/主文件路径)
$auto = array(
'query-monitor/query-monitor.php',
'health-check/health-check.php',
'wordpress-seo/wp-seo.php',
);
// 黑名单:无论如何都不自动更新
$never = array(
'my-custom-shop/my-custom-shop.php', // 改过源码
'legacy-slider/legacy-slider.php', // 已停止维护
);
$plugin = isset( $item->plugin ) ? $item->plugin : '';
if ( in_array( $plugin, $never, true ) ) {
return false;
}
if ( in_array( $plugin, $auto, true ) ) {
return true;
}
// 其余插件交还给后台的逐个开关决定
return $update;
}
第三步:按「更新类型」做更细的策略
可以只看安全更新、跳过功能更新。WordPress 的更新包里会带一个标记,但不总是可靠;实践中更稳的做法是结合版本号规则——补丁版本(第三位变化)自动更新,次版本(第二位变化)不自动:
add_filter( 'auto_update_plugin', 'mmf_only_patch_updates', 20, 2 );
function mmf_only_patch_updates( $update, $item ) {
if ( true !== $update ) {
return $update; // 只收窄白名单插件,不影响其他决定
}
$current = mmf_get_plugin_version( $item->plugin );
$new = isset( $item->new_version ) ? $item->new_version : '';
if ( ! $current || ! $new ) {
return $update;
}
// 只在同一主版本内的补丁更新才自动装
$c = explode( '.', $current );
$n = explode( '.', $new );
if ( isset( $c[0], $n[0] ) && $c[0] !== $n[0] ) {
return false;
}
if ( isset( $c[1], $n[1] ) && $c[1] !== $n[1] ) {
return false;
}
return true;
}
function mmf_get_plugin_version( $plugin_file ) {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$data = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin_file, false, false );
return isset( $data['Version'] ) ? $data['Version'] : '';
}
第四步:核心与主题的独立策略
// 核心:只自动装小版本(安全补丁),主版本手动
define( 'WP_AUTO_UPDATE_CORE', 'minor' );
// 主题一律不自动更新(主题更新更容易改结构)
add_filter( 'auto_update_theme', '__return_false' );
// 翻译包自动更新,风险极低
add_filter( 'auto_update_translation', '__return_true' );
WP_AUTO_UPDATE_CORE 的取值是 true(全部)、false(关闭)、'minor'(仅小版本),这是核心常量,与过滤器是两套并行机制。
第五步:用 WP-CLI 验证策略是否生效
# 查看哪些插件有待更新
wp plugin list --update=available --fields=name,version,update_version
# 触发一次自动更新检查(不实际安装)
wp core check-update
wp plugin update --all --dry-run
# 实际执行一次更新,观察是否被策略拦截
wp plugin update query-monitor
注意 WP-CLI 的 wp plugin update 是手动命令,不走 auto_update_plugin 过滤器。要验证过滤器,需要在后台触发自动更新任务,或者用 wp eval 直接跑一次判定:
wp eval '
$item = (object) array( "plugin" => "legacy-slider/legacy-slider.php", "new_version" => "9.9.9" );
var_dump( apply_filters( "auto_update_plugin", null, $item ) );
'
返回 false 说明黑名单生效。
第六步:配置更新结果通知
add_filter( 'auto_plugin_theme_update_email', 'mmf_update_email', 10, 4 );
function mmf_update_email( $email, $type, $successful_updates, $failed_updates ) {
if ( 'plugin' !== $type ) {
return $email;
}
$email['to'] = 'ops@example.com';
$email['subject'] = '[站点更新] ' . count( $successful_updates ) . ' 个插件已更新';
return $email;
}
常见踩坑
-
在过滤器里对未命中白名单的插件
return false:这会废掉后台的逐个开关,等于变相全关。正确做法是返回传入的$update。 -
用插件名而不是插件路径匹配:
$item->plugin的值是目录/主文件.php这种格式,直接比对插件 slug 永远匹配不上。 -
把改过源码的插件放进白名单:下次自动更新会把你的改动覆盖掉,且不提示。
-
忽略文件权限:自动更新需要 WordPress 进程对
wp-content/plugins有写权限;权限不足时更新会静默失败。 -
站点在 Git 管理下:核心检测到
.git或.svn目录会跳过自动更新,这是有意为之的保护。 -
案例: 某站点把所有插件都设成自动更新,某天一个表单插件发布了大版本,改动了数据存储结构,凌晨自动装上后老表单提交全部失败,第二天上午才发现,损失了三天的询盘数据。复盘后改为白名单策略:业务插件一律手动更新且更新前先在预发布站验证。教训是——「自动更新」这个动作本身没错,错的是对业务核心插件也自动。
-
不等预发布验证就上生产:哪怕是白名单插件,重大版本也应该先在预发布站跑一遍。
总结
策略可以固化为一句话:安全类、工具类插件走自动更新白名单;业务核心、二次开发、停止维护的插件进黑名单;其余交给后台开关并人工周检。
代码上三个要点:白名单用 true、黑名单用 false、未命中返回原值;匹配键用 目录/主文件.php;把策略写在 mu-plugins 里纳入版本管理,避免后台被误改。
另外别忘了配套三件事:开启 auto_plugin_theme_update_email 通知、每周用 wp plugin list --update=available 人工复查一次黑名单插件、以及把漏洞查询纳入月度流程(方法见 查询插件已知漏洞)。如果某个插件你既不敢自动更新又长期不手动更新,那它很可能已经落入 识别已弃用插件 描述的状态,该考虑替换了。
