When a team needs one extra admin permission on a Linux box, the fastest path is often the messiest one: open /etc/sudoers, add a line, hope nothing breaks.
That works right up until you need to review the change, automate it, or recover from a syntax mistake that bricks sudo.
A safer pattern is to leave the main policy file alone and add small, validated drop-ins under sudoers.d.
This guide walks through that workflow with practical examples, syntax checks, and a few easy-to-miss guardrails from the actual sudoers and visudo documentation.
Why sudoers.d is the better default
The sudoers policy supports an include-directory mechanism, usually via #includedir /etc/sudoers.d. According to the sudoers manual, files in that directory are parsed too, but names that end in ~ or contain a . are skipped.
That makes sudoers.d useful because you can:
- keep the base
/etc/sudoersfile package-friendly - separate app- or team-specific privileges into small files
- validate one candidate rule before installing it
- manage delegated access with configuration management more cleanly
The last point matters a lot. A 3-line drop-in is much easier to audit than a hand-edited global policy file full of historical exceptions.
First, confirm your main file includes the directory
On many Debian and Ubuntu systems, the main file already includes it.
Check with:
sudo grep -nE '^[#@]includedir' /etc/sudoers
You are typically looking for something like:
#includedir /etc/sudoers.d
If you do not see an include directory, stop and review your distro defaults before inventing your own layout.
Rule 1: validate with visudo, not a text editor alone
The visudo manual is very clear about why the tool exists: it locks the file against simultaneous edits and checks syntax before saving.
Even better, it supports check-only mode and an alternate file path, which is exactly what you want for a drop-in workflow.
The two flags to remember are:
-
-cor--checkfor validation -
-for--filefor an alternate file path
A safe pattern looks like this:
cat >/tmp/90-app-maint <<'EOF'
Cmnd_Alias APP_MAINT = /usr/bin/systemctl restart myapp.service, /usr/bin/journalctl -u myapp.service -n 200
%deploy ALL=(root) APP_MAINT
EOF
sudo /usr/sbin/visudo -cf /tmp/90-app-maint
On a valid file, you should see output like:
/tmp/90-app-maint: parsed OK
That is the moment to install it, not before.
Example 1: delegate one service restart and log access
A common real-world need is letting a deployment group restart one service and inspect its recent logs without giving them unrestricted root.
Create a drop-in like this:
sudo install -d -m 0755 /etc/sudoers.d
sudo tee /etc/sudoers.d/90-app-maint >/dev/null <<'EOF'
Cmnd_Alias APP_MAINT = /usr/bin/systemctl restart myapp.service, /usr/bin/journalctl -u myapp.service -n 200
%deploy ALL=(root) APP_MAINT
EOF
sudo chown root:root /etc/sudoers.d/90-app-maint
sudo chmod 0440 /etc/sudoers.d/90-app-maint
sudo /usr/sbin/visudo -cf /etc/sudoers.d/90-app-maint
What this does:
- defines a command alias named
APP_MAINT - allows members of the
deploygroup to run those commands asroot - keeps the permission scope narrow and explicit
To verify the effective access from an allowed account:
sudo -l
If you need to test as a specific user from an admin shell:
sudo -l -U someuser
Example 2: allow package metadata refresh, but not full package installs
Sometimes a user only needs to refresh package metadata or inspect upgrade candidates.
A narrower drop-in might look like this:
sudo tee /etc/sudoers.d/91-apt-audit >/dev/null <<'EOF'
Cmnd_Alias APT_AUDIT = /usr/bin/apt update, /usr/bin/apt list --upgradable
%ops ALL=(root) APT_AUDIT
EOF
sudo chown root:root /etc/sudoers.d/91-apt-audit
sudo chmod 0440 /etc/sudoers.d/91-apt-audit
sudo /usr/sbin/visudo -cf /etc/sudoers.d/91-apt-audit
This is intentionally different from granting full package installation rights.
If you are tempted to add apt install, apt remove, wildcard-heavy command patterns, or shell escapes to the same rule, pause and re-scope it. Small delegated actions are the whole point.
File naming and permission gotchas that bite people
A few details from the manual matter more than they look.
1) Do not put dots in drop-in filenames
Per the sudoers manual, files in an included directory are skipped if the name ends in ~ or contains a ..
Good:
/etc/sudoers.d/90-app-maint
Bad:
/etc/sudoers.d/90-app-maint.conf
/etc/sudoers.d/90-app-maint~
That means editor backup files and “nice-looking” .conf names can silently fail to load.
2) Use root ownership and mode 0440
The sudoers documentation states the default file mode is 0440, readable by owner and group and writable by none. The visudo manual also documents ownership and permission checks in validation mode.
A reliable install pattern is:
sudo chown root:root /etc/sudoers.d/90-app-maint
sudo chmod 0440 /etc/sudoers.d/90-app-maint
3) Validate after writing, not just before
If your automation writes the file and then changes ownership or mode incorrectly, the syntax may still be fine while the policy remains unusable.
So validate the installed path too:
sudo /usr/sbin/visudo -c
According to the visudo manual, check mode against the default sudoers path also checks included files plus ownership and permissions.
A safer automation pattern
If you manage hosts with Ansible, shell scripts, or CI-built images, use a staged file plus validation before the final move.
tmp=$(mktemp)
cat >"$tmp" <<'EOF'
Cmnd_Alias APP_MAINT = /usr/bin/systemctl restart myapp.service, /usr/bin/journalctl -u myapp.service -n 200
%deploy ALL=(root) APP_MAINT
EOF
sudo /usr/sbin/visudo -cf "$tmp"
sudo install -o root -g root -m 0440 "$tmp" /etc/sudoers.d/90-app-maint
sudo /usr/sbin/visudo -c
rm -f "$tmp"
That gives you three useful properties:
- syntax is checked before install
- final permissions are enforced during install
- the complete active policy is checked afterward
What not to do
I would avoid these patterns unless you have a very specific reason:
- editing
/etc/sudoersdirectly for every small exception - granting
ALL=(ALL:ALL) ALLto convenience groups - using wildcards loosely around commands with shell escapes or user-controlled arguments
- storing drop-ins with
.conf,.bak, or editor backup suffixes - skipping a full
visudo -cafter policy changes
If a rule looks “temporarily broad”, it usually becomes permanently broad.
A quick rollback path
If a new drop-in causes confusion, rollback is simple because the change is isolated.
sudo mv /etc/sudoers.d/90-app-maint /root/90-app-maint.disabled
sudo /usr/sbin/visudo -c
That is much less stressful than untangling a large hand-edited main file.
Final thought
sudo policy is one of those things that feels trivial until the day it is not.
Using sudoers.d plus visudo turns it into something modular, reviewable, and a lot less fragile. For Linux admin work, that is usually the difference between “quick fix” and “clean operational habit.”
Sources and references
-
sudoersmanual: https://www.sudo.ws/docs/man/sudoers.man/ -
visudomanual: https://www.sudo.ws/docs/man/visudo.man/ - Debian
sudopackage metadata: https://packages.debian.org/search?keywords=sudo
Top comments (0)