网站被挂马、被上传 WebShell、被读取到配置文件里的数据库密码——这类事故的根源,十有八九是 Web 目录权限设置不当。最常见的错误配置是 chmod -R 777 /var/www。看似"解决了一切权限报错",实际上等于告诉系统上任何一个进程:这个目录你可以随便读写执行。一个很小的上传漏洞,就可能演变成整个站点被控、进而服务器被控。
Web 目录权限是个看起来简单、实际很容易踩坑的话题,因为它牵涉三方:文件属主、文件权限位、以及 Web 服务和 PHP 以什么用户身份运行。这三者要配合起来看,而不是孤立地 chmod 777。原则只有一句话:该写的目录尽量少、权限尽量小、运行用户尽量低。
这篇文章按"运行用户是谁、目录该给什么权限、上传目录怎么单独处理、Nginx/Apache/PHP 怎么配、常见错误怎么修"的顺序展开,给出可直接落地的权限方案和命令。涉及 chmod、chown 的批量操作都是高危操作,改错会让网站 500 或让文件泄露,动手前请先备份权限(getfacl)并确认可回滚。
一、先搞清楚:Web 进程以谁的身份运行
权限配置的第一步不是敲 chmod,而是确认运行用户。Linux 上,进程能对文件做什么,取决于进程的有效用户(effective UID)和文件权限位的组合。所以你必须先知道 Nginx 的 worker 进程、Apache 的进程、PHP 的 FPM 进程分别以谁运行。
# 查看 nginx worker 进程的运行用户
ps -eo pid,user,comm | grep nginx
# 或看主配置里的 user 指令
grep -E "^\s*user" /etc/nginx/nginx.conf
# 查看 Apache 运行用户
ps -eo pid,user,comm | grep -E "httpd|apache2"
grep -E "^\s*(User|Group)" /etc/httpd/conf/httpd.conf # RHEL 系
grep -E "^\s*(User|Group)" /etc/apache2/envvars # Debian 系
# 查看 php-fpm 池的运行用户
grep -E "^\s*user|^\s*group" /etc/php-fpm.d/www.conf # RHEL 系
grep -E "^\s*user|^\s*group" /etc/php/*/fpm/pool.d/www.conf # Debian 系
不同发行版和组件默认用的用户不同,下面这张表是常见默认值(经验总结,实际以你的配置为准):
| 组件 | RHEL/CentOS/Rocky | Debian/Ubuntu | 说明 |
|---|---|---|---|
| Nginx worker | nginx | www-data | 主进程通常由 root 启动再降权 |
| Apache | apache | www-data | 由 User/Group 指令指定 |
| PHP-FPM | apache 或 nginx | www-data | 看 pool 配置,建议独立用户 |
| 网站文件属主 | 部署用户 | 部署用户 | 建议非 root、非 Web 用户 |
关键认知:Nginx 和 PHP-FPM 通常是不同的用户。Nginx 负责把静态文件和 .php 请求处理掉,静态文件由 Nginx 用户读,PHP 脚本由 PHP-FPM 用户执行。如果你把文件属主设成 Nginx 用户却让 PHP 去写,就会出现权限问题;反之亦然。所以现代架构里,更推荐的做法是让 Nginx 和 PHP-FPM 使用同一个用户(或者把 PHP-FPM 用户加进 Nginx 用户组)来简化协作,同时把权限收紧在"只读 + 少数可写目录"。
二、目录与文件的基础权限方案
先记住一条原则性区别:目录的 x 位表示"能否进入/遍历",文件的 x 位表示"能否执行"。所以目录和文件的权限基准不一样。
推荐的基准(经验值,适用于绝大多数 PHP/静态站点):
- 目录:
755(属主读写执行,组和其他只读+遍历) - 文件:
644(属主读写,组和其他只读) - 属主:网站的部署/运维账号(比如
deploy),组:Web 运行组(www-data或nginx)
这样 Web 进程作为"组"或"其他"用户只能读、不能写,杜绝了"通过 Web 漏洞往代码目录写文件"的可能。
# 设定属主和属组(注意 -R 会递归,先确认路径正确)
chown -R deploy:www-data /var/www/html # Debian/Ubuntu
chown -R deploy:nginx /var/www/html # RHEL/CentOS/Rocky
# 目录统一 755,文件统一 644(两条命令分别处理,顺序不能反)
find /var/www/html -type d -exec chmod 755 {} \;
find /var/www/html -type f -exec chmod 644 {} \;
# 备份当前权限,便于回滚(重要!)
getfacl -R /var/www/html > /root/www_acl_backup_$(date +%F).txt
这里必须强调两条命令的顺序:先设目录、再设文件。因为 find -type f 不会影响目录,但要小心不要用 chmod -R 755 一把梭——那会把所有文件也变成可执行,虽然对 PHP 无害,但会让静态文件、上传文件带上 x 位,扩大风险。
三、可写目录:把"能写"限制到最小
网站总有些目录需要写权限,比如上传目录、缓存目录、日志目录、session 目录。核心思路是把可写范围压缩到最小,并且禁止在可写目录里执行脚本。
以常见的 PHP 应用为例,典型可写目录是:
wp-content/uploads/(WordPress 上传目录)storage/、cache/、logs/、sessions/(Laravel 等框架)runtime/(ThinkPHP 等)
对这些目录,给予 Web 用户写权限,但不给执行权限(对目录来说,去掉 x 会导致无法进入,所以目录仍需要 x;这里的"不给执行"是针对脚本文件层面,见下一节的 Nginx/Apache 配置)。权限可以设成 775 并把属组设为 Web 组:
# 上传目录:属主给部署用户,属组给 Web 用户,组可写
chown -R deploy:www-data /var/www/html/wp-content/uploads
chmod -R 775 /var/www/html/wp-content/uploads
# 只对目录和文件分别设权限,保持一致性
find /var/www/html/storage -type d -exec chmod 775 {} \;
find /var/www/html/storage -type f -exec chmod 664 {} \;
注意 664(组可写、其他只读)比 644 多了组写权限,适用于需要组内共享写的场景。不要用 777,因为那会额外给"其他"用户写权限——系统上任何服务(包括被攻陷的无关进程)都能往里写。
一个进阶但很有用的技巧是利用 setgid 位保证新建文件继承属组。如果上传目录的属组会变化,可以给目录设 setgid:
# 目录属组设为 www-data,并设 setgid(2xxx),新建文件自动继承该组
chmod 2775 /var/www/html/wp-content/uploads
setgid 目录的好处是:无论谁在这里建文件,文件的组都自动是目录的组,避免出现"新建文件属组不对导致 Web 读不到"的经典问题。
四、禁止在可写/上传目录执行脚本
这是防 WebShell 最关键的一招。攻击者上传了 .php 马儿,即使文件落地成功,只要你让 Nginx/Apache 不在上传目录里执行 PHP,它也只能变成一段无害的静态文本。
Nginx + PHP-FPM 的配置方式是在上传目录的 location 里拒绝 .php:
# 在 server 块中,针对上传目录做保护
location ~* ^/wp-content/uploads/.*\.php$ {
deny all;
return 403;
}
# 或者更通用地拒绝常见脚本后缀
location ~* /(uploads|files|images|cache)/.*\.(php|php5|phtml|jsp|asp|aspx|sh)$ {
deny all;
}
Apache 则用 .htaccess 或在虚拟主机里配置:
# 在 /var/www/html/wp-content/uploads/.htaccess 中
Require all denied
# 或全局关闭该目录的 PHP 引擎(老式 mod_php,需 php_flag 支持)
php_flag engine off
需要说明的是,php_flag engine off 只对 mod_php 有效;用 PHP-FPM 时应该用上面的"拒绝后缀"方式,因为 PHP 执行已交给 FPM,Apache 的 php_flag 管不到。
另外,把上传目录设置成 Nginx 的 internal; 或加上 X-Content-Type-Options: nosniff 头,能进一步降低风险。还可以给整个站点加上安全响应头:
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
五、PHP 运行用户的配置与常见权限报错
前面提到,Nginx 和 PHP-FPM 是不同用户,实践中经常出现"Nginx 能读到文件、PHP 写不进去"或反过来的情况。解决思路是让两方在一个组里,或者干脆统一用户。
以 RHEL 系为例,让 PHP-FPM 用 nginx 用户:
; /etc/php-fpm.d/www.conf
user = nginx
group = nginx
Debian 系对应的文件是 /etc/php/*/fpm/pool.d/www.conf:
user = www-data
group = www-data
修改用户后要重启 php-fpm,并且确认新用户对代码目录和 session 目录有正确的读/写权限,否则网站会白屏或报 500。
# RHEL 系
systemctl restart php-fpm
# Debian 系(版本号按实际填)
systemctl restart php8.1-fpm
# 单独给 session 目录授权(PHP session 默认存这里)
chown -R www-data:www-data /var/lib/php/sessions # Debian
chown -R nginx:nginx /var/lib/php/session # RHEL
下面这张表列出常见权限报错和对应的修法,排查时对照很快:
| 现象 | 常见原因 | 修法 |
|---|---|---|
| 上传失败、写入被拒 | 上传目录 Web 用户无写权限 | 目录 775、属组设为 Web 用户 |
| 访问页面 403 | 目录缺 x 位或文件缺读权限 |
目录 755、文件 644 |
| 页面空白/500 | PHP 无法读脚本或 session 目录不可写 | 检查 php-fpm 用户与文件属组 |
| 静态图片不显示 | Nginx 用户对文件无读权限 | 文件给 644、属组含 Nginx 组 |
| 新建文件属组不对 | 未设 setgid 或属组配置错 | 目录加 setgid(2775) |
| 缓存目录写满报错 | 缓存目录权限或磁盘满 | 检查权限 + df -h |
最后强调一个容易忽略的点:代码目录绝对不要归 Web 用户所有。正确的模型是"部署用户拥有代码,Web 用户只能读,少数目录 Web 用户可写"。如果整个站点都归 www-data 所有且组可写,那么一旦有任何脚本执行漏洞,攻击者就能直接改写代码、植入后门。
常见问题
Q:为什么不能用 chmod 777 一劳永逸?
因为 777 给"其他"用户也开了写和执行权限。系统上任何进程(包括被攻陷的、以其他用户运行的服务)都能读写执行这个目录里的文件。这等于把 Web 目录变成了公共可写区,是 WebShell 和挂马的温床。绝大多数看似"需要 777"的场景,都能用正确的属主 + 755/775 解决。
Q:网站因为权限问题 500,临时改成 777 先顶着可以吗?
不建议,风险没有临时一说——攻击者不会等你改回来。正确做法是定位到底哪个进程、对哪个目录需要什么权限,然后精确授予。可以用 namei -l /path/to/file 逐级检查路径上每一层的权限,快速找到断点。
Q:Nginx 和 PHP-FPM 用不同用户时,最省心的做法是什么?
把 PHP-FPM 的运行用户设为 Nginx 的用户(或让 PHP-FPM 用户加入 Nginx 的组),代码目录属主归部署用户、属组归 Web 组。这样两方都能读,只有明确的可写目录给组写权限。
Q:上传目录加了"禁止执行 PHP"后,上传的图片打不开怎么办?
说明你的规则误伤了静态文件。检查 location 的正则,确保只匹配脚本后缀(\.php$),不要把图片后缀也拦了。~~* 是大小写不敏感匹配,注意别把 uploads 目录下的所有请求都 deny 掉。
Q:改了权限之后网站正常了,还需要做什么?
把当前权限导出备份(getfacl -R),记录在文档里,并纳入变更管理。下次再遇到权限问题,有基线可对比,也能避免有人again用 chmod 777 覆盖掉你的配置。同时监控代码目录的文件变更,及时发现被植入的 WebShell。
总结
- 权限模型的核心是"三方协同":文件属主、权限位、Web/PHP 运行用户,先确认运行用户再动 chmod。
- 基准权限用目录 755、文件 644,属主给部署用户、属组给 Web 组;可写目录收紧到
775/664,绝不使用777。 - 可写目录要"少而准",并利用 setgid(
2775)让新建文件继承属组,避免权限漂移。 - 防 WebShell 的关键是在上传/可写目录禁止执行脚本:Nginx 用 location +
deny、Apache 用FilesMatch,这是性价比最高的一招。 - 代码目录不要归 Web 用户所有,正确模型是"部署用户拥有、Web 用户只读、少数目录可写"。
- 所有
chmod/chown -R操作前先getfacl备份权限、确认路径,出错可回滚;遇到 500 不要用 777 顶,用namei -l定位真正的权限断点。
企业QQ咨询




