sudo
Run a command as another user, usually root
sudo runs one command as another user, root unless you say otherwise, and asks for your
password. su switches to another account for as long as you keep the shell open, and asks for
that account's password. That difference is why a machine with several administrators uses
the first: nobody has to know the root password, and /var/log/auth.log records which person ran
what.
A fresh Debian install may have neither
su comes from util-linux, which Debian marks required, so it is always there. sudo is a
separate package marked optional. If you gave the installer a root password, it does not install
sudo and does not put your account in the sudo group; if you left the root password empty, it
does both. That single choice, made once and forgotten, is behind most of
sudo: command not found.
Debian's shipped /etc/sudoers grants the privilege through group membership, with one line:
%sudo ALL=(ALL:ALL) ALL. Adding somebody is usermod -aG sudo alice, run as root, and it takes
effect at their next login rather than immediately.
The environment does not come with you
sudo clears the environment and rebuilds a small one. PATH is replaced by the secure_path
setting in /etc/sudoers, so a program you can run cannot always be run under sudo, and
sudo finds /usr/sbin binaries your own shell does not.
Redirection is not part of the command
sudo tee file and sudo sh -c '… > file' exist because of one trap. In sudo cmd > /etc/file
the shell opens /etc/file before sudo has run, and the shell is still you, so the write is
refused and sudo never starts. The error comes from the shell and names the file rather than
sudo or the command.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
the sudo policy these examples run under /etc/sudoers.d is read in lexical order and the last matching rule wins, so the second file below overrides the first for one command and leaves everything else alone. The examples run as user; deploy is a second account with no privileges, so -u and su have somewhere to go that is neither root nor the account already running the command.
==> /etc/sudoers.d/user <==
user ALL=(ALL) NOPASSWD:ALL
==> /etc/sudoers.d/zz-page <==
user ALL=(ALL) PASSWD: /usr/bin/passwd
Running a command as someone else
sudo takes a command, not a shell. Everything after the program name is that program's arguments, and anything the shell would do with them happens before sudo is involved.
Run one command as root
whoami; sudo whoami
The first line is you, the second the identity the command ran with. Only that one command is privileged: the shell you typed it into is unchanged.
Show output
user
root
See the full identity a command runs with
sudo id
Not just the user id. The group list is replaced too, so a command run under sudo is in root's groups and not in yours.
Show output
uid=0(root) gid=0(root) groups=0(root)
Run as some other account
cd /srv/app; sudo -u deploy id -un; sudo -u deploy pwd
-u takes any user, and root is only the default. The working directory is left alone, which matters when the command you are running takes a relative path.
Show output
deploy
/srv/app
Watch a command fail without sudo
ls /root; echo "exit=$?"
Root's home directory is mode 700, so an ordinary account cannot list it. ls exits 2 rather than 1: it distinguishes a serious failure from merely finding nothing.
Show output
ls: cannot open directory '/root': Permission denied
exit=2
Run the same thing with it
sudo ls -A /root
-A includes the dotfiles and leaves out . and .., which on a home directory is most of what there is to see.
Show output
.bashrc
.profile
.ssh
See that the exit status is the command's own
sudo false; echo "exit=$?"
sudo returns what the command returned, so a script can test it as normal. sudo refusing is also exit 1, with a message on stderr, so a script that has to know why it stopped cannot tell the two apart on the status alone.
Show output
exit=1
Refuse to prompt at all
sudo -n whoami
-n fails rather than asking for a password. Use it anywhere a prompt would hang: a cron job, a script running under a service manager, anything with no terminal attached.
Show output
root
See what -n does when a password is needed
sudo -n passwd --status deploy; echo "exit=$?"
The policy on this machine wants a password for passwd and no other command, so -n refuses this one and allowed the last one. A policy is written per command, not per account.
Show output
sudo: a password is required
exit=1
What the environment loses
env_reset is on by default on Debian, so a command under sudo does not inherit your environment. It gets a small one built from the policy instead.
Compare the two PATHs
echo "$PATH"; sudo printenv PATH
The second is secure_path from /etc/sudoers, and nothing you set can change it. This is why sudo finds /usr/sbin programs your own shell does not, and why a command in ~/bin or /opt is suddenly not found.
Show output
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Watch a variable fail to arrive
FOO=bar sudo printenv FOO; echo "exit=$?"
Nothing is printed and printenv exits 1, because FOO was removed on the way through. A script that sets a variable and then calls sudo expecting the command to see it will get no error, just different behaviour.
Show output
exit=1
Pass the environment through anyway
FOO=bar sudo -E printenv FOO
-E keeps what you had, and the policy can forbid it. Use it when a build or a proxy setting has to survive, and not as a habit: the reset exists so that a variable you did not think about, such as LD_PRELOAD, cannot follow a command into root.
Show output
bar
See where HOME points
printenv HOME; sudo printenv HOME
Debian's sudo sets HOME to the target user's, so a command that writes a dotfile writes it into /root. -H asks for exactly this and therefore changes nothing here; it is worth knowing on a system whose policy has been changed.
Show output
/home/user
/root
Compare a login shell with a plain one
cd /srv/app; sudo pwd; sudo -i pwd
-i is the closest thing to logging in as root: it starts a login shell, so the working directory becomes root's home and root's shell startup files run. Without it you stay where you already were.
Show output
/srv/app
/root
Keep the directory but take the shell
cd /srv/app; sudo -s pwd; sudo -s printenv HOME
-s runs your shell as root without the login part, so the directory stays and HOME still moves. Between -i, -s and neither, the question is whose startup files you want to run, and the answer is usually neither.
Show output
/srv/app
/root
Combine -i with another account
cd /srv/app; sudo -u deploy -i pwd; sudo -u deploy pwd
The same distinction applied to a service account: with -i you land in its home and read its profile, without it you are only borrowing its identity.
Show output
/home/deploy
/srv/app
See what a login shell does to PATH
sudo -i printenv PATH
Still secure_path. A login shell would normally build PATH from /etc/login.defs and root's profile, and the sudoers setting wins over both.
Show output
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
The redirect that still runs as you
The most common way sudo appears not to work. The shell sets up redirection before it runs anything, and the shell is not privileged.
Watch a redirect fail
printf 'sudo echo written > /etc/tips.conf\n' > try.sh
bash try.sh
echo "exit=$?"
The error names the file, not echo and not sudo, because sudo never ran: the shell tried to create /etc/tips.conf as user and stopped there. Run from a file here so the message carries a filename, as it would in a script of your own.
Show output
try.sh: line 1: /etc/tips.conf: Permission denied
exit=1
Write the file with tee
echo written | sudo tee /etc/tips.conf
tee does the opening, and tee is the thing running under sudo. It also prints what it wrote, so add > /dev/null when you do not want that echoed back.
Show output
written
Append instead of replacing
echo appended | sudo tee -a /etc/tips.conf
-a is >> to tee's >. Getting this wrong on a config file replaces it, so prefer tee over the shell form even once you know the trap.
Show output
appended
Give the redirect its own shell
sudo sh -c 'echo written > /etc/tips.conf'; sudo cat /etc/tips.conf
The other way round: run a shell under sudo and let that shell do the redirecting. Quote it carefully, because everything inside is expanded by the privileged shell rather than yours.
Show output
written
Finding out what you may do
List your own privileges
sudo -l
The defaults in force and every rule that matches you. Worth running on an unfamiliar machine before assuming either that you can do something or that you cannot.
Show output
Matching Defaults entries for user on deb1:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin, use_pty
User user may run the following commands on deb1:
(ALL) NOPASSWD: ALL
(ALL) PASSWD: /usr/bin/passwd
Try to read the policy directly
grep -c . /etc/sudoers; echo "exit=$?"
Mode 440 and owned by root, so you cannot read the file that decides what you may do. sudo -l exists because of this: it answers the question without handing over the policy.
Show output
grep: /etc/sudoers: Permission denied
exit=2
Read it with the privilege it describes
sudo grep -E "^(Defaults|root|%sudo)" /etc/sudoers
Debian's shipped policy, with the comments stripped out. Four defaults and two grants, and the last line is the one that matters: membership of the sudo group carries the privilege.
Show output
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
Defaults use_pty
root ALL=(ALL:ALL) ALL
%sudo ALL=(ALL:ALL) ALL
See who is in the sudo group
getent group sudo
Empty here, and user can still run sudo, because this machine grants it through a file in /etc/sudoers.d instead. Group membership is the usual route on Debian and not the only one, so ask sudo -l rather than inferring an answer from this.
Show output
sudo:x:27:
Check a sudoers file before it takes effect
sudo visudo -c -f /etc/sudoers.d/zz-page
visudo normally opens an editor and refuses to save a file that does not parse. -c -f runs only the check, for a file you wrote with something else or that a configuration management tool dropped in.
Show output
/etc/sudoers.d/zz-page: parsed OK
See what a typo looks like
printf 'user ALL=(ALL) NOPASWD:ALL\n' > bad.conf
sudo visudo -c -f bad.conf
echo "exit=$?"
NOPASWD is missing its S. A file like this dropped straight into /etc/sudoers.d can lock every account out of sudo at once. That is why the editing tool is the one that checks, and why editing over ssh with no second session open is a bad afternoon waiting to happen.
Show output
bad.conf:1:27: syntax error
user ALL=(ALL) NOPASWD:ALL
^
exit=1
su, and when it is the one you want
su switches account rather than running one command, and it wants the target account's password. The examples below reach it through sudo, because a password prompt is the one thing a page of captured output cannot show you.
Switch to another account with a login shell
sudo su - deploy -c 'pwd; echo "$PATH"'
The - is the entire difference. It makes this a login shell, so the working directory becomes deploy's home and PATH is built from /etc/login.defs and that account's profile: no sbin directories, because those are for administrators.
Show output
/home/deploy
/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games
Switch without the login part
cd /srv/app; sudo su deploy -c 'pwd; echo "$PATH"'
Same account, and everything else inherited from whoever ran it. The directory has not changed and PATH is still the privileged one, so a process running as deploy is searching root's directories. Leaving the - off is the single most common mistake with this command.
Show output
/srv/app
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Confirm where a login shell puts you
sudo su - deploy -c 'whoami; printenv HOME'
The identity and the home directory both moved, and a service account usually needs both before it will run anything: a program that writes to ~ and finds root's home has been started the wrong way.
Show output
deploy
/home/deploy
Become root interactively
su -
What you type on a machine with no sudo, and it asks for the root password. There is no output block because the command opens a shell and waits, so nothing a batch run can capture would be what you see.
See which packages the two come from
dpkg -S $(command -v su) $(command -v sudo)
They come from different packages, and only one of the two is part of every Debian system.
Show output
util-linux: /usr/bin/su
sudo: /usr/bin/sudo
Check which one Debian always installs
dpkg-query -W -f='${Package} ${Priority}\n' util-linux sudo
required means the system will not work without it. optional means a standard install has it and a minimal one may not, so a script that has to run on any Debian machine should stick to su, or to neither.
Show output
sudo optional
util-linux required