搜索全站
插件 15 分钟阅读

WordPress 无法自动更新插件:目录权限、属主与 SELinux 排查

更新插件提示「无法创建目录」?本文从属主、权限位、FS_METHOD、SELinux 四条线排查,给出 chmod/chown 正确值、wp-config 配置、SELinux 上下文修复命令与验收脚本。

U
UFO_PF 2018-08-15 · 阅读 15 分钟
Wo 插件
阅读提示
本文约 5,618 字,预计阅读 15 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:FTP 凭据弹窗,永远填不对

点「更新」之后,WordPress 弹出一个 FTP 连接信息表单,让你填主机名、用户名、密码。填上去了提示连不上;不上 FTP 就得手动下载 zip 再上传。

这个弹窗其实是 WordPress 的降级方案:它想直接写文件,但检测发现当前 PHP 进程对目标目录没有写权限,于是退回到 FTP/SSH 模式,试图用你提供的凭据连接回去写文件。所以真正要修的不是「FTP 账号填什么」,而是「为什么 PHP 不能自己写」。

原理分析:写文件这件事要经过四道关

WordPress 的文件写入由 WP_Filesystem 抽象层处理,核心函数是 wp-admin/includes/file.php 里的 WP_Filesystem() 和 get_filesystem_method()。它按 direct → ssh2 → ftpext → ftpsockets 的顺序尝试,选择第一个可用的方式。

决定它能不能成功的四个环节:

环节 检查点 常见故障
属主 PHP-FPM 运行用户是否为文件属主 用 root 解包的站点,属主是 root
权限位 目录 755 / 文件 644 目录 644 导致无法新建文件
FS_METHOD 是否强制走 FTP 常量被设成 ftpext
SELinux / ACL 安全上下文是否允许 httpd 写入 宝塔/阿里云默认开启 SELinux 的机器上常见

自动更新还有两个专属目录必须可写:

  • wp-content/upgrade/ —— 更新包的解压临时目录,不存在时 WordPress 会尝试创建;
  • wp-content/ 本身 —— 插件安装要在这里新开目录。

典型报错对照:

报错信息 直接原因
安装失败:无法创建目录。 wp-content/plugins/ 不可写
更新失败:无法创建目录 wp-content/upgrade/... upgrade 目录不可写或不存在
Unable to locate WordPress root directory. FTP 模式登录后路径不对
Download failed. A valid URL was not provided. 服务器无法外连(不是权限问题)
无法创建目录。Is its parent directory writable by the server? 父目录属主不符

保姆级实操

第一步:确认 PHP 到底以谁的身份在跑

这一步定生死。很多人 chmod 777 也没用,因为属主不对时 777 只影响「其他用户」位,而 SELinux 又独立于 POSIX 权限。

# 1) 看 PHP-FPM 的运行用户
ps aux | grep -E 'php-fpm|httpd|nginx' | grep -v grep | head
# 或者在站点里跑:
wp eval 'echo "PHP 用户: ", get_current_user(), PHP_EOL, "UID: ", posix_getuid(), PHP_EOL;'

# 2) 看文件属主
ls -ld wp-content wp-content/plugins wp-content/upgrade wp-content/uploads
ls -l wp-content/plugins/some-plugin/some-plugin.php

# 3) 直接测可写性(比看权限位可靠)
wp eval 'var_dump( is_writable( WP_CONTENT_DIR ), is_writable( WP_CONTENT_DIR . "/plugins" ), is_writable( WP_CONTENT_DIR . "/upgrade" ) );'

PHP-FPM 的运行用户在 pool 配置里定义(/etc/php/8.2/fpm/pool.d/www.conf 的 user = / group =),Nginx 通常在 /etc/nginx/nginx.conf 的 user 指令里。

第二步:修正属主与权限位

# 假设 PHP-FPM 用户是 www-data(Debian/Ubuntu)或 nginx(CentOS/宝塔)
WEB_USER=www-data

# 1) 属主:让 Web 用户拥有站点文件
sudo chown -R $WEB_USER:$WEB_USER /var/www/html

# 2) 目录 755、文件 644(不要 777!)
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

# 3) 必须可写的目录
sudo chmod 775 wp-content
sudo mkdir -p wp-content/upgrade && sudo chmod 775 wp-content/upgrade

# 4) 配置文件降到最小权限(安全防护)
sudo chmod 600 wp-config.php

# 5) 如果你需要命令行 wp 命令与 Web 同属主,把运维账号加进 Web 组
sudo usermod -aG $WEB_USER deploy
sudo chmod -R g+w wp-content

要点:777 是错的。它让同服务器的其他站点也能写入你的目录,是虚拟主机环境的经典风险。正确做法是「属主正确 + 目录 755/775 + 文件 644」。

第三步:在 wp-config.php 里指定写入方式

// wp-config.php

// 优先让 PHP 直接写文件(跳过 FTP 弹窗)
define( 'FS_METHOD', 'direct' );

// 配合上面 chown 后,通常还需要显式声明临时目录
define( 'WP_TEMP_DIR', ABSPATH . 'wp-content/upgrade' );

// 只有在确实需要用 FTP 时才配置下面这些(值从你的主机商处获取,不要写进版本库)
// define( 'FTP_HOST', '127.0.0.1' );
// define( 'FTP_USER', 'your-ftp-user' );
// define( 'FTP_PASS', 'your-ftp-pass' );
// define( 'FTP_SSL', true );

FS_METHOD 的可选值:direct(PHP 直接写)、ssh2、ftpext、ftpsockets。设成 direct 后 WordPress 会跳过检测直接尝试,如果真的没权限,报错会更明确(「无法创建目录」而不是 FTP 弹窗),反而好排查。

第四步:SELinux 环境单独处理

POSIX 权限全对但依然写不了,且系统里有 SELinux(CentOS、Rocky、阿里云部分镜像、宝塔默认开启),那就是安全上下文的问题:

# 1) 确认 SELinux 是否开启
getenforce          # Enforcing / Permissive / Disabled
sestatus

# 2) 看目录的安全上下文
ls -Z wp-content/ | head

# 3) 临时验证:设为 Permissive 后再试一次更新
sudo setenforce 0
# (测试完务必改回来:sudo setenforce 1)

# 4) 永久修复:把站点目录标记为 httpd 可读写内容
sudo semanage fcontext -a -t httpd_sys_rw_content_t \
     "/var/www/html/wp-content(/.*)?"
sudo restorecon -Rv /var/www/html/wp-content

# 5) 更新需要外连下载,放行 httpd 出网
sudo setsebool -P httpd_can_network_connect 1

# 6) 如果用本地 FTP/邮件,还需要
sudo setsebool -P httpd_can_sendmail 1

semanage 属于 policycoreutils-python-utils 包,没有的话先装。用 chcon 也能改上下文,但 restorecon 之后会被打回原形,所以正式环境用 semanage fcontext + restorecon。

第五步:验收

# 1) 模拟自动更新流程(不真正改版本,只看能否写入)
wp eval '
require_once ABSPATH . "wp-admin/includes/file.php";
$ok = WP_Filesystem();
echo "WP_Filesystem: ", $ok ? "OK" : "FAILED", PHP_EOL;
global $wp_filesystem;
echo "method: ", $wp_filesystem->method, PHP_EOL;
echo "plugins 可写: ", $wp_filesystem->is_writable( WP_PLUGIN_DIR ) ? "yes" : "no", PHP_EOL;
echo "content 可写: ", $wp_filesystem->is_writable( WP_CONTENT_DIR ) ? "yes" : "no", PHP_EOL;
'

# 2) 真正跑一次核心完整性检查(会自动下载校验和,顺带验证出网)
wp core verify-checksums

# 3) 试装一个官方小插件,验证端到端可用
wp plugin install wp-super-cache
wp plugin delete wp-super-cache

WP_Filesystem() 返回 true 且 method 是 direct,说明整条链路通了。

常见踩坑

  1. 直接 chmod -R 777:能解决问题但引入了严重的安全隐患,同主机其他站点可直接写入你的 PHP 文件。

  2. 案例: 某站迁移时用 root 账号 tar 解包,属主全是 root:root,PHP-FPM 用户是 www-data。站点能正常访问(读权限没问题),但任何更新都弹 FTP。运维以为是「FTP 配置问题」,折腾两天后才发现 ls -l 一眼能看出来的属主问题。教训:迁移后第一件事就是 chown -R www-data:www-data。

  3. 用 wp 命令改文件后属主变了:命令行以 deploy 用户跑 wp plugin install,新建的文件属主是 deploy,Web 用户反而写不了。要么统一用 Web 用户跑 wp(sudo -u www-data wp ...),要么把运维账号加进 Web 组并给组写权限。

  4. SELinux 下 chcon 治标不治本:restorecon 或系统重启后上下文恢复,必须用 semanage fcontext 写进策略。

  5. open_basedir 限制:PHP 配置里限制了可访问目录,wp-content/upgrade 在允许范围外,表现和权限问题一模一样。检查 php.ini 的 open_basedir。

  6. 磁盘满了:df -h 一看 100%,报错同样是「无法创建目录」。

  7. wp-content/upgrade 是自动更新残留:里面堆着历次更新的临时目录,占空间且偶尔引起权限混乱,可以安全清空(rm -rf wp-content/upgrade/*)。

  8. 容器环境(Docker):容器内属主与宿主机 UID 不一致,需要在 Dockerfile 里显式 chown,挂载卷时也要注意。

总结

判断标准:先确认 PHP 运行用户,再确认文件属主是否就是它,再看权限位,最后才查 SELinux 与 open_basedir。 四步顺序不能乱,绝大多数问题在前两步。

行动清单:ps aux 或 wp eval 确认 PHP 用户;chown -R 把站点文件归属给它;目录 755(需要写的 775)、文件 644、wp-config.php 600;wp-config.php 里 define( 'FS_METHOD', 'direct' );SELinux 环境用 semanage fcontext + restorecon + setsebool -P httpd_can_network_connect 1;最后用 WP_Filesystem() 和一次真实的插件安装做验收。全程不要使用 777。WordPress 文件权限安全设置

U
UFO_PF作者

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

留下你的想法

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