文章目录
结论先行:WordPress 目录权限设置默认按目录 755、文件 644 执行,wp-config.php 可收紧到 600 或 640;属主给部署用户,Web 用户只保留读取与必要的写入目录权限。判断做对的标准是:前台、后台、上传、更新、计划任务均正常,且用 Web 用户身份无法改写主题、插件和核心 PHP 文件。
故障现象与快速定位命令
这类问题通常不是「站点全挂」,而是局部功能异常:后台媒体上传失败并提示无法创建目录;安装插件时要求 FTP 凭据;自动更新或语言包更新卡在「正在解压缩」;访问 /wp-content/uploads/2026/01/ 里的图片返回 403;Nginx 或 Apache 日志出现 Permission denied、open() failed (13: Permission denied);安全扫描发现 wp-content 下出现陌生 PHP 文件。另一类相反现象是:为了「先让它能跑」把目录改成 777,上传恢复了,但几天后被植入 WebShell,或者同服务器另一个站点被横向写入。
先不要改权限,先采集事实。以下路径以常见 Linux 部署为例:站点根目录 /var/www/example.com/public,PHP-FPM 用户 www-data(Debian/Ubuntu)或 apache(RHEL 系),部署用户 deploy。2026 年 WordPress 主线仍沿用「核心文件只读、wp-content/uploads 可写」的模型;WordPress 6.x 的 WP_Filesystem、get_filesystem_method()、wp_is_file_mod_allowed() 逻辑没有把 777 变成推荐值,官方 文件权限文档也仍以 755/644 为基线。
# 1) 看现象对应的日志,别猜
sudo tail -n 200 /var/log/nginx/error.log
sudo tail -n 200 /var/log/php8.3-fpm.log
# 2) 看站点根目录、属主、权限、ACL
pwd
stat -c '%U:%G %a %n' /var/www/example.com/public
ls -ld /var/www/example.com/public /var/www/example.com/public/wp-content
sudo getfacl -p /var/www/example.com/public/wp-content/uploads 2>/dev/null
# 3) 找异常宽松与异常缺失执行位
sudo find /var/www/example.com/public -type d -perm 777 -print
sudo find /var/www/example.com/public -type f -perm 666 -print
sudo find /var/www/example.com/public/wp-content -type d ! -perm -u+x -print
sudo find /var/www/example.com/public -type d ! -perm -o+x -print
# 4) 统计当前分布,作为回退依据
sudo find /var/www/example.com/public -type d -printf '%m
' | sort | uniq -c
sudo find /var/www/example.com/public -type f -printf '%m
' | sort | uniq -c
# 5) 确认 Web 用户到底是谁
sudo ps -eo user,group,comm,args | grep -E 'php-fpm|apache2|httpd' | grep -v grep
grep -E '^s*(user|group|listen.owner|listen.group)s*=' /etc/php/8.3/fpm/pool.d/www.conf
把第 4 步输出保存成 /root/perm-before-2026-$(date +%F).txt,后面任何修复失败都能按统计回退,而不是凭记忆再改一遍。若站点放在面板环境,路径可能是 /www/wwwroot/example.com,但命令不变;我们之前在宝塔 LNMP 部署 WordPress:目录、权限与伪静态一次配好 (2)里也强调过,伪静态与权限要分开验证,别把 403 一律归因到 rewrite。
755 与 644 的含义
权限位是三组八进制数:属主、属组、其他。7 = rwx,5 = r-x,4 = r--。目录的 x 不是「执行程序」,而是「可穿越、可 stat() 目录内条目」;文件的 x 才是可执行。因此目录 755 表示属主可增删改,属组和其他人只能列目录并进入;文件 644 表示属主可读写,其余人只读。PHP 文件不需要给 Web 用户 x,因为由 PHP-FPM 解释器读取源码,不是由内核直接执行 ELF。
| 对象 | 推荐模式 | 属主:属组示例 | 为什么 |
|---|---|---|---|
站点根目录、wp-admin、wp-includes | 755 | deploy:www-data | Web 用户需穿越并读取,禁止写入核心代码 |
普通 .php / .css / .js | 644 | deploy:www-data | FPM 读源码即可,写权限等于给漏洞落地开口 |
wp-config.php | 600 或 640 | deploy:www-data | 含 DB_PASSWORD、AUTH_KEY,不需要 other 可读 |
wp-content/uploads 及子目录 | 755,必要时 775 | deploy:www-data 且组可写 | 仅此目录允许 Web 用户创建文件 |
| 上传的文件 | 644,必要时 664 | deploy:www-data 或 www-data:www-data | 静态资源可读,禁止被当成脚本执行由 Web 服务器另行拦截 |
| 任何目录 777 / 文件 666 | 禁止 | — | 同机任意进程可写,等于取消隔离边界 |
标准修复命令如下。注意 find -exec chmod 必须分开处理目录和文件,不要一条 chmod -R 755 把图片也变成可执行;也不要对 wp-config.php 套 644 后再忘记收紧。
SITE=/var/www/example.com/public
OWNER=deploy
GROUP=www-data
sudo chown -R $OWNER:$GROUP $SITE
sudo find $SITE -type d -exec chmod 755 {} +
sudo find $SITE -type f -exec chmod 644 {} +
sudo chmod 640 $SITE/wp-config.php
# 只给上传目录组写能力,并让新建文件继承组
sudo chmod 2775 $SITE/wp-content/uploads
sudo find $SITE/wp-content/uploads -type d -exec chmod 2775 {} +
sudo find $SITE/wp-content/uploads -type f -exec chmod 664 {} +
# 验证:应只看到 uploads 带组写位
sudo find $SITE -type d -perm /002 -print
sudo find $SITE -type f -perm /002 -print
这里的 2775 中开头的 2 是 setgid:Web 用户在 uploads/2026/02/ 新建文件时,属组自动继承 www-data,部署用户后续仍能通过组权限清理,避免「今天上传成功、明天 deploy 删不掉」。若服务器启用了 SELinux,chmod 正确仍可能 403,需补 sudo semanage fcontext -a -t httpd_sys_rw_content_t '$SITE/wp-content/uploads(/.*)?' 与 sudo restorecon -Rv $SITE/wp-content/uploads;这是标签问题,不是八进制问题。
属主与 Web 用户
777 出事的根因不是「数字大」,而是它把「谁可以写」这个问题交给了同机所有进程。共享主机、Docker 多站点挂载、面板里跑的备份脚本、被入侵的相邻站点,只要拿到 other 写位,就能往 wp-content/plugins/ 丢 wp-cache.php,再由 FPM 解析。正确模型是两层身份:deploy 负责 git pull、wp core update、wp plugin install;www-data 只负责运行时读取和用户上传。
先确认 FPM 池配置,再决定属组。2026 年常见 PHP 8.2/8.3 池文件位于 /etc/php/8.3/fpm/pool.d/www.conf:
获取报价
邮箱 + 需求 + 预算区间,24 小时内回复报价。
[www]
user = www-data
group = www-data
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
php_admin_value[open_basedir] = /var/www/example.com/public:/tmp
php_admin_flag[allow_url_fopen] = off
改完执行 sudo php-fpm8.3 -t && sudo systemctl reload php8.3-fpm。若用 Nginx,nginx 主进程只是转发静态文件,静态读依赖 other 的 r-x,所以目录 755 不能改成 750,除非把 nginx 也加进 www-data 组并重载。常见踩坑有三类:用 root 跑过 wp-cli,导致 wp-content/uploads 里混进 root:root 文件;容器里宿主机 UID 1000 与镜像内 www-data UID 33 不一致,挂载卷后看起来属主对、实际不匹配;用 chmod -R 之后没处理 ACL,getfacl 里残留 user:www-data:rwx 让收紧失效。
处理残留 ACL 用 sudo setfacl -R -b $SITE 后再重跑标准命令;容器场景在 docker compose 里显式写 user: '33:33' 或在 Dockerfile 里 usermod -u 1000 www-data,不要靠 777 抹平 UID 差异。需要后台直接装插件时,推荐在 wp-config.php 里显式声明 define('FS_METHOD', 'direct'); 并配合部署用户执行 wp plugin install,而不是把整个目录交给 Web 用户;get_filesystem_method() 返回 direct 只代表 PHP 有写权限,不代表这个写权限应该长期开放。涉及多人协作和发布流程时,可把权限基线写进WordPress 企业网站开发的部署规范里,让 CI、回滚和外包交接使用同一套属主约定。
目录缺执行位的典型症状
把目录设成 644、744 或 750 且 Web 用户不在属组时,文件本身可读也没用:内核在解析 /wp-content/uploads/2026/01/logo.png 的每一级路径时都要 x。典型症状按层出现:第一层,首页 HTML 正常、CSS/JS 404 或 403,浏览器 Network 里 /wp-includes/js/jquery/jquery.min.js 返回 403;第二层,后台能登录但媒体库缩略图全裂,wp_get_attachment_image_src() 返回 URL 却取不到文件;第三层,Nginx 日志写 stat() '/var/www/.../wp-content/' failed (13: Permission denied),而 ls -l 看文件是 644,管理员因此误判成「文件权限不够」并错误地给文件加 666;第四层,PHP 侧 is_dir()、wp_mkdir_p()、WP_Image_Editor 生成缩略图失败,wp_upload_bits() 写入报 Unable to create directory。
定位要逐级 namei,它比逐条 ls 更快暴露断点:
sudo namei -l /var/www/example.com/public/wp-content/uploads/2026/01/logo.png
# 期望每一级目录至少含 x;示例断点:
# drwxr--r-- deploy www-data wp-content <-- 缺 g+x/o+x,FPM 在此失败
sudo -u www-data -- stat /var/www/example.com/public/wp-content/uploads/2026/01/logo.png
sudo -u www-data -- test -x /var/www/example.com/public/wp-content && echo 'traverse ok' || echo 'traverse denied'
修复只补目录执行位,不给文件加执行位:sudo find $SITE -type d -exec chmod 755 {} +。若父目录 /var/www 或 /var/www/example.com 本身是 750 且 www-data 不在组里,站点内怎么改都无效,用 sudo namei -l 一直看到 / 为止。另一个高频坑是 umask 077:部署脚本新建的 uploads/2026/ 变成 700,当月第一张图正常、换月后全裂;在 /etc/profile.d/wp-deploy.sh 或 CI job 里设 umask 002,并给 uploads 加 setgid 即可。失败回退:若补完执行位出现 500,先 sudo nginx -t && sudo systemctl reload nginx 排除配置变更,再用前面保存的 perm-before-2026-*.txt 对照,只回退 wp-content 子树,别整站退回 777。
用 Web 用户身份验证
「我 root 下 ls 都正常」不能证明配置正确。所有验证必须切换到真实运行时身份,且带上 FPM 的环境约束。sudo -u www-data 默认不加载 open_basedir、umask 和池里的 php_admin_value,所以它适合做文件系统层验证;PHP 层还要用 wp eval 或临时 php -r 复核。
SITE=/var/www/example.com/public
WEBU=www-data
# A. 必须成功:读核心、读配置、穿越目录、写 uploads
sudo -u $WEBU -- cat $SITE/wp-includes/version.php >/dev/null && echo 'read core ok'
sudo -u $WEBU -- test -r $SITE/wp-config.php && echo 'read config ok'
sudo -u $WEBU -- sh -c 'test -x '$SITE'/wp-content && test -x '$SITE'/wp-content/uploads' && echo 'traverse ok'
sudo -u $WEBU -- sh -c 'echo probe > '$SITE'/wp-content/uploads/.perm-probe && rm -f '$SITE'/wp-content/uploads/.perm-probe' && echo 'upload write ok'
# B. 必须失败:写核心、写主题、写插件、写配置
sudo -u $WEBU -- sh -c 'echo x >> '$SITE'/wp-config.php' 2>/dev/null && echo 'DANGER: config writable' || echo 'config locked ok'
sudo -u $WEBU -- sh -c 'echo x > '$SITE'/wp-content/plugins/.perm-probe' 2>/dev/null && echo 'DANGER: plugins writable' || echo 'plugins locked ok'
sudo -u $WEBU -- sh -c 'echo x > '$SITE'/wp-content/themes/.perm-probe' 2>/dev/null && echo 'DANGER: themes writable' || echo 'themes locked ok'
# C. PHP 运行时复核(wp-cli 以 Web 用户跑,不切 root)
sudo -u $WEBU -- wp --path=$SITE eval 'echo "uid=".getmyuid()." writable_uploads=".(is_writable(WP_CONTENT_DIR."/uploads")?"yes":"no")." fs=".get_filesystem_method().PHP_EOL;'
sudo -u $WEBU -- wp --path=$SITE core verify-checksums
B 组如果打印 DANGER,说明还有 777、ACL、setgid 之外的组写位或 wp-content 被挂成独立可写卷,立刻用 getfacl -p 和 find -perm /002 反查。C 组里 get_filesystem_method() 返回 direct 不代表失败,要结合 B 组判断:2026 年不少托管环境用 FS_METHOD 配合只读代码卷,后台更新由 CI 执行 wp core update,这是更干净的形态。若 core verify-checksums 报出 wp-content/plugins/xxx/shell.php 之外的核心文件被改,先别 chmod,按入侵处理:摘流量、保留 /var/log 与镜像快照、从官方安装包校验后覆盖核心,再轮换 AUTH_KEY 与数据库口令。
还要补一条 Web 服务器层拦截,避免「uploads 可写」被利用成「uploads 可解析」:
# Nginx:放在处理 PHP 的 location 之前
location ~* ^/wp-content/uploads/.*.php(?:[0-9]+)?$ {
deny all;
return 403;
}
# Apache / .htaccess(uploads 目录内)
<FilesMatch '.php[0-9]?$'>
Require all denied
</FilesMatch>
Options -Indexes -ExecCGI
验证用 curl -i https://example.com/wp-content/uploads/.perm-probe.php 应得到 403,且 grep -E 'perm-probe' /var/log/nginx/access.log 能追到请求。把这套 A/B/C 检查写进发布流水线的 post-deploy 阶段,比人工登服务器看 ls -l 稳定得多;如果团队缺少统一的运维基线,可参考服务页里的部署与交接项,把权限校验、日志留存和回退脚本一起交付,而不是只交一份站点压缩包。
修复步骤与排障流程

- 冻结变更并取证:保存
find -printf '%m'统计、getfacl输出、Nginx/PHP-FPM 最近 200 行日志到/root/perm-before-2026-日期.txt,同时确认ps里的真实 Web 用户与 FPM 池文件。 - 清掉 777 与残留 ACL:对全站执行
setfacl -R -b,按「目录 755 / 文件 644 /wp-config.php640」重建基线,chown -R deploy:www-data,不要使用chmod -R 755。 - 开最小写入面:仅
wp-content/uploads用2775+ 文件664,需要后台临时装插件时改由deploy跑wp plugin install,装完立刻复查find -perm /002。 - 逐级验证路径:用
namei -l从/看到目标文件,补父目录执行位;再用「用 Web 用户身份验证」小节的 A/B/C 三组命令,要求 A 全通过、B 全拒绝、C 无校验和异常。 - 补运行时护栏:Nginx/Apache 禁止 uploads 解析 PHP,FPM 配
open_basedir,CI 设umask 002,SELinux 环境补httpd_sys_rw_content_t标签并restorecon。 - 失败回退与复盘:若出现 500/403,先
nginx -t、php-fpm8.3 -t排除语法,再只按取证文件回退wp-content子树;确认恢复后把命令固化成scripts/wp-perm-audit.sh,禁止任何人再用 777 作临时方案。
流程里最容易省掉的是第 1 步和第 4 步。没有取证,回退会变成第二次破坏;不用 Web 身份跑 B 组「必须失败」的断言,就等于只验证了「能上传」,没验证「不能被写马」。wp-config.php 里可加 define('DISALLOW_FILE_EDIT', true); 关闭后台主题编辑器,配合 define('DISALLOW_FILE_MODS', true); 用于纯 CI 更新的生产站;注意后者会让后台安装/更新全部走发布流程,需先和站点负责人确认运营习惯,否则会被当成「后台坏了」。
预防:把权限变成可审计的基线
2026 年的现实是:WordPress 核心更新频繁、PHP 8.3 已成主流、容器与面板混用、外包与内部团队交替改站点。靠「记得别用 777」挡不住事故,要把基线写成脚本、写进 CI、写进监控。建议仓库里放 scripts/wp-perm-audit.sh,每次 git pull、wp core update、插件批量升级后由 deploy 执行;输出非白名单的可写路径即以非零码退出,让发布失败而不是带病上线。
#!/usr/bin/env bash
set -euo pipefail
SITE='${SITE:-/var/www/example.com/public}'
ALLOW='^/var/www/example.com/public/wp-content/uploads(/.*)?$'
bad=$(sudo find "$SITE" ( -type d -o -type f ) -perm /002 -print | grep -Ev "$ALLOW" || true)
if [ -n "$bad" ]; then
echo 'FAIL: writable outside uploads' >&2
echo "$bad" >&2
exit 1
fi
sudo -u www-data -- sh -c 'echo x > '"$SITE"'/wp-content/plugins/.audit' 2>/dev/null && {
echo 'FAIL: plugins writable by web user' >&2; exit 1;
}
echo 'OK: WordPress 目录权限设置基线通过'
监控侧每周跑一次 wp core verify-checksums 与 find $SITE -type f -name '*.php' -newermt '7 days ago' -print,把 wp-content/uploads 之外的近期 PHP 变更推到告警群;备份脚本不要用 root 直接解包到站点根,解到临时目录后 rsync --chown=deploy:www-data --chmod=D755,F644 再切换软链。多站点同机时给每站独立 FPM 池和独立系统用户,open_basedir 按站隔离,避免「A 站被写马 → 扫到 B 站目录」;这正是 777 最常被低估的爆炸半径。官方文档只给权限基线,不替你管 UID 映射、ACL、SELinux 和发布流程,这部分要由站点负责人签字确认。
常见问题
改成 755/644 后后台要求 FTP 凭据,是不是必须退回 777?
不是。先 sudo -u deploy -- wp --path=$SITE plugin install 插件名 --activate 由部署用户完成安装,再复查属主;或在 wp-config.php 临时加 define('FS_METHOD', 'direct'); 并仅对 wp-content/plugins 开短时组写,操作完立即收回。退回 777 只是把认证提示换成长期后门。
上传目录 755 传不了图,改 775 算不算妥协?
不算,前提是属组是 Web 用户组且带 setgid(2775),并且写入面只限 wp-content/uploads。用 find $SITE -perm /002 -print 确认没有扩散到 themes、plugins 或站点根;同时保留 uploads 禁解析 PHP 的规则。
容器里权限全对却仍 403,下一步查什么?
先 namei -l 看宿主机挂载点父目录,再比对宿主机 UID 与镜像内 www-data 的 UID;随后查 SELinux/AppArmor 标签与 FPM open_basedir。这三类都不是八进制问题,改 777 通常无效还会掩盖真实断点。
上线前动作清单
- 全站执行基线:目录 755、文件 644、
wp-config.php640,find -perm 777 -o -perm 666输出为空,并保存一份取证文件。 - 确认
ps与www.conf中的 Web 用户,完成 A/B/C 三组sudo -u www-data断言:可读、可传、不可改代码。 - 仅
wp-content/uploads使用2775/664,配置 Nginx 或 Apache 禁止该目录解析 PHP,用curl -i验证返回 403。 - 把
wp-perm-audit.sh、umask 002、wp core verify-checksums接入发布流水线,失败即阻断上线。 - 约定回退路径:只回退
wp-content子树、先nginx -t与php-fpm8.3 -t,并禁止把 777 写进任何临时工单或面板模板。
需要把权限基线、CI 与应急回退一次落地?查看WordPress 开发服务,或浏览技术栏目更多排障实录。
“,”faq”:[{“question”:”改成 755/644 后后台要求 FTP 凭据,是不是必须退回 777?”,”answer”:”不是。用 deploy 执行 wp plugin install,或短时开 FS_METHOD 与 plugins 组写并立即收回;退回 777 等于用长期后门换掉一条提示。”},{“question”:”上传目录 755 传不了图,改 775 算不算妥协?”,”answer”:”不算。用 deploy:www-data 加 2775 setgid,只限 wp-content/uploads,并用 find -perm /002 确认未扩散,同时禁止 uploads 解析 PHP。”},{“question”:”容器里权限全对却仍 403,下一步查什么?”,”answer”:”查挂载点父目录、宿主机与镜像 UID 映射、SELinux/AppArmor 标签和 FPM open_basedir;这些不是 chmod 能解决的问题。”}],”cover”:”Flat technical illustration of a server directory tree with locked folders, a guarded upload gate, a web process reading files, and a blocked write path, clean blue and slate palette, isometric layout, subtle grid background, no text, no letters, no numbers, no logo, no watermark.