sudo 大概是 Linux 里出现频率最高的命令之一,但我发现很多人从一开始就被“sudo 能提权”这句话带偏了:要么以为 sudo 就是“临时变成 root”,要么在遇到sudo: command not found、user is not in the sudoers file这类报错时直接懵圈。加上这几年服务器系统版本迭代快,CentOS 10、Ubuntu 24.04 甚至国产发行版都开始调整默认用户组和 sudo 行为,这块知识点越来越值得花点时间系统捋一遍。
这篇文章不打算写成 man 手册的翻译。我会从实际运维和日常使用的角度出发,把 sudo 的原理、常见用法、sudoers 配置、高频报错排查以及面试里经常被追问的细节全部串一遍。不管你是在虚拟机上折腾 Linux 的新手,还是负责几十台服务器的运维,只要还在跟 Linux 打交道,这篇内容都能直接用上。
1. sudo 的定位:它不是“切换 root”,而是“按规则以更高权限执行”
1.1 sudo 到底是怎么工作的
很多人以为 sudo 就是把当前用户切换成 root,然后执行后面的命令。这个理解不算全错,但差得很远。sudo 的设计初衷是:普通用户在自己账号下,经过授权后,以指定用户(通常是 root)的身份去执行某一条命令。它不是一个登录行为,而是一个“执行命令前临时提升权限”的机制。
整个流程是这样的:
- 你在终端输入
sudo command。 - sudo 读取
/etc/sudoers文件,检查当前用户是否被允许执行这条命令。 - 如果策略允许,sudo 会要求你输入当前用户自己的密码(不是 root 密码)。
- 密码校验通过后,sudo 在短期内(默认 15 分钟)记录一次“已认证”状态,接下来的 sudo 操作不再重复输密码。
- sudo 以目标用户的 UID/GID 去执行命令,同时把命令和结果写到审计日志里。
注意第二步,sudo 绝不是“任何人输个密码就能 root”,它首先要过 sudoers 这道授权关。这也是为什么新装的 Ubuntu 默认用户能 sudo,而很多 VPS 初始用户却提示 “not in the sudoers file”——前者安装时被放进了sudo组,后者没有被授权。
我在实际工作中见过不少新同事在这点上栽跟头:以为拿到了 root 密码就等于想执行什么就执行什么,结果发现sudo -i直接提示没有权限。说句实话,root 密码只在登录场景有用,日常操作权限反而掌握在 sudoers 这个文件手里。
1.2 su、sudo -i、sudo -s、sudo su 到底有什么区别
这个是面试高频题,也是很多人实际使用时最容易迷糊的地方。我先把四者的区别用一张表说清楚:
| 命令 | 作用 | 是否加载目标用户环境变量 | 是否需要目标用户密码 |
|---|---|---|---|
su - | 完整切换到 root,并加载 root 的环境配置 | 是 | 需要 root 密码 |
su root | 切换到 root,但保留当前 shell 的部分环境变量 | 否 | 需要 root 密码 |
sudo -i | 以 root 身份启动一个登录 shell | 是 | 需要当前用户密码 |
sudo -s | 以 root 身份启动一个 shell,但不切换当前工作目录和部分环境 | 否 | 需要当前用户密码 |
sudo su | sudo 校验后调用 su,实现“免 root 密码进入 root shell” | 取决于是否加- | 需要当前用户密码 |
个人判断:在 CentOS/RHEL 系服务器上,我几乎只用sudo -i来进入 root 环境,因为sudo -s虽然也能提权,但它不会加载 root 的~/.bashrc和~/.bash_profile,经常出现 PATH 不完整、alias 没生效的情况。举个例子,源码编译安装的软件目录在/usr/local/nginx/sbin,如果执行sudo -s后想直接敲nginx -t,大概率会提示命令找不到,反而让人误以为是安装出错了。
至于sudo su,这算是个“历史遗留技巧”:以前有些发行版不允许 SSH 直接以 root 登录,但运维又需要长期 root 环境,于是用sudo su绕一下。现在大部分环境下我更推荐用sudo -i,行为更明确,也不会出现重复加载环境导致 PATH 混乱的问题。
1.3 setuid:sudo 凭空多出的执行权限从哪里来
如果去查看 sudo 命令的权限位,你会发现它和普通程序不太一样:
$ ls -l /usr/bin/sudo ---s--x--x 1 root root 232416 4月 20 /usr/bin/sudo那个醒目的s位就是 setuid 位。它的作用是:任何用户执行这个二进制文件时,进程的属主自动切换成文件属主(root)。这是 sudo 能拿到高权限的根本机制。
有了这层理解,再去看网上说的“Linux 提权”就好懂了。所谓提权,本质上就是攻击者想办法让某个进程以 root 身份启动,再去复用或者滥用这个进程的能力。而 sudo 本身就带 setuid,一旦 sudoers 配置不当,比如给普通用户放开了ALL权限但没限制命令,那这个用户实际上离 root 只有一步之遥。这一点在后面的安全章节我会再展开。
2. 高频使用场景拆解:从切换身份到复合命令执行
2.1 单条命令、多条命令与交互式 shell 的组合技巧
基本用法是sudo 命令,这个不需要多讲。真正有讲究的问题是:想执行多条命令时,sudo 应该放在哪里?
先看一个常见的错误写法:
sudo cd /etc/nginx && sudo vim nginx.conf这里sudo cd大概率会失败,因为 sudo 执行完 cd 后,进程就退出了,当前 shell 的目录根本没变。此时正确做法是:
sudo -i cd /etc/nginx vim nginx.conf或者不想进入交互 shell,就用sudo sh -c把多条命令包起来:
sudo sh -c 'cd /etc/nginx && vim nginx.conf'再补充一个高频操作:查看只有 root 才能读的文件,比如/etc/shadow,直接sudo cat /etc/shadow就行。如果是修改,则建议用sudo -e /etc/nginx/nginx.conf。sudo -e会调用 sudoers 里定义的编辑器打开文件,保存时以提权身份写回,比sudo vim更安全,也避免很多编辑器临时文件权限问题。
2.2 管道和重定向的权限陷阱:sudo echo 为什么也会失败
这是我认为 sudo 使用里最经典、踩坑率最高的问题。看下面这条命令:
sudo echo "nameserver 114.114.114.114" > /etc/resolv.conf执行后终端会直接告诉你Permission denied。为什么?原因是:sudo 只提升了 echo 这个命令的权限,而>重定向是当前 shell 自己实现的,shell 由普通用户启动,所以它去打开 /etc/resolv.conf 时用的还是普通用户权限。换句话说,sudo 只管了 echo,没管重定向这一步。
解决方式有三种,按实际偏好排序:
# 方式一:把整个复合命令交给 sudo 执行 sudo sh -c 'echo "nameserver 114.114.114.114" > /etc/resolv.conf' # 方式二:用 tee 接收管道内容,让 tee 以 root 身份写文件 echo "nameserver 114.114.114.114" | sudo tee /etc/resolv.conf # 方式三:追加内容时用 tee -a echo "# new dns" | sudo tee -a /etc/resolv.conf再说一下sudo sh -c这个语法,它也是热搜词里出现过的sudo sh 是什么意思的答案:sudo sh -c的本质是让 sudo 以 root 身份去运行sh -c,而sh -c '字符串'的意思是启动一个 shell 并执行整段字符串里的命令。这样管道、重定向、分号拼接就都发生在 root shell 内部,自然不存在权限不够的问题。
那有人会问:sudo -s之后再执行这些不也一样吗?倒也确实,如果你已经在sudo -i的 root shell 里,就不存在这些纠结了。这也侧面说明了为什么运维人员经常两条路径都要掌握:单条命令用 sudo,复杂复合命令直接进 root shell。
2.3 保留环境变量:sudo -E 与 docker exec 的配合
sudo 默认会把环境变量清空,只保留一小部分白名单里的变量,比如HOME、LOGNAME、PATH等。这意味着你当前 shell 里 export 的一个变量,在 sudo 执行时是拿不到的。
我遇到比较多的场景是 Docker 命令和 GUI 程序。比如 WSL2 里折腾运行 Linux 图形程序时,热搜词里有这样一条命令:
sudo docker exec -it --env="DISPLAY=$DISPLAY" --env="QT_X11_NO_MITSHM=1" 容器名 bash问题在于如果不加额外处理,sudo 会把DISPLAY变量清掉,导致容器里程序找不到 X server。解决方法是加-E参数保留环境变量:
sudo -E docker exec -it --env="DISPLAY=$DISPLAY" --env="QT_X11_NO_MITSHM=1" 容器名 bash或者让 sudo 在配置层面放行该变量,在/etc/sudoers里加:
Defaults env_keep += "DISPLAY QT_X11_NO_MITSHM"顺便说一句,用sudo -E时如果还要覆盖某一两个变量,可以在命令里临时指定,写作:
sudo -E VAR=value command这种写法我在 CI/CD 脚本里用得比较多,比每次依赖全局配置更可控。
2.4 系统状态类命令为什么离不开 sudo:iftop、tcpdump 等
很多人刚接触 iftop、tcpdump 时有个困惑:明明已经用 root 账户执行了,为什么还提示权限不足?这类工具不是普通程序,它们需要监听网卡、读取系统底层状态,对进程权限有额外要求。
拿 iftop 举例。它要抓取所有网卡流量,这需要进程具备CAP_NET_RAW或CAP_NET_ADMIN能力。普通用户即使能运行,也拿不到数据;某些环境下即便 root 也要确认是否在受限容器里。所以最标准的用法就是sudo iftop。如果你敲sudo iftop后提示command not found,那不是权限问题,而是这个包没装:CentOS/RHEL 上需要启用 EPEL 源后yum install iftop,Ubuntu/Debian 用apt install iftop。
同理,sudo tcpdump -i eth0、sudo ethtool eth1、sudo htop也都是这类场景。我一般会跟新人说:看到“需要读取系统级信息”的工具,先默认用 sudo 跑一次,如果报错再检查环境和包,这样排查路径会短很多。
3. 配置才是关键:手把手看懂 /etc/sudoers
3.1 sudoers 的基本格式:每行都写了什么
/etc/sudoers是 sudo 的核心策略文件。绝大多数发行版都要求用visudo命令来编辑它,因为 visudo 会在保存前检查语法,写错了还能避免把 sudo 整个弄挂。sudoers 文件每行最基本的格式是:
用户名 主机名=(可切换的用户:可切换的组) 命令拆开看一个真实例子:
jds ALL=(ALL:ALL) ALL含义是:用户 jds 可以在所有主机上,以任意用户和任意组的身份,执行任意命令。
再看一个带组匹配的写法:
%wheel ALL=(ALL) ALL%开头表示用户组,意思是 wheel 组的所有成员可以执行任意命令。这也是 CentOS/RHEL 默认的授权方式。Ubuntu/Debian 则默认用sudo组:
%sudo ALL=(ALL:ALL) ALL所以如果你在 Ubuntu 上看到 “user is not in the sudoers file” 的报错,本质上就是因为当前用户没有被加入sudo组,或者没有在 sudoers 里有对应条目。解决办法是用 root 登录后执行:
usermod -aG sudo 用户名如果是 CentOS/RHEL 系,把sudo换成wheel:
usermod -aG wheel 用户名顺带提醒一下,有些国产 Linux 发行版因为基于 CentOS 或 openEuler 的底子,组名也可能叫wheel,实际操作前先看/etc/sudoers里默认授权的是哪个组,再动手加人才不会出错。
3.2 按需给授权:NOPASSWD 与命令白名单
在实际运维里,默认的ALL=(ALL) ALL往往不够用。比如有一台跑着 web 服务的服务器,开发人员频繁需要 reload nginx,但他们不应该有修改系统配置的权限。这时可以在/etc/sudoers.d/目录下新建一个文件(这个目录天生就是给这类零散配置用的),写入:
dev1 ALL=(root) NOPASSWD: /usr/sbin/nginx -s reload, /usr/bin/systemctl reload nginx这里的NOPASSWD:标签表示执行这些命令时不需要输入密码。注意,我只给了 nginx reload 和 systemctl reload 两条命令,其他的还是需要密码甚至没有权限。这种最小化授权在多人协作的服务器上非常实用。
再举一个自动化脚本场景:你写了个备份脚本,每天凌晨要用 root 权限打包/var/www,又不想把 root 密码写在脚本里。可以在 sudoers 里配置成:
backupuser ALL=(root) NOPASSWD: /usr/local/scripts/backup.sh注意这里配的是脚本的绝对路径,并且脚本本身要设置好权限,别让其他低权限用户能改脚本内容,否则等于把 root 权限送出去了。
3.3 别名和默认项:让 sudoers 更清晰更可控
如果服务器用户很多、命令很多,逐行写在 sudoers 里会非常乱。这时候可以用别名:
User_Alias ADMINS = user1, user2, user3 Cmnd_Alias WEB_CMD = /usr/bin/systemctl restart nginx, /usr/bin/systemctl reload nginx Host_Alias WEB_SERVERS = web01, web02 ADMINS WEB_SERVERS=(root) WEB_CMD这个写法的可读性明显更高,而且后续加人加命令只需要改别名那一行。
另一个值得一提的配置是Defaults。它控制 sudo 的默认行为,我经常用到的是这些:
Defaults timestamp_timeout=10 Defaults tty_tickets Defaults env_keep += "HTTP_PROXY HTTPS_PROXY"timestamp_timeout=10:认证有效时间改成 10 分钟,超过又要重新输密码。安全要求高的环境甚至可以改成0,也就是每次 sudo 都输密码。tty_tickets:每个终端单独计一次认证。这个很有用,否则在终端 A 验证过 sudo,终端 B 也跟着免密了,有安全洁癖的人接受不了。env_keep:白名单放行某些环境变量。
这部分建议不要贪多,先理解场景再配置,因为 sudoers 写错了轻则功能异常,重则 sudo 直接不可用。
4. 高频报错与排查:把热搜里的坑都过一遍
4.1 sudo: add-apt-repository: command not found 的根源
这个报错在 Ubuntu/Debian 系的提问里特别多。add-apt-repository本身不是 shell 内置命令,它来自软件包software-properties-common。如果你的系统没装这个包,即使权限再高也找不到命令。
排查和解决其实很简单:
# 确认命令到底在不在 type -a add-apt-repository # 不在就安装对应包 sudo apt update sudo apt install software-properties-common不过我还想多说一句,这类 “command not found” 的问题,排查思路是相通的。先which 命令或者type 命令看路径,再看是不是 PATH 没包含,最后才怀疑包没装。我见过不少人折腾半天,最后发现只是某个工具的二进制在/usr/local/sbin,而当前用户的 PATH 里根本没有这个目录。sudo 环境下尤其明显,因为 sudo 默认使用secure_path,只认/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这几个目录。
如果你的程序安装在其他路径,比如编译安装的/opt/nginx/sbin,在 sudo 执行时大概率会提示找不到。解决办法是编辑 sudoers,加入:
Defaults secure_path = /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/nginx/sbin或者以后都用绝对路径执行。
4.2 user is not in the sudoers file:如何给用户开通 sudo 权限
这个报错我在 3.1 里讲过原因。处理办法除了刚才提到的usermod -aG sudo 用户名,还有一个更直接的方法:以 root 身份登录系统,然后执行visudo,在文件里添加一行:
用户名 ALL=(ALL) ALL但“新增行”的方式可读性和可维护性都比较差,我更喜欢用人组管理的方式:把用户加进sudo或wheel组。这样以后收回权限只要从组里移除用户就行,不会在 sudoers 里堆一堆用户名。
这里有一个安全建议:如果只是在个人测试机上玩,怎么方便怎么来可以;但在生产服务器上,我强烈建议大家不要随意给普通开发账号配置ALL=(ALL) ALL。很多攻击事件里,攻击者一开始只是拿到了一个低权限账号,就是因为 sudoers 里账号权限给得太宽,最终直接提权成了 root。
4.3 已在 root 或 sudo 下,却依然没有写入权限
这个问题经常出现在文件系统挂载参数或者 SELinux 相关的场景中。比如你执行:
sudo touch /mnt/data/test.txt结果提示只读文件系统,这时候提权也没用,因为问题出在挂载选项上,需要重新挂载:
mount -o remount,rw /mnt/data还有一种情况是 SELinux 拦住了进程对文件的访问,即使 root 也未必能绕过。排查时可以看/var/log/messages或/var/log/audit/audit.log,确认是不是 denied 日志。然后根据情况调整上下文或放行策略。
这种“明明 sudo 了还是没权限”的问题,要求我们记住一个原则:sudo 只是换了用户身份,它不能突破文件系统挂载模式、不能直接绕过 SELinux/AppArmor,更不能让一条根本不该执行成功的命令变成成功。遇到权限问题先从“身份、文件、挂载、内核安全模块”四个方向排查,思路会清晰得多。
4.4 sudo 后环境变量被清空导致程序行为异常
我在 2.3 提过sudo -E和env_keep。实际工作中还遇到过一种隐蔽情况:某程序在普通用户下运行正常,加了 sudo 后连中文都显示乱码,或者连不上自定义代理。十有八九就是环境变量 LG_ALL / LANG / HTTP_PROXY 没被保留。
解决方式也很简单:
sudo -E command如果你想确认 sudo 环境下到底有哪些变量,可以执行:
sudo env | grep -E "LANG|PROXY|PATH"看看缺什么,再决定是加-E还是写进 sudoers 的env_keep。
5. 从面试到实战:把 sudo 理解成一套权限体系
5.1 面试常问的 sudo 概念与我的标准回答
结合这些年带人、帮人改简历和做面试辅导的经验,跟 sudo 相关的面试题基本绕不开以下几类:
Q1:sudo 和 su 有什么区别?
我会先讲权限模型:su 直接切换用户,需要目标用户密码;sudo 是基于策略授权,只需当前用户密码,并且可以通过 sudoers 精细限制“谁能做什么”。然后补一句:sudo 有审计日志,su 要配合 history 等才能追溯,整体安全性上 sudo 更适合多人服务器。
Q2:为什么sudo echo 1 > /etc/file会提示 Permission denied?
原因就是我 2.2 写的重定向权限问题。面试中问这个问题,主要考察候选人是否理解 shell 重定向发生在用户态进程里,而不是在 sudo 的子进程里。能回答到“>是当前 shell 执行的,不受 sudo 影响”这一步,已经算过关。
Q3:sudo 默认的认证有效期是多久?如何修改?
默认是 15 分钟,通过Defaults timestamp_timeout调整。这里我通常会补一句tty_tickets的作用,显得真实做过配置,而不是只背了概念。
Q4:NOPASSWD 有什么风险?
风险不是“不输密码”本身,而是把高风险命令也 NOPASSWD 之后,等于任何能接触这个用户的攻击者都直接拥有了这些命令的 root 权限。尤其是sudo NOPASSWD: ALL,基本等同于把 root 钥匙挂在门上。所以我的建议是:自动化脚本里可以 NOPASSWD 具体的几条命令,绝不对整个用户组放开。
Q5:sudo 提权在 CTF/攻防里常见的利用点有哪些?
这类问题要小心回答,不能给出实际攻击教程。我的回答角度是:sudo 提权漏洞大多源于两个方面,一是 sudoers 配置过度放权,二是 sudo 程序本身或系统内核存在可利用漏洞。作为防守方,我们要做的是定期审计 sudoers、及时打补丁,不要轻易允许普通用户执行可以加载自定义配置的程序。
5.2 我在实际运维中坚持的 5 条 sudo 安全习惯
聊完面试,最后给大家交个底:前面那些理论如果落不到实际习惯上,价值有限。这几条是我在服务器上踩坑之后沉淀下来的规矩:
能不给 ALL 就不给 ALL。普通开发账号给到具体的服务管理命令即可,比如 reload、restart。真正需要 root 环境的人,单独给 wheel 组并做好登记。
sudoers 修改必须走 visudo。写错一行可能导致所有用户 sudo 失效,visudo 的语法检查虽然基础,但能救命。
分配账号后顺手加过期时间。用
usermod -e 2026-01-01 用户名等方式,哪怕只是内部测试机,也能避免一堆“僵尸账号”攒着权限。善用
sudo -l自查。我要求团队成员在拿到新账号后第一件事就是执行sudo -l,看看自己到底被授权了什么命令。这不仅方便排查问题,也能及时发现管理员误配的过度权限。关键操作先
sudo -i再执行。批量操作、脚本操作我都是先进 root shell,因为交互式环境下的 PATH 和完善的 root 配置能减少超过一半的“命令找不到”和“环境变量异常”。但这个做法因人而异,如果团队安全审计比较严格,你可能需要反过来要求所有人一律用最小授权命令。
我在实际工作中最推荐的组合是:日常单条操作用sudo,批量操作或排查复杂问题时用sudo -i,需要给团队开放权限时优先修改/etc/sudoers.d/下的文件而不是直接动主配置。这样既灵活又有迹可查。
最后再分享一个小技巧:如果你不确定一条命令在 sudo 之后会发生什么,可以先执行sudo -l 命令或者sudo /bin/bash -c 'command'试运行,必要时配合echo $?确认退出码。很多看似玄学的权限问题,其实只是因为你少验证了一步。