本文约 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,说明整条链路通了。
常见踩坑
-
直接
chmod -R 777:能解决问题但引入了严重的安全隐患,同主机其他站点可直接写入你的 PHP 文件。 -
案例: 某站迁移时用 root 账号
tar解包,属主全是root:root,PHP-FPM 用户是www-data。站点能正常访问(读权限没问题),但任何更新都弹 FTP。运维以为是「FTP 配置问题」,折腾两天后才发现ls -l一眼能看出来的属主问题。教训:迁移后第一件事就是chown -R www-data:www-data。 -
用
wp命令改文件后属主变了:命令行以deploy用户跑wp plugin install,新建的文件属主是deploy,Web 用户反而写不了。要么统一用 Web 用户跑 wp(sudo -u www-data wp ...),要么把运维账号加进 Web 组并给组写权限。 -
SELinux 下
chcon治标不治本:restorecon或系统重启后上下文恢复,必须用semanage fcontext写进策略。 -
open_basedir限制:PHP 配置里限制了可访问目录,wp-content/upgrade在允许范围外,表现和权限问题一模一样。检查php.ini的open_basedir。 -
磁盘满了:
df -h一看100%,报错同样是「无法创建目录」。 -
wp-content/upgrade是自动更新残留:里面堆着历次更新的临时目录,占空间且偶尔引起权限混乱,可以安全清空(rm -rf wp-content/upgrade/*)。 -
容器环境(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 文件权限安全设置
