You need a Linux machine where you can use sudo, and
you should not practise on a server anyone depends on. The commands
here change real system state. A spare machine, a virtual machine, or a
cloud instance you can rebuild is the right place; a laptop you own is
fine too. If the terminal itself is new to you, do
Introduction to the
Linux Terminal first — it takes about ten minutes and everything
below assumes it. Every step tells you how to undo it.
What sudo Is Actually For
sudo exists so that nobody has to log in as root. Each administrator keeps their
own account and password, elevates only for the commands that need it, and every elevation is
logged against a real person. Shared root passwords give you none of that: no attribution, no
revocation without changing it for everyone, no audit trail.
Start by seeing what you can already do:
sudo -l
This lists the rules that apply to you. It is also the first thing an attacker runs after compromising an account, for exactly the same reason.
Always Edit With visudo
/etc/sudoers in a plain editor.
A syntax error there can make sudo refuse to run at all — and if root
login is disabled, you have locked yourself out of administering the machine.
visudo checks the syntax before saving and refuses to write a broken file.
That check is the entire point.
sudo visudo # the main file
sudo visudo -f /etc/sudoers.d/deploy # a drop-in
sudo visudo -c # check the current configuration
Prefer drop-in files under /etc/sudoers.d/ over editing the main
file. They survive package upgrades, are easy to review, and can be removed cleanly. Note that
files with a dot in the name are ignored — name it deploy, not
deploy.conf.
Keep a second root session open while changing sudo rules, so a mistake is an inconvenience rather than a lockout.
Reading a Rule
alice ALL=(ALL:ALL) ALL
Four parts, left to right:
alice— who the rule applies to. A leading%means a group (%sudo,%wheel)ALL=— on which hosts. Almost alwaysALLon a single machine(ALL:ALL)— which users and groups they may run as- final
ALL— which commands. This is where least privilege lives
Granting a specific, limited capability:
%webadmin ALL=(root) /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx
Always use absolute paths. A bare command name can be satisfied by anything
earlier in the user's PATH.
Rules That Look Restrictive But Are Not
This is the part that matters most, and it is genuinely counter-intuitive: many narrow-looking grants are equivalent to full root.
- Any editor —
sudo vim,nano,lessandmorecan all spawn a shell from inside. Granting one grants root. Usesudoeditinstead - Any interpreter —
python,perl,awk,find(via-exec) all execute arbitrary code - Wildcards in arguments —
/bin/chown * /srvlets the user supply arguments you did not anticipate. Wildcards in sudoers are far weaker than they look - A writable script — if the user can edit the script they are allowed to run as root, they can run anything as root. Check the script's own permissions
systemctlwithout a specific unit — generalsystemctlaccess allows starting a unit the user controls
sudoedit for files, never sudo <editor>.
sudoedit /etc/nginx/nginx.conf copies the file to a temporary location,
opens it as your unprivileged user, and copies it back on save. The editor never
runs as root, so there is no shell to escape into. Grant it as
sudoedit /etc/nginx/*.
NOPASSWD, and When It Is Defensible
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl reload nginx
NOPASSWD removes the password prompt. It is legitimate for automation — a
deploy account cannot type a password — but it means anyone who obtains that account's shell
gets that capability instantly, with no second factor.
- Never
NOPASSWD: ALLfor a human account. That is a root login with extra steps - Scope it to exact commands with absolute paths and no wildcards
- Pair it with a locked-down account — key-only SSH, no interactive shell where possible
Useful Defaults
Defaults timestamp_timeout=5
Defaults logfile="/var/log/sudo.log"
Defaults passwd_tries=3
Defaults lecture=once
Defaults:deploy !requiretty
timestamp_timeout— minutes before sudo asks again.0means every single time; a low number is a good balance on a shared machinelogfile— a dedicated sudo log alongside the syslog entriesenv_reset— on by default and should stay that way; it strips the caller's environment so variables likeLD_PRELOADcannot follow you into a root process
Auditing
sudo -l -U alice # what alice may do
sudo grep sudo /var/log/auth.log | tail # recent sudo activity (Debian/Ubuntu)
journalctl _COMM=sudo -n 50 # the same via journald
getent group sudo # who is in the sudo group
Review group membership as carefully as the rules themselves. Most real over-privilege comes
from an account quietly sitting in sudo or wheel, not from an exotic
sudoers line.
Now Do It Yourself: Five Steps
A sudo rule that parses is not the same as a sudo rule that is safe. You will
write rules, break them on purpose to learn the error format, and then see a rule
that passes every check while quietly granting full root. Steps 1 to 4 need no
sudo at all — you can do them on any machine, because
visudo will check a file you own.
Go: open a terminal.
Do: run sudo -l. It will ask for your password.
You should see: a Matching Defaults entries block listing settings such as env_reset and timestamp_timeout, then the commands you may run. env_reset is a security control worth noticing: it strips your environment variables before running the command, so you cannot smuggle in a hostile PATH.
If not: user is not allowed to run sudo means your account is not in the administrators group — on Debian and Ubuntu that is sudo, on Red Hat-family systems wheel. Steps 2 to 4 still work regardless, because they only validate files.
Go: run cd, then mkdir -p practice and cd practice. You are writing an ordinary file, not touching the real configuration.
Do: run these two commands.
printf 'alice ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx\n' > ok.rule
visudo -c -f ok.rule
You should see: ok.rule: parsed OK. Read the rule as five parts: who (alice), on which hosts (ALL), as whom they may run ((ALL)), whether a password is required (NOPASSWD), and exactly which command.
If not: visudo: command not found means it is in /usr/sbin, which is not on a normal user's path — run /usr/sbin/visudo -c -f ok.rule. 🔴 Never edit the real sudoers file with an ordinary editor. Use sudo visudo, which validates before saving. A syntax error in that file can lock every administrator out of the machine, and fixing it then needs a rescue boot.
Go: same folder.
Do: run these two commands. The colon after NOPASSWD is missing.
printf 'alice ALL=(ALL) NOPASSWD /usr/bin/systemctl\n' > bad.rule
visudo -c -f bad.rule
You should see: bad.rule:1:44: syntax error, the offending line printed back, and a ^ underneath pointing at the position. The format is file:line:column.
If not: note carefully where the caret points — the end of the line, not at the missing colon. A parser reports where it gave up, not where you went wrong, and those are rarely the same place. The same end-of-line caret appears if you misspell the keyword as NOPASWD, which is why reading the whole line yourself beats trusting the pointer.
Go: same folder.
Do: run these two commands. The % is what marks a group.
printf 'Cmnd_Alias SERVICES = /usr/bin/systemctl start nginx, /usr/bin/systemctl stop nginx\n%%developers ALL=(ALL) NOPASSWD: SERVICES\n' > group.rule
visudo -c -f group.rule
You should see: group.rule: parsed OK. Granting to a group rather than to named people is what stops sudoers rotting — when someone joins or leaves you change their group membership, and no rule needs touching.
If not: if you write the group without % it is read as a username, so the rule silently applies to nobody and never fires. That failure is invisible: the file parses, sudo simply does not match. Confirm a rule actually applies with sudo -l -U username.
Go: same folder. This is the step that matters.
Do: run these two commands.
printf 'alice ALL=(ALL) NOPASSWD: /usr/bin/find\n' > danger.rule
visudo -c -f danger.rule
You should see: danger.rule: parsed OK — completely valid. And yet it is equivalent to giving alice unrestricted root, because find has an -exec option that runs any command she names. The same is true of editors, awk, tar, less and anything else that can shell out or write arbitrary files.
If not: there is nothing to fix here — the point is that the validator checks grammar, never consequences. Before granting any command, ask what else it can be made to do. Prefer whole-command paths with fixed arguments, never a wildcard, and never a program that can spawn another. When in doubt, look the program up on GTFOBins, a public catalogue of exactly these escapes.
Without scrolling up: visudo -c says parsed OK. What has it
actually told you, and what has it not? Answer: that the grammar is valid. It says
nothing at all about whether the rule is safe — a rule granting
find, vi or tar parses perfectly and hands over
full root.
Now do it without the page: write a rule letting the
developers group restart one named service without a password, check it
parses, then argue with yourself about whether that command could be turned into
something wider. Then check the real file's permissions with
ls -l /etc/sudoers — it is 440, readable only by root
and its group, and sudo refuses to run at all if that is loosened.
Summary
- Always
visudo, and prefer drop-ins in/etc/sudoers.d/(no dots in the filename) - Keep a second root session open while editing rules
- Editors and interpreters mean full root — use
sudoedit - Absolute paths, no wildcards, and check that granted scripts are not user-writable
NOPASSWDonly for automation, scoped to exact commands- Audit group membership — that is where over-privilege usually hides
Run getent group sudo and sudo -l. If either lists an account
or a capability you would not deliberately grant today, you have found the highest-value
fix on the machine.