搜索全站
插件 15 分钟阅读

WordPress 插件自动更新白名单:只更新可信插件

自动更新全开怕崩站,全关怕漏补丁。用 auto_update_plugin 钩子做插件白名单,只让可信插件自动更新。本文给出完整代码、WP-CLI 验证方法与分级策略。

U
UFO_PF 2026-07-30 · 阅读 15 分钟
Wo 插件
阅读提示
本文约 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;
}

常见踩坑

  1. 在过滤器里对未命中白名单的插件 return false:这会废掉后台的逐个开关,等于变相全关。正确做法是返回传入的 $update。

  2. 用插件名而不是插件路径匹配:$item->plugin 的值是 目录/主文件.php 这种格式,直接比对插件 slug 永远匹配不上。

  3. 把改过源码的插件放进白名单:下次自动更新会把你的改动覆盖掉,且不提示。

  4. 忽略文件权限:自动更新需要 WordPress 进程对 wp-content/plugins 有写权限;权限不足时更新会静默失败。

  5. 站点在 Git 管理下:核心检测到 .git 或 .svn 目录会跳过自动更新,这是有意为之的保护。

  6. 案例: 某站点把所有插件都设成自动更新,某天一个表单插件发布了大版本,改动了数据存储结构,凌晨自动装上后老表单提交全部失败,第二天上午才发现,损失了三天的询盘数据。复盘后改为白名单策略:业务插件一律手动更新且更新前先在预发布站验证。教训是——「自动更新」这个动作本身没错,错的是对业务核心插件也自动。

  7. 不等预发布验证就上生产:哪怕是白名单插件,重大版本也应该先在预发布站跑一遍。

总结

策略可以固化为一句话:安全类、工具类插件走自动更新白名单;业务核心、二次开发、停止维护的插件进黑名单;其余交给后台开关并人工周检。

代码上三个要点:白名单用 true、黑名单用 false、未命中返回原值;匹配键用 目录/主文件.php;把策略写在 mu-plugins 里纳入版本管理,避免后台被误改。

另外别忘了配套三件事:开启 auto_plugin_theme_update_email 通知、每周用 wp plugin list --update=available 人工复查一次黑名单插件、以及把漏洞查询纳入月度流程(方法见 查询插件已知漏洞)。如果某个插件你既不敢自动更新又长期不手动更新,那它很可能已经落入 识别已弃用插件 描述的状态,该考虑替换了。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。