This is Part 8 of the Building a Homelab with AI series. Previously: IaC Lessons Learned | Next: File Permissions Deep Dive
Why Sudoers Matters More Than You Think
Every homelab operator has a moment where they type sudo and pause. “Wait, who else can run this?” In a homelab, the answer is usually “just me.” But “just me” running sudo on seven machines, each with its own sudoers configuration, is how inconsistencies creep in. And inconsistencies are where security gaps hide.
I recently audited the sudoers configuration across my entire homelab fleet — a Proxmox hypervisor, a Docker host, a dev machine, and a handful of service containers. What I found was fine. But the process of checking it properly taught me things I wish I’d known years ago, and I’ve been doing Linux administration for 26 years.
This series is a deep-dive into sudoers configuration, file permissions, and Linux security hardening — written for homelab operators who know their way around a terminal but want to be more deliberate about how they manage privilege escalation.
What sudo Actually Does
Let’s start from first principles, because even experienced admins sometimes have a fuzzy mental model here.
sudo is not “run as root.” It’s “run as another user, subject to policy.” The policy engine is the sudoers file. When you type sudo apt update, here’s what actually happens:
sudoreads/etc/sudoers(and any files in/etc/sudoers.d/)- It checks whether the calling user is authorized to run the specified command as the specified target user (root by default)
- If authorized, it may prompt for the caller’s password (not root’s — this is a common misconception)
- It executes the command with the target user’s privileges
- It logs the event to syslog (typically
/var/log/auth.logon Debian/Ubuntu)
The password prompt is a confirmation mechanism, not an authentication gate. You’ve already authenticated to the system via SSH key or login. The sudo password prompt just confirms “yes, I really mean to escalate privileges.” This distinction matters when we talk about NOPASSWD later.
Anatomy of a Sudoers Rule
A sudoers rule follows this general format:
who where=(as_whom) what
Let’s break down the most common configurations:
The Classic “Full Access”
<user> ALL=(ALL) NOPASSWD: ALL
This reads as: user <user>, on all hosts (ALL), may run commands as any user ((ALL)), without a password (NOPASSWD:), for all commands (ALL).
This is what I use across my homelab. For a single-operator environment where SSH key authentication is the only way in, this is a reasonable choice. The SSH key is the authentication factor. Adding a sudo password on top of that adds friction without meaningfully improving security — an attacker who has your SSH key already owns your user session.
The More Restrictive Version
<user> ALL=(ALL) ALL
Same thing, but requires a password for every sudo invocation (with a configurable timeout — by default, sudo caches the password for 15 minutes). This makes sense in multi-user environments or if you want a speed bump before destructive operations.
Command-Specific Rules
monitoring ALL=(ALL) NOPASSWD: /usr/bin/systemctl status *, /usr/bin/journalctl
This is more surgical: user monitoring can run systemctl status for any service and journalctl, but nothing else. No apt install, no rm -rf /, no passwd. This is principle of least privilege applied to sudo.
For a homelab, command-specific rules are useful for service accounts. If you have a monitoring agent that needs to check service status, giving it full sudo is reckless. Give it exactly the commands it needs.
Group Rules
%sudo ALL=(ALL:ALL) ALL
The % prefix targets a Unix group instead of a user. This rule says “anyone in the sudo group can run any command as any user or group, with a password.” On Debian/Ubuntu systems, this rule ships in the default /etc/sudoers file. It’s why adding a user to the sudo group grants them sudo access.
The Sudoers File Hierarchy
The main sudoers file lives at /etc/sudoers. But editing it directly is an anti-pattern (we’ll cover why in Part 11). Modern best practice is to use drop-in files in /etc/sudoers.d/.
Here’s the hierarchy:
/etc/sudoers # Main file, ships with the OS. Don't touch it.
/etc/sudoers.d/ # Drop-in directory. Your customizations go here.
<user> # Per-user sudo rules
monitoring # Service account rules
ansible-tmp-* # (these shouldn't be here -- see anti-patterns)
The main /etc/sudoers file contains an @includedir directive (or #includedir on older systems — yes, the # is not a comment here, which is one of the more confusing syntax choices in Unix history):
# This is NOT a comment. It includes files from /etc/sudoers.d/
@includedir /etc/sudoers.d
Files in /etc/sudoers.d/ are loaded in lexicographic order. The last matching rule wins, so ordering can matter if you have conflicting rules. Keep it simple: one file per user or role, no conflicts.
File Naming Rules
Files in /etc/sudoers.d/ have restrictions:
- Must not contain a
.(dot) or end in~(tilde) — these are silently ignored - Should be named after the user or role they configure
- No specific extension required (don’t use
.confor.sudoers— while some work, the dot rule makes this fragile)
This trips people up regularly. I’ve seen <user>.conf in /etc/sudoers.d/ that was completely ignored because of the dot in the filename. No error, no warning, just silent failure.
NOPASSWD: The Homelab Debate
The question every homelab operator asks: should I use NOPASSWD?
My position: yes, for single-operator homelabs with SSH key-only authentication.
Here’s the reasoning:
-
Your SSH key is the authentication factor. If an attacker has your private key, they already have shell access as your user. A sudo password only slows them down briefly — and they can likely escalate through other means (kernel exploits, misconfigured services, cron jobs running as root).
-
Automation requires it. Ansible, cron jobs, monitoring scripts — anything that runs without a human at the keyboard needs
NOPASSWDfor the commands it runs. Mixing passworded and passwordless sudo for the same user is fragile and confusing. -
Friction leads to bad habits. When sudo asks for a password every five minutes, people start running
sudo -sand working in a root shell permanently. That’s worse thanNOPASSWD— you lose the audit trail of which commands were elevated.
When NOPASSWD is wrong:
- Multi-user systems where you can’t trust every sudo-capable user equally
- Servers exposed directly to the internet with password-based SSH (you shouldn’t have these, but if you do, NOPASSWD makes compromise trivial)
- Compliance environments where regulatory frameworks mandate password confirmation for privilege escalation
Permissions on Sudoers Files
This is where we get to the audit that kicked off this series. The correct permissions for sudoers files are:
$ ls -la /etc/sudoers.d/<user>
-r--r----- 1 root root 30 Feb 15 18:43 /etc/sudoers.d/<user>
$ stat -c '%a %U:%G' /etc/sudoers.d/<user>
440 root:root
Mode 0440 — read-only for owner (root) and group (root). No write permission for anyone. No access for others.
Owner root:root — only root owns the file. No user should be able to modify their own sudo policy.
This is not optional. sudo itself enforces these permissions. If a sudoers file is writable by anyone other than root, sudo will refuse to parse it. Try it:
# This will break sudo for the affected user:
$ sudo chmod 644 /etc/sudoers.d/<user>
# sudo will now complain:
# /etc/sudoers.d/<user> is world readable
# ...and may ignore the file entirely
The visudo command (discussed in the next section) automatically sets correct permissions. This is one of many reasons to never bypass visudo.
The visudo Imperative
Never edit sudoers files with a regular text editor. Always use visudo:
# Edit the main sudoers file:
$ sudo visudo
# Edit a drop-in file:
$ sudo visudo -f /etc/sudoers.d/<user>
visudo does three critical things:
- Locks the file — prevents concurrent edits that could corrupt the sudoers configuration
- Syntax validates — if you introduce a syntax error,
visudowill warn you and refuse to save. A syntax error in sudoers can lock you out of sudo entirely. - Sets correct permissions — the saved file gets
0440 root:rootautomatically
A syntax error in /etc/sudoers is one of the few things that can make a Linux system effectively unrecoverable without physical console access (or, in our case, pct exec from the Proxmox host). visudo is your safety net.
Validating Without Editing
You can syntax-check a sudoers file without opening it for editing:
$ sudo visudo -cf /etc/sudoers.d/<user>
/etc/sudoers.d/<user>: parsed OK
This is essential for automation. Our Ansible playbook uses it:
- name: Allow admin user passwordless sudo
ansible.builtin.copy:
content: "{{ admin_user }} ALL=(ALL) NOPASSWD: ALL\n"
dest: "/etc/sudoers.d/{{ admin_user }}"
mode: "0440"
validate: "visudo -cf %s"
The validate parameter tells Ansible to run visudo -cf on the file before deploying it. If the syntax is invalid, Ansible aborts the change. You never deploy a broken sudoers file.
Checking Your Own Setup
Here’s a quick audit you can run on any machine:
# 1. Check main sudoers file permissions
$ ls -la /etc/sudoers
-r--r----- 1 root root ... /etc/sudoers
# 2. Check drop-in directory permissions
$ ls -la /etc/sudoers.d/
drwxr-xr-x 2 root root ... .
-r--r----- 1 root root ... <user>
# 3. Validate syntax
$ sudo visudo -c
/etc/sudoers: parsed OK
/etc/sudoers.d/<user>: parsed OK
# 4. See what your current user can do
$ sudo -l
User <user> may run the following commands on dev:
(ALL) NOPASSWD: ALL
# 5. Check for world-readable sudoers files (there should be none)
$ sudo find /etc/sudoers.d/ -perm /004 -type f
# (should return nothing)
If any of those checks surprise you, you have homework to do.
What’s Next
In Part 9, we’ll zoom out from sudoers to Linux file permissions in general — the permission bits that protect every file on your system, including sudoers files. We’ll cover the octal notation, special bits (setuid, setgid, sticky), and the specific permission patterns that matter for homelab security.
Then in Part 10, we’ll look at how to automate all of this with Ansible so you never have to manually configure sudoers again. And in Part 11, we’ll collect the anti-patterns and war stories — the mistakes that break things at 3 AM.