当前位置:首页>Linux>Linux 用户组与文件权限:为什么你的程序总是 Permission denied?

Linux 用户组与文件权限:为什么你的程序总是 Permission denied?

  • 2026-10-08 22:47:33
Linux 用户组与文件权限:为什么你的程序总是 Permission denied?
你刚部署完一个网站,打开浏览器一看——500 错误。查日志,Permission denied。你改了文件权限,没用。你改了文件属主,还是不行。你给了 777,依然报错——因为目录没给执行权限。

这种场景我经历太多次了。很多人用了好几年 Linux,对权限还是一知半解,出了问题就乱试 777,试到能跑就行。

但权限模型其实是 Linux 最核心的设计之一,理解了它,你才能真正说自己会用 Linux。

什么是用户、组、文件权限?

Linux 是一个多用户操作系统。这句话说起来简单,背后的含义是:多个用户可以同时在一台机器上干活,而且互相不能乱看乱动对方的东西。

为了实现这个目标,Linux 设计了一套三层的权限控制体系:

文件 → 属主(User) → 属组(Group) → 其他人(Others)

每个文件都被绑定到一个用户和一个组。当有人要访问这个文件时,系统会问三个问题:

  1. 你是谁?你是这个文件的属主吗?如果是,用属主权限。
  2. 如果你不是属主,你属于这个文件的属组吗?如果是,用组权限。
  3. 如果你既不是属主也不是属组成员,那你就是其他人,用其他人的权限。

这个逻辑很清晰吧?

打个比方,你可以把 Linux 想象成一个写字楼:

  • 每个用户就是一个员工,有自己的办公桌(家目录)
  • 每个组就是一个部门,部门共享一些文件
  • 其他人就是其他部门的人,没权限进你的办公室

这个类比只是帮助理解,真实实现并不完全一致。

💡 根用户

有一个特殊用户叫 root,它是超级管理员,不受任何权限限制。哪怕权限是 000,root 照样能读写。

为什么需要用户和组?

最早的 Unix 设计出来就是给多人用的。几十年过去了,这个设计依然好用,为什么?

安全隔离

这是最核心的原因。如果没有权限隔离,一个普通用户误删了系统文件,整个机器就挂了。应用程序如果漏洞被黑客入侵,黑客只能拿到应用程序用户的权限,没法直接染指整个系统。

最小权限原则

在生产环境中,比较好的做法是给每个服务创建单独的用户。比如 nginx 跑在 nginx 用户下,mysql 跑在 mysql 用户下。这样就算其中一个服务被攻破,攻击者也不能随便读写其他服务的文件。

我见过不少人图省事,所有服务都用 root 跑,这等于把家门钥匙直接放在门口。一旦出问题,就是全境失守。

协作共享

组的设计方便团队协作。开发组共享项目代码,大家都有读写权限,但其他人看不到。这个设计到今天依然适用。

权限到底是怎么工作的?

三种基本权限

对文件来说,有三种基本权限:

权限
字母
数字
对文件的意义
对目录的意义
读
r
4
可以读取文件内容
可以列出目录里的文件
写
w
2
可以修改文件内容
可以添加/删除目录里的文件
执行
x
1
可以运行这个程序/脚本
可以进入这个目录(cd)
⚠️ 目录权限很容易理解错

很多人以为只要我有文件权限,就能访问文件。不对!你必须对文件所在的所有父目录都有执行权限,才能到达这个文件。

如果目录没有 x 权限,就算你有文件的 r 权限,也读不了。

权限的数字表示

权限用数字表示的时候,就是三个权限位相加:

  • rwx
     = 4+2+1 = 7
  • rw-
     = 4+2 = 6
  • r-x
     = 4+1 = 5
  • r--
     = 4 = 4

所以常见的 755 就是:

  • 属主:rwx (7)
  • 组:r-x (5)
  • 其他人:r-x (5)

644 就是:

  • 属主:rw- (6)
  • 组:r-- (4)
  • 其他人:r-- (4)

看一下实际例子

用 ls -l 看看文件权限:

$ ls -l-rw-r--r--  1 alice  developers  1024 Jul  1 10:00 app.pydrwxr-xr-x  2 alice  developers   64  Jul  1 10:00 src

逐段解释:

第一段 -rw-r--r--:

  • 第一个字符:- 表示普通文件,d 表示目录,l 表示软链接
  • 接下来三个:rw- → 属主 alice 有读写权限,没有执行
  • 再接下来三个:r-- → 组 developers 成员只有读权限
  • 最后三个:r-- → 其他人只有读权限

第二段 1:硬链接个数。不用太关心。

第三段 alice:属主用户名。

第四段 developers:属组名。

常用命令全解析

查看当前用户和组

whoami       # 我是谁id           # 显示我的用户ID、组ID、所属哪些组groups       # 显示我所属的所有组

创建用户(useradd)

useradd 用户名                  # 创建新用户(默认不创建家目录)useradd -m 用户名               # 创建用户,同时创建家目录 /home/用户名useradd -M 用户名               # 创建用户,明确不创建家目录useradd -s /sbin/nologin 用户名  # 创建用户,指定登录 shell 为 nologin(禁止登录)useradd -r 用户名               # 创建系统用户(UID < 1000,不创建家目录)useradd -d /data/用户名 用户名   # 指定家目录路径useradd -e 2026-12-31 用户名     # 设置账号过期日期useradd -u 1500 用户名           # 指定 UID

常用选项一览:

选项
含义
-m
创建家目录 /home/用户名
-M
强制不创建家目录
-s
指定登录 shell,默认 /bin/bash
-g
指定主组
-G
指定附加组
-r
创建系统用户,UID 在 1~999 之间
-d
指定家目录路径
-e
账号过期日期,格式 YYYY-MM-DD
-u
指定 UID
💡 实际经验

大多数发行版 useradd 默认不创建家目录,记得加 -m 参数。我刚开始经常忘,结果登录的时候提示 No directory, logging in with HOME=/。

修改密码(passwd)

passwd 用户名          # 给指定用户设置密码(root 可以改任何人的密码)passwd                 # 修改自己的密码passwd -d 用户名       # 删除用户密码,允许空密码登录passwd -e 用户名       # 强制用户下次登录时必须修改密码passwd -n 7 用户名     # 设置密码最短使用天数(7 天内不能改密码)passwd -x 90 用户名    # 设置密码最长使用天数(90 天后必须改密码)passwd -w 7 用户名     # 密码过期前 7 天开始警告passwd -i 3 用户名     # 密码过期后 3 天内仍可登录,超过则锁定
💡 强制改密码的实际场景

passwd -e 在新人入职时特别有用。你创建好账号,设一个临时密码,然后 passwd -e 新人,对方第一次登录系统就会强制要求改密码。既安全又不尴尬。

锁定与解锁用户

锁定用户分两种:锁密码和锁账号。

锁密码——用户仍然可以用 SSH Key 等方式登录,只是不能用密码:

passwd -l 用户名     # 锁定密码,在 /etc/shadow 中密码字段前加 !!passwd -u 用户名     # 解锁密码passwd -S 用户名     # 查看密码状态(PS = 正常,LK = 锁定,NP = 无密码)

锁账号——账号直接过期,任何方式都登不进来:

usermod -L 用户名    # 锁定账号,在 /etc/shadow 中密码字段前加 !(注意和 passwd -l 的区别)usermod -U 用户名    # 解锁账号

passwd -l 和 usermod -L 的区别可以不用太纠结,日常用 passwd -l 就行。两者的效果都是让密码失效,只是 /etc/shadow 里的标记方式不同。

禁止用户登录

有时候你需要一个用户来跑服务,但不希望别人用这个账号登录系统。比如 nginx 用户、mysql 用户,都是"服务用户",不需要登录 shell。

usermod -s /sbin/nologin 用户名    # 改成 nologin,登录时显示提示信息后退出usermod -s /bin/false 用户名       # 改成 /bin/false,登录时直接退出(无提示)

/sbin/nologin 和 /bin/false 的区别:

  • /sbin/nologin
    :用户尝试登录时会显示一行提示信息(可以自定义),然后退出
  • /bin/false
    :直接退出,什么都不显示
💡 看服务用户的 shell
$ cat /etc/passwd | grep nginxnginx:x:996:993:nginx user:/var/cache/nginx:/sbin/nologin

最后一列就是 shell。服务用户的 shell 基本都是 /sbin/nologin。

如果你想让某个用户连 FTP 都不能用,但能用 SSH:

usermod -s /usr/sbin/nologin 用户名

然后把 /usr/sbin/nologin 加到 /etc/shells 里(不推荐,大多数情况下直接用 /sbin/nologin 就够了)。

💡 如何恢复登录?

直接改回来就行:

usermod -s /bin/bash 用户名

改完之后 su - 用户名 试一下能不能登进去。如果之前还锁了密码,记得一起解:

passwd -u 用户名

修改用户信息(usermod)

usermod -l 新用户名 旧用户名    # 重命名用户(改用户名)usermod -d /new/home 用户名     # 修改家目录路径(不会自动移动文件)usermod -m -d /new/home 用户名  # 修改家目录路径,同时把旧文件搬过去usermod -c "备注" 用户名        # 修改用户备注信息(GECOS 字段)usermod -e 2026-12-31 用户名    # 修改账号过期日期usermod -s /bin/bash 用户名      # 修改登录 shell

删除用户(userdel)

userdel 用户名        # 删除用户,但保留家目录和邮件userdel -r 用户名     # 删除用户,同时删除家目录和邮件
⚠️ 删除用户前注意

userdel -r 会直接删掉家目录下的所有文件,没法恢复。删之前确认一下有没有需要保留的数据。另外,用户创建的文件(分散在系统各处)不会自动删除,属主会变成数字 UID,需要用 find / -uid UID 去找。

创建组

groupadd 组名                    # 创建新组useradd -G 组名 用户名           # 创建用户时把用户加入组usermod -aG 组名 用户名          # 把已有的用户添加到组
⚠️ 注意 `-a` 参数

usermod -G 会覆盖用户原来的所有组!一定要加 -a 参数(append),意思是"追加"到组。

# 正确:追加,不影响原来的组usermod -aG docker alice# 错误:alice 只属于 docker 组了,原来的组全没了usermod -G docker alice

查看用户属于哪些组

groups 用户名

例子:

$ groups alicealice : alice docker developers

说明 alice 是这三个组的成员。

修改文件属主和属组:chown

chown = change owner,改文件的属主和属组。

chown 用户名 文件                # 只改属主chown 用户名:组名 文件           # 同时改属主和属组chown :组名 文件                 # 只改组,不改属主chown -R 用户名:组名 目录        # 递归改目录下所有文件

例子:

# 把 /var/www 目录的属主改成 nginx 用户,属组改成 nginx 组chown -R nginx:nginx /var/www# 只把组改成 developerschown :developers /home/alice/project

修改文件属组:chgrp

其实 chown :group 就能改组,chgrp 只是更直观:

chgrp 组名 文件chgrp -R 组名 目录

修改文件权限:chmod

chmod = change mode,改权限位。

两种写法:数字法和符号法。

数字法(最常用):

chmod 644 文件     # 属主 rw,组 r,其他人 rchmod 755 目录     # 属主 rwx,组 rx,其他人 rxchmod -R 755 目录  # 递归

符号法(适合改个别权限):

u 代表属主,g 代表组,o 代表其他人,a 代表所有人。

chmod u+x 文件     # 给属主加上执行权限chmod g+w 文件     # 给组加上写权限chmod o-r 文件     # 移除其他人的读权限chmod a+x 文件     # 给所有人加执行权限

/etc 下的用户配置文件详解

用户和组的信息都存在 /etc 下面的几个文件里。我们来逐个看清楚它们到底存了什么。

/etc/passwd:用户信息数据库

这是最核心的用户信息文件,所有用户信息都存在这里。我们先看一个实际例子:

$ cat /etc/passwd | head -5root:x:0:0:root:/root:/bin/bashbin:x:1:1:bin:/bin:/sbin/nologindaemon:x:2:2:daemon:/sbin:/sbin/nologinalice:x:1000:1000:Alice Smith:/home/alice:/bin/bashnginx:x:996:993:nginx user:/var/cache/nginx:/sbin/nologin

每一行用冒号分成 7 个字段,我们一个个说:

字段
含义
例子
1
用户名
root
、alice、nginx
2
密码占位符
x
 表示密码存在 /etc/shadow
3
UID(用户 ID)
0
 root,1-999 系统用户,1000+ 普通用户
4
GID(主组 ID)
对应 /etc/group 里的组 ID
5
备注信息(GECOS)
用户全名或描述
6
家目录路径
/root
、/home/alice、/var/cache/nginx
7
登录 Shell
/bin/bash
、/sbin/nologin
💡 第二个字段为什么是 x?

很早以前密码真的存在这里,但是所有人都能读这个文件,太不安全了。后来就把密码移到了 /etc/shadow,这里只放一个 x 占位。

关于 UID 的约定:

  • UID 0
    :root,超级用户
  • UID 1-999
    :系统用户,跑服务用的,一般不登录
  • UID 1000 及以上
    :普通用户,给人用的

大多数发行版第一个创建的普通用户 UID 就是 1000,第二个 1001,以此类推。

/etc/shadow:加密密码存放地

/etc/shadow 才是真正存密码的地方。这个文件只有 root 能读,普通用户看不了,这就安全多了。

我们看个例子:

sudo cat /etc/shadow | grep alicealice:$6$xyz...$abcdef...:19000:7:90:7:3:19500:

还是冒号分隔,一共 9 个字段:

字段
含义
说明
1
用户名
对应 /etc/passwd 的用户名
2
加密后的密码
这就是核心了,我们下面细说
3
上次改密码日期
从 1970-01-01 算起的天数
4
密码最小天数
多少天内不能改密码
5
密码最大天数
多少天后必须改
6
警告提前天数
过期前多少天开始警告
7
过期后宽限天数
过期后多少天内仍可登录
8
账号过期日期
从 1970-01-01 算起的天数,到期后不能登录
9
保留字段
暂时没用

第二个字段的密码格式:$id$salt$hashed

  • id
     是加密算法:
    • $1$
       = MD5
    • $2a$
       = bcrypt
    • $5$
       = SHA-256
    • $6$
       = SHA-512(现在基本都用这个)
  • salt
     = 盐值,随机生成,相同密码加密出来也不一样
  • hashed
     = 最终的哈希结果

特殊的密码状态:

  • 密码字段是空:用户不需要密码就能登录(非常不安全,不推荐)
  • 密码开头是 ! 或 !!:密码被锁定,用户不能登录(passwd -l 和 usermod -L 就是这么干的)
  • !
     是 usermod -L 加的,!! 是 passwd -l 加的,效果差不多
⚠️ 权限很重要

/etc/shadow 权限必须是 ---------- 或者 -rw-------,只有 root 能读写。如果不小心改成任何人能读,那所有加密密码就泄露了,攻击者可以离线暴力破解。

/etc/group:组信息数据库

所有的组信息都存在这里。格式和 passwd 很像:

$ cat /etc/group | head -5root:x:0:bin:x:1:daemon:x:2:users:x:100:developers:x:1000:alice,bob

四个字段:

字段
含义
1
组名
2
密码占位符(也是 x)
3
GID(组 ID)
4
组成员列表(附加组成员,逗号分隔)

这里容易混淆一个点:用户的主组不在这个列表里。

比如用户 alice 的主组是 developers(GID 1000),你不用把 alice 写到第四个字段里,因为主组信息已经在 /etc/passwd 的 GID 字段里了。第四个字段只放附加组成员。

举个例子:

alice:x:1000:1000:Alice:/home/alice:/bin/bashdevelopers:x:1000:docker:x:987:alice,bob

这里:

  • alice 的主组是 developers(GID 1000)
  • alice 还是 docker 组的附加成员,所以 docker 那行的最后一列写上 alice

/etc/gshadow:组密码存放地

和 /etc/shadow 对应,组密码存在这里。现在基本不用组密码了,但文件还是存在。格式类似,有兴趣可以自己看看,一般不用管它。

/etc/default/useradd:创建用户的默认配置

当你用 useradd 创建用户时,很多默认参数都从这里读:

$ cat /etc/default/useradd# useradd defaults fileGROUP=100HOME=/homeINACTIVE=-1EXPIRE=SHELL=/bin/bashSKEL=/etc/skelCREATE_MAIL_SPOOL=yes

几个重要的:

  • HOME=/home
    :新家目录放在 /home 下面
  • SHELL=/bin/bash
    :默认 shell 是 bash,如果不想让用户登录,创建的时候要自己改
  • SKEL=/etc/skel
    :骨架目录,新建用户时,会把这里的文件复制到新家目录
💡 /etc/skel 是什么?

这就是"骨架目录"。新建用户时,系统会把 /etc/skel 里的所有东西复制到用户新家目录。这里一般放 .bashrc、.profile 这些初始配置文件。如果你想让所有新建用户都默认有某些配置,改 /etc/skel 就行。

/etc/login.defs:登录相关默认配置

这里放一些创建用户时的默认规则:

MAIL_DIR        /var/spool/mailPASS_MAX_DAYS   99999PASS_MIN_DAYS   0PASS_MIN_LEN    5PASS_WARN_AGE   7UID_MIN                  1000UID_MAX                 60000SYS_UID_MIN          201SYS_UID_MAX          999GID_MIN                  1000GID_MAX                 60000SYS_GID_MIN          201SYS_GID_MAX          999CREATE_HOME     yesUMASK           077

重点说几个:

  • UID_MIN 1000
    :普通用户从 1000 开始分配 UID
  • CREATE_HOME yes
    :默认创建家目录(这就是为什么你不加参数也会创建 /home/用户名,不同发行版默认不一样)
  • UMASK 077
    :新建家目录的 umask,077 意味着只有你自己能读,其他人不能看,安全

四个文件的关系总结

画个关系图你就明白了:

/etc/passwd    →  用户基本信息 + 主组 GID/etc/shadow    →  用户加密密码 + 过期信息/etc/group     →  组信息 + 附加组成员/etc/gshadow   →  组密码(基本不用)

它们四个配合起来,就是 Linux 用户组权限的完整信息存储。

💡 不要直接手工改这些文件

虽然你可以用 vi 直接编辑,但推荐用命令改:

  • useradd
    /userdel/usermod 改用户
  • groupadd
    /groupdel/groupmod 改组
  • passwd
     改密码

这些命令会帮你正确更新所有四个文件,不容易出错。

一个真实的生产环境例子

走一遍完整流程,看看权限在实际中怎么用。

比如你接手了一个 Python Web 应用要部署:

  1. 创建专用用户和组
groupadd myappuseradd -m -g myapp myapp
  1. 把代码放到 /var/www/myapp
    ,修改属主:
chown -R myapp:myapp /var/www/myapp
  1. 设置正确的权限
    :
# 目录需要 x 权限才能进入,所以 755find /var/www/myapp -type d -exec chmod 755 {} \;# 普通文件只需要读写,所以 644find /var/www/myapp -type f -exec chmod 644 {} \;# 启动脚本需要执行权限chmod 755 /var/www/myapp/start.sh
  1. 应用需要写日志,创建日志目录,修改权限
    :
mkdir /var/log/myappchown myapp:myapp /var/log/myappchmod 755 /var/log/myapp
  1. nginx 需要读取静态文件,把 nginx 用户加入 myapp 组
    :
usermod -aG myapp nginx
  1. 给组开放读权限就行
    :
chmod g+r /var/www/myapp/static

整个流程下来,权限清晰:

  • myapp
     用户可以读写自己的代码和日志
  • nginx
     组有权限读静态文件,但不能写,安全
  • 其他人什么都干不了

工程实践经验

哪些权限设置是安全的?

场景
推荐权限
理由
普通配置文件
644
属主可改,其他人只读
你的个人脚本
755 或 700
如果不需要别人执行就 700
Web 静态文件
644
nginx 只读就够
应用日志目录
755
应用自己写,别人不能读
你的家目录
700
其他人连看都不能看,安全第一
机密文件(密钥)
600
只有你自己能读写

为什么不要随便用 777?

777 意味着任何人都可以读写执行。等于你把文件放在公共场所,谁都能改。如果攻击者能上传一个文件到 777 目录,他就能直接执行它。

我见过太多项目为了图省事,整个目录都是 777,这是在给黑客开门。

💡 如果遇到 Permission denied 怎么办?

别急着改 777,先问自己三个问题:

  1. 进程是用哪个用户跑的?
  2. 文件的属主属组是谁?
  3. 从根目录到这个文件,每一层目录都有 x 权限吗?

特殊权限位:SUID、SGID、Sticky Bit

这三个不常用,但碰到了要认识:

  • SUID (4xxx)
    :运行程序时,以文件属主的权限运行,不是以执行者的权限。比如 passwd 命令需要改 /etc/shadow,所以设置了 SUID。
  • SGID (2xxx)
    :目录设置 SGID 后,在这个目录下创建的文件,自动继承目录的属组。共享项目的时候很有用。
  • Sticky Bit (1xxx,通常是 /tmp)
    :目录设置后,只有文件属主才能删除自己的文件。 /tmp 人人都能写,但你不能删别人的文件。

一般来说,你不太需要手动设置这些。但看到权限位有四位比如 4755,要知道第一位是特殊权限。

精细化权限:ACL 访问控制列表

传统的 UGO(User/Group/Others)模型只能给一个用户、一个组设置权限。如果你想给多个用户、多个组设置不同权限怎么办?

这就要用 ACL(Access Control List)了。ACL 允许你对任意用户、任意组设置单独的权限,比 UGO 精细得多。

基本用法:setfacl

# 给用户 alice 单独加读权限setfacl -m u:alice:r 文件# 给组 dev 加读写执行权限setfacl -m g:dev:rwx 目录# 给用户 bob 移除所有 ACL 权限setfacl -x u:bob 文件# 移除所有 ACL 规则setfacl -b 文件# 递归给目录下所有文件设置setfacl -R -m g:dev:rw 目录

例子:我们项目叫 myproject,有三个开发:alice、bob、charlie,都在 dev 组。我们还需要让 nginx 能读项目里的静态文件,但不需要给 nginx 用户加进 dev 组。

# 用传统 UGO:属主是项目用户,组是 dev,其他人只读chown myapp:dev /var/www/myprojectchmod 750 /var/www/myproject# 用 ACL 给 nginx 单独开放读权限setfacl -m u:nginx:rx /var/www/myproject

这样:

  • myapp
     属主:可以读写
  • dev
     组成员:可以读写
  • nginx
    :只能读和执行,不能写
  • 其他人:什么都干不了

完美比传统方式要灵活得多。

查看 ACL

getfacl 文件

输出大概是这样:

# file: myproject# owner: myapp# group: devuser::rwxuser:nginx:r-xgroup::r-xmask::r-xother::---

mask 是什么?

ACL 有个 mask 概念。它相当于一个"上限":所有用户和组的 ACL 权限,不能超过 mask。

比如 mask: r-x,就算你给某个用户 rwx,实际也只能拿到 r-x。

mask 会自动计算,不用手动改。如果你手动改了 ACL,记得让系统重新计算 mask:

setfacl -m m:rwx 目录

让 ACL 自动继承到新建文件

给目录加一个默认 ACL,这个目录下新建的文件会自动继承 ACL:

# 设置默认 ACL,所有新文件自动让 nginx 能读setfacl -m d:u:nginx:rx /var/www/myproject

d: 前缀表示"默认规则"。

这个功能在共享项目目录里特别好用。你不用每次新建文件都重新设一遍 ACL,默认规则会自动生效。

ACL 什么时候需要用?

💡 使用场景
  • 多个用户需要不同权限,但不想都放到同一个组里
  • 单个用户需要特殊权限,不用为他专门创建一个组
  • Web 服务器需要读取项目文件,但不需要写入权限
  • 多人协作,每个人权限不同

大多数场景下,传统 UGO 够用了。但碰到上面这些情况,ACL 就是正确的选择。

常见坑

  1. 父目录没有执行权限,ACL 再开也没用。ACL 只是在传统权限之上加规则,不绕过基础权限检查。父目录连 x 权限都没有,ACL 开了也进不去。

  2. 删文件的时候,权限看目录,不看文件。只要目录有 w 权限,不管文件属主是谁,都能删。ACL 可以限制谁能删谁不能删。

  3. 文件系统要支持 ACL。现在绝大多数发行版默认都开了,不用管。老系统可能需要挂载的时候加 acl 选项。

常用命令汇总

命令
作用
setfacl -m u:用户名:权限 文件
添加用户权限
setfacl -m g:组名:权限 文件
添加组权限
setfacl -m d:u:用户名:权限 目录
添加默认规则(新建文件自动继承)
setfacl -x u:用户名 文件
删除用户权限
setfacl -b 文件
删除所有 ACL 规则
getfacl 文件
查看 ACL 规则

sudo 权限管理

如果你想让普通用户能执行一些 root 才能干的活,不要直接把密码给人家,用 sudo。

编辑 /etc/sudoers(用 visudo 命令):

alice ALL=(ALL) /usr/bin/systemctl restart nginx

这样 alice 就能用 sudo systemctl restart nginx 重启 nginx,但不能干别的。

一次真实的线上故障:为什么图片全部 403?

事情是这样的。

那天下午,我部署完一个网站,首页正常,文字内容都有,但所有图片全是裂的。打开浏览器开发者工具一看,所有图片请求全部返回 403 Forbidden。

排查过程

第一步,看 nginx 配置。路径没问题,root 指向正确,location 块也没少。

第二步,看文件权限。ls -l 一看,图片文件全是 644:

$ ls -l /var/www/site/images/-rw-r--r-- 1 root root 204800 Jul  3 14:00 banner.jpg-rw-r--r-- 1 root root 102400 Jul  3 14:00 logo.png

644,属主可读写,其他人只读。nginx 又不是属主,但至少能读吧?为什么还是 403?

第三步,我突然想到一件事——目录权限。往上翻一层:

$ ls -ld /var/www/site/images/drw-r--r-- 2 root root 4096 Jul  3 14:00 images/

看到了吗?drw-r--r--,也就是 744。

为什么 744 的目录,里面的文件读不了?

这就是很多人容易搞混的地方:文件和目录的权限语义不一样。

对于文件:

  • r = 能读内容
  • w = 能改内容
  • x = 能执行

对于目录:

  • r = 能列出目录里有哪些文件(ls 可以)
  • w = 能在目录里增删文件
  • x = 能"进入"这个目录,访问里面的文件

关键就在 x 权限。没有 x 权限的目录,你连进都进不去,更别说读里面的文件了。

回到这个例子:images/ 目录权限是 744,分解一下:

  • 属主(root):rwx(7)—— root 能正常访问,没问题
  • 属组:r--(4)—— 只能 ls 列出文件,不能访问
  • 其他人:r--(4)—— 同上,只能列出,不能访问

nginx 进程跑在 nginx 用户下(或者 www-data),它不是 root,也不在属组里,所以被归类为"其他人"。其他人只有 r 权限,没有 x 权限。

结果就是:nginx 可以 ls 看到目录里有哪些图片,但不能打开这些图片文件。所以返回 403。

✏️ 一个常见的误解

很多人以为:只要文件权限是 644,就一定能被读到。不对。

要访问一个文件,你需要对从根目录到该文件路径上的每一层目录都有 x(执行)权限。任何一层断了,后面的都访问不了。

就像你要进一个房间,你不仅要能打开房间门(文件权限),还得能走进大楼(/)、走进楼层(/var)、走进走廊(/var/www)、走到房间门口(/var/www/site/images)。中间任何一道门锁了,你都进不去。

修复

修起来很简单,给目录加上 x 权限就行:

chmod 755 /var/www/site/images/

或者更精细一点,只给需要的人加:

# 属主 rwx,属组 r-x,其他人 r-xchmod 755 /var/www/site/images/# 或者更保守:属主 rwx,属组 r-x,其他人不给权限chmod 750 /var/www/site/images/

改完之后,图片全部恢复正常。

这次故障教会我的

这次排查让我真正理解了 Linux 权限模型里最容易被忽略的一点:目录的 x 权限。

以前学权限的时候,书上说"目录的 x 权限代表可以进入目录",背是背了,但没真正理解它的实际意义。直到线上图片挂了,自己一步步排查出来,才算真正搞懂。

从那以后,我养成了一个习惯:部署完网站,用 Web 服务器用户的角度去验证一遍。不是用 root 或者你自己的账号,而是用 nginx/www-data 的身份:

# 用 nginx 用户身份测试是否能访问文件sudo -u nginx cat /var/www/site/images/banner.jpg > /dev/null && echo "OK" || echo "FAIL"

如果这个命令返回 FAIL,那浏览器里一定是 403。

另外,我还写了一个检查脚本,专门扫目录权限。核心逻辑就一行:

# 检查从根目录到目标路径的每一层,是否都有 x 权限namei -l /var/www/site/images/banner.jpg

namei 会列出路径中每一层目录的权限,你一眼就能看出哪一层断了。

常见陷阱

陷阱一:忘记父目录需要执行权限

你有一个文件 /home/alice/secret.txt,权限是 777,所有人都能读。但如果你把 /home/alice 权限改成 700,那其他人还是读不了。因为别人进不去你的家目录。

很多人犯这个错:文件权限开了,目录权限没开,还是访问不到。

陷阱二:用了 usermod -G 没加 -a,把用户原来的组全弄没了

这个错误真的太常见了。我刚学 Linux 也犯过。本来只是想把用户加到 docker 组,结果用户原来的组全被覆盖了,导致各种权限错误。

记住:追加组一定要加 -a。

陷阱三:nginx/php-fpm 进程用户不对

部署完网站,老是 Permission denied。一看,nginx 跑在 nginx 用户,文件属主是 root,权限 600,nginx 当然读不了。

正确做法:要么把文件属主改成 nginx,要么把 nginx 加入文件的组。

陷阱四:上传文件后权限不对

如果你用 FTP 上传文件,文件的属主是 FTP 用户,不是 Web 服务器用户。Web 服务器读不到。

这时候需要检查一下 FTP 的 umask 设置,或者上传后统一改一次权限。

陷阱五:家目录权限太开放

很多人把自己家目录设成 755,任何人都能进去看。这本来也没什么,但如果你里面有 .ssh/id_rsa 私钥,权限应该是 600,不然 SSH 会拒绝使用它,因为太不安全了。

陷阱六:锁了密码就以为用户登不进来

passwd -l 锁的是密码,不是账号。如果用户配了 SSH Key,照样能登录。真正想彻底阻止登录,要改 shell 为 /sbin/nologin,或者用 usermod -L 加 usermod -s /sbin/nologin 双保险。

还有,有人用 passwd -l 锁定用户后又去 usermod -U 解锁,结果发现解不了。因为 passwd -l 打的是 !! 标记,usermod -U 只能解 ! 标记。混用两个命令很容易把自己搞晕。

陷阱七:创建服务用户忘记加 nologin

服务用户默认创建出来是可以登录的,如果你忘了指定 nologin 的 shell,等于给攻击者多开了一扇门。创建服务用户的标准写法:

useradd -r -M -s /sbin/nologin 服务名

-r 创建系统用户,-M 不创建家目录,-s 禁止登录。三个一起用。

善意忠告

权限这个东西,说穿了就是一句话:最小权限原则。

给够用的最小权限就够了,不要给更多。你不需要让全世界都能写你的代码,不需要让全世界都能读你的日志。

我刚学 Linux 的时候,也是碰到 Permission denied 就直接 chmod 777,反正能跑就行。后来碰过一次安全问题,才明白权限设计不是没事找事,它是你系统的第一道防线。

真的,花一个小时把这个东西彻底搞懂,比你以后每次出问题都乱试一百次强太多。这是基础中的基础,理解了用户组权限,你才算真正入门 Linux。

最新文章

随机文章