Module 15: Security Basics — sudo, Firewall, and Hardening
🎯 Goal: Apply practical least-privilege defaults before a Linux machine or service becomes reachable by others.
By the end of this module, you should be able to explain what sudo changes, inspect rather than casually edit privilege rules, expose only intentional network ports, update packages responsibly, and use a short hardening checklist for a learning server.
Why this matters
Everything you've learned so far - users and permissions, package management, networking, SSH, logging - comes together here into the practical question every operator eventually faces: is this machine reasonably safe to expose, and can I prove it? You don't need to become a security specialist to avoid the most common, avoidable mistakes: overly broad sudo access, an open port nobody remembers enabling, or a root account reachable over SSH. This module closes out the Linux track with the fundamentals that matter before you start running Docker containers and Kubernetes clusters that expose services to networks.
Concepts
Security is a series of narrow decisions
Good hardening is not a single command. It is deciding which user needs elevated access, which process must listen on which interface and port, which credentials are permitted, and how you will notice a change. Start restrictive, make one deliberate exception at a time, verify it, and keep a recovery path so you do not lock yourself out of a remote machine.
Recap: sudo from module 04, and why it exists. You already know sudo lets a permitted user run a command as another user (usually root) without logging in as that user directly. The deeper reason this matters is the principle of least privilege: give an account only the access it needs to do its job, no more. A user who can run any command as root via sudo is, for practical purposes, root - so the real security question isn't "does this user have sudo" but "exactly what can this user do with sudo, and is that the minimum necessary."
/etc/sudoers and why you don't edit it directly. The file controlling who can run what via sudo is /etc/sudoers. It's deliberately edited only through the visudo command, never with a regular text editor. The reason is safety: a syntax error in /etc/sudoers can lock every non-root user out of using sudo at all, which is exactly the kind of self-inflicted lockout you want to avoid. visudo opens the file in your editor but validates the syntax before saving, refusing to save (and warning you) if you've introduced an error, so you get a chance to fix it before it takes effect.
/etc/sudoers.d/ for modular configuration. Rather than editing the main /etc/sudoers file directly (even via visudo), the common practice is to drop small, focused configuration snippets into /etc/sudoers.d/, one file per purpose (for example, granting one specific user permission to run one specific command without a password). This keeps changes isolated, easier to review, and easier to remove cleanly later, instead of hunting through one large shared file. Files here are also edited via visudo -f <path> for the same syntax-safety reason.
Least privilege in sudo rules, concretely. A sudoers rule can be as broad as "this user can run any command as any user" or as narrow as "this user can run exactly this one command, with exactly these arguments, as root, without even needing to type a password." The narrower version is almost always safer: if that account is ever compromised, the damage is capped at exactly what the rule allows, not "anything root can do."
sudoers rule spectrum:
ALL=(ALL) ALL ← full root access, any command
ALL=(ALL) NOPASSWD: ALL ← full root access, no password even
ALL=(ALL) NOPASSWD: /usr/bin/uptime ← exactly one command, nothing else
narrower ─────────────────────────────────────► broader
(blast radius if compromised: one command) (blast radius: everything)
Firewalls, conceptually. A firewall is a set of rules that decides which network traffic is allowed in or out of a machine, based on things like port number and protocol. You already used ss in module 10 to see which ports are actually listening on your machine - a firewall is the layer that decides whether traffic reaching those listening ports from outside is allowed to arrive at all. The security logic is simple: if nothing needs to reach a port from the outside, block it; if only a specific service needs to be reachable, allow only that.
ufw (Uncomplicated Firewall). ufw is a simplified, human-friendly front end for Ubuntu's underlying firewall system. Instead of writing low-level rules, you express intent directly: allow this port, deny that one, check overall status. It has a single on/off state (enabled or disabled) and a list of rules evaluated for traffic that isn't otherwise already permitted (e.g., you don't need a rule for traffic your own machine initiated and is waiting on a reply for).
ss -tuln shows: Local Address:Port
127.0.0.1:5432 ← bound to localhost only, unreachable from outside
no matter what the firewall says
0.0.0.0:22 ← bound to ALL interfaces, reachable from the network
IF the firewall (ufw) allows it through
firewall (ufw) only matters for the second case — it can't expose
a service that was never listening externally in the first place
Checking exposure with ss (recap + new angle). Module 10 introduced ss for inspecting sockets. In a security context, the key question becomes: "what is actually listening for incoming connections on this machine, and did I intend for that?" A service listening on 0.0.0.0 (all network interfaces) is reachable from outside if the firewall allows it; a service listening only on 127.0.0.1 (localhost) is not reachable from other machines regardless of firewall rules, because it never accepts connections from outside in the first place. Reviewing listening ports regularly is a simple, high-value habit: unexpected listeners are one of the most common signs of misconfiguration or compromise.
Disabling root SSH login (ties to module 13). By default, some SSH server configurations permit logging in directly as root over SSH, given the right password or key. This is widely considered poor practice: it removes the audit trail benefit of forcing administrators to sudo from a named personal account, and it means a single compromised credential (root's) grants total access with no username to even guess. The fix is a setting in the SSH server's configuration file (/etc/ssh/sshd_config), PermitRootLogin, set to no, followed by restarting the ssh service (module 11) for the change to take effect.
Keeping packages updated (ties to module 05). Many real-world compromises exploit known, already-patched vulnerabilities in outdated software. sudo apt update && sudo apt upgrade (module 05) pulling in security patches promptly is one of the single highest-value, lowest-effort security practices that exists - far more impactful than most exotic hardening steps.
fail2ban, conceptually. fail2ban is a service that watches log files (tying back to module 14's /var/log/auth.log and journalctl) for patterns of repeated failed login attempts, and automatically, temporarily bans the offending IP address (usually via firewall rules) after a threshold is crossed. It's a practical defense against automated password-guessing ("brute force") attempts against services like SSH. It's mentioned here conceptually with an optional install exercise - a full configuration deep dive is beyond this beginner module's scope.
Command reference
| Command | What it does | Example |
|---|---|---|
sudo visudo |
Safely opens /etc/sudoers for editing, validating syntax before saving |
sudo visudo |
sudo visudo -f <path> |
Safely opens a specific file (e.g., in /etc/sudoers.d/) for editing with the same syntax validation |
sudo visudo -f /etc/sudoers.d/alice-docker |
sudo ufw status |
Shows whether ufw is active and lists current rules |
sudo ufw status verbose |
sudo ufw enable |
Turns the firewall on, applying its rule set to future traffic | sudo ufw enable |
sudo ufw disable |
Turns the firewall off entirely | sudo ufw disable |
sudo ufw allow <port> |
Adds a rule permitting incoming traffic on a specific port | sudo ufw allow 22 |
sudo ufw allow <service> |
Adds a rule permitting incoming traffic for a named service (from /etc/services) instead of a raw port number |
sudo ufw allow OpenSSH |
sudo ufw deny <port> |
Adds a rule blocking incoming traffic on a specific port | sudo ufw deny 8080 |
sudo ufw delete <rule> |
Removes a previously added rule, referenced either by number (from status numbered) or by repeating the original rule |
sudo ufw delete allow 8080 |
sudo ufw status numbered |
Lists current rules with index numbers, useful for deleting a specific one precisely | sudo ufw status numbered |
ss -tuln |
Lists listening TCP/UDP sockets (-t TCP, -u UDP, -l listening only, -n numeric ports) - recap from module 10, used here to audit exposure |
ss -tuln |
sudo apt update && sudo apt upgrade |
Refreshes package lists and installs available upgrades, including security patches (module 05 recap) | sudo apt update && sudo apt upgrade -y |
sudo systemctl restart ssh |
Restarts the SSH server so a configuration change (e.g., sshd_config) takes effect (module 11 recap) |
sudo systemctl restart ssh |
sudo apt install fail2ban |
Installs fail2ban for automated banning of repeated failed login attempts (optional) |
sudo apt install fail2ban |
Hands-on exercises
Open your WSL2 Ubuntu terminal. Recap your own sudo access: run
sudo -l. Read the output - it lists exactly what commands your user is permitted to run viasudo(commonly(ALL : ALL) ALLfor a default WSL2 user, meaning "any command, as any user"). Note whether a password is required.Practice
visudosafely. Runsudo visudo. Without changing anything, look at the structure of the file (comments, the default rule granting thesudogroup broad access). Exit without saving (innano, this isCtrl+Xthen answering "no" to save changes, or:q!if your default editor isvim).Create a least-privilege rule using
/etc/sudoers.d/. Runsudo visudo -f /etc/sudoers.d/practice-uptimeand add this single line (replacingyourusernamewith your actual Linux username):yourusername ALL=(ALL) NOPASSWD: /usr/bin/uptimeSave and exit. Then test it: run
sudo uptimeand confirm it runs without prompting for a password, whilesudo whoamistill prompts normally (proving the narrow rule only covers the one command you specified).Clean up the practice rule: run
sudo rm /etc/sudoers.d/practice-uptimeto remove it, since granting passwordlesssudofor anything, even something as harmless asuptime, isn't something you want lingering.Audit your currently listening ports: run
ss -tuln. For each line, note theLocal Address:Portcolumn - identify which entries are bound to127.0.0.1(localhost only) versus0.0.0.0or*(all interfaces). If you completed module 13's exercises, you should see SSH's port 22 listed.Check
ufw's current status: runsudo ufw status verbose. On a fresh WSL2 install this is usuallyinactive. Enable it: runsudo ufw allow OpenSSHfirst (so you don't lock out your own SSH access), thensudo ufw enable, confirming the prompt about possibly disrupting existing connections, then re-runsudo ufw status verboseto confirm it's nowactiveand lists your allow rule.Add and then remove a rule deliberately. Run
sudo ufw allow 8080, thensudo ufw status numberedand note the number assigned to that rule. Remove it precisely by number:sudo ufw delete <number>(replace with the actual number you saw), then confirm withsudo ufw status numberedthat it's gone.Cause a deliberate, recognizable error: run
sudo ufw allow notaport. Read the error message closely -ufwshould reject this clearly rather than silently accepting garbage input, sincenotaportisn't a valid port number or known service name. This is good practice for reading validation errors from tools that check their input.Check and harden root SSH login. Run
sudo grep -i permitrootlogin /etc/ssh/sshd_config. Note the current setting (it may be commented out with a default value, or explicitly set). Edit the file withsudo nano /etc/ssh/sshd_config(or your preferred editor), ensure the line readsPermitRootLogin no(uncommenting/changing it if needed), save, then runsudo systemctl restart sshto apply it. Re-run thegrepcommand to confirm the change stuck.Confirm your packages are current: run
sudo apt updatefollowed bysudo apt list --upgradable. If anything is listed, this is a live example of exactly the patching gap discussed in the concepts section - you don't have to install the upgrades right now to have made the point, but note what's listed.(Optional) Install
fail2banto see it conceptually in action: runsudo apt install -y fail2ban, thensudo systemctl status fail2banto confirm it's running, andsudo fail2ban-client statusto see its currently active protection "jails" (likely just the default SSH jail). You are not expected to fully configure it here - the goal is just to see it installed and running.Tie it all together: run
sudo journalctl -u ssh --since "1 hour ago" | grep -i "fail\|invalid"(module 14 recap) to look for any failed or invalid login attempts against your own practice SSH server from earlier exercises. Explain in your own words, out loud or in a note, why this single command connects modules 08, 11, 13, and 14 together.
Production practice: a narrow attack surface
Least privilege applies to users, files, services, and ports. Every unnecessary permission or listener is another path that must be secured and monitored.
Hardening is a set of gates. An attacker must reach a listening service, authenticate or exploit it, and gain useful permissions. Narrowing any gate reduces exposure; no single gate replaces the others.
flowchart LR
N["Network reachability"] --> F["Firewall rule"] --> S["Listening service"] --> A["Authentication"] --> P["Process permissions"]
The gates explain defense in depth. This change workflow keeps a security improvement from becoming an avoidable outage.
flowchart TD
A["Proposed security change"] --> B["Identify required access"]
B --> C["Keep recovery channel open"]
C --> D["Apply narrow rule"]
D --> E["Test intended access"]
E --> F["Test unintended access is blocked"]
F --> G["Document and monitor"]
A learning VM may need SSH from your own network but not public access to a development database. Allow the intentional SSH rule, verify it from the correct network, and keep the database bound to localhost or a private network rather than opening it “just in case.”
Firewall changes can lock you out of a remote server. Before enabling a restrictive policy, keep an existing session open, explicitly allow the management path you need, and know how to reach the provider console or another recovery channel.
In a local or disposable environment, list active listeners and classify each as expected or unexpected. For one harmless test service, decide whether it should bind to loopback or a broader interface and explain the choice before changing any firewall setting.
Explain why sudo is not simply “run as administrator,” why an open firewall port does not make an application secure, and how least privilege changes the impact of a compromised service.
- Can you name the user and port each running service actually needs?
- Do you have a recovery plan before changing remote access rules?
- Can you explain why a security control should be verified after it is changed?
Independent challenge
No commands given here — figure it out yourself using what you know from this module and earlier ones.
Task: Audit your machine's exposure and tighten one privilege, end to end. First, enumerate every port currently listening and classify each as bound to all interfaces versus localhost only (reuse the socket-inspection skill from module 10, applied here with a security eye). Second, grant one specific user passwordless sudo for exactly one harmless command via a drop-in file — then prove the grant is genuinely narrow by showing that same user still gets prompted (or refused) for a different command. Remove the drop-in when done. Tie the "who can do what as root" reasoning back to the user model from module 04.
Stuck? One hint
ss -tuln and reading its Local Address column tells you 0.0.0.0/* (exposed) from 127.0.0.1 (local only); the narrow grant goes in a file under /etc/sudoers.d/ edited via visudo -f, using a NOPASSWD: rule naming one absolute command path and nothing more.
Common mistakes & troubleshooting
- Editing
/etc/sudoersdirectly with a plain text editor instead ofvisudo. A syntax error saved this way can breaksudofor everyone on the machine, including yourself, with no safety net. Always usevisudo(orvisudo -ffor files in/etc/sudoers.d/). - Enabling
ufwbefore allowing SSH access, when connecting over SSH. If yousudo ufw enablewithout first runningsudo ufw allow OpenSSH(or the equivalent port), and you were connected over SSH, you can immediately lock yourself out with no way back in except at the physical/console level. Always allow your own access method before enabling. - Granting broad
sudorules "just to make something work." A rule likeALL=(ALL) NOPASSWD: ALLfor a service account defeats the purpose of having sudoers rules at all; take the extra minute to scope the rule to the specific command(s) actually needed. - Assuming a service bound to
127.0.0.1needs a firewall rule. It doesn't - if nothing outside the machine can even reach that socket in the first place, a firewall rule for it is redundant (though not harmful). Checkss -tuln's address column before assuming you need aufwrule. - Forgetting to restart
sshd/sshafter editingsshd_config. Configuration file changes (likePermitRootLogin no) don't take effect until the service is restarted; test the new behavior only aftersudo systemctl restart ssh. - Confusing "firewall blocks it" with "service isn't listening."
ufw denyon a port that nothing is even listening on accomplishes nothing meaningful; conversely, a service happily listening with no firewall rule at all may be fully exposed. Check bothss -tuln(is it listening) andufw status(is it reachable) together. - Treating
fail2banas a replacement for good SSH hygiene. It reduces the effectiveness of brute-force password guessing but doesn't replace disabling root login, keeping software patched, or using key-based auth - it's one layer among several, not a silver bullet.
Checkpoint quiz
Write down your answer to each question before expanding it — checking without attempting first is the single easiest way to fool yourself into thinking you've learned this.
- Why is
visudoused instead of editing/etc/sudoerswith a normal text editor? - What does the principle of least privilege mean in the context of a sudoers rule, with a concrete example?
- What's the difference in risk between a service listening on
127.0.0.1versus0.0.0.0? - Why must you be careful about the order of operations when enabling
ufwon a machine you're connected to over SSH? - Why is disabling root login over SSH considered good practice, given that key-based or password authentication is already required?
- What does
fail2banactually do, and what earlier module's tooling does it conceptually rely on to work? - Why is
sudo apt update && sudo apt upgradeconsidered one of the highest-value security practices, compared to more exotic hardening steps?
Show answers
- Because
visudovalidates the syntax of the file before allowing you to save, preventing a malformed/etc/sudoersfile from locking every user out of usingsudoon the machine - a mistake that's very hard to recover from if made with a plain editor. - It means granting only the exact access needed and no more - for example, giving a user permission to run only
sudo systemctl restart myapprather than blanketsudoaccess to run any command as root, so that even if that account is misused or compromised, the damage is capped to that one narrow capability. - A service listening on
127.0.0.1only accepts connections originating from the same machine, so it's unreachable from the network regardless of firewall settings; a service listening on0.0.0.0accepts connections from any reachable network interface, meaning it can be reached externally unless a firewall or other control blocks it - making it a much larger exposure if unintended. - Because enabling
ufwimmediately starts enforcing its rules, and if a rule permitting your current connection method (e.g., SSH/port 22) hasn't been added first, the firewall can cut off the very connection you're using, locking you out with no way back in except through direct/console access. - Because it removes the ability to authenticate directly as the all-powerful root account over the network at all - forcing administrators to log in as a named personal account and use
sudofor privileged actions, which preserves an audit trail and means a single leaked root credential can't be used for direct SSH access. fail2banwatches authentication-related logs (like/var/log/auth.log, tied to module 14's logging concepts) for patterns of repeated failed login attempts and automatically, temporarily blocks the offending source (typically via firewall rules) once a threshold is exceeded, mitigating automated brute-force login attempts.- Because a large share of real-world compromises exploit already-known vulnerabilities in outdated software for which patches already exist; regularly applying updates closes those specific, already-solved holes with comparatively minimal effort, delivering more practical risk reduction than many more elaborate, narrowly-scoped hardening measures.
Cumulative review
Closed-book. Don't reopen earlier modules while attempting these — the point is to find out what actually stuck.
- The systemd journal physically lives on your WSL2 virtual disk. Explain, tying module 12's
ext4.vhdxbehaviour to module 14, why an unbounded journal is a real disk-space concern, and name the command that caps its size. - You've set
PermitRootLogin no. Explain the audit-trail benefit of that (module 15) and how key-based auth from module 13 still lets you administer the box — specifically, which key ends up on the server and where. - After a burst of SSH login attempts, name both the traditional
/var/logfile and thejournalctlunit you'd inspect to see them, and give the module-08 filter that isolates just the failed/invalid ones. - SSH refuses to use your private key because its mode is
644. Connect this to the permission model from module 03, and state the correct modes for the private key file and for the~/.sshdirectory. fail2banbans IPs after repeated failures. Which log/tooling from module 14 does it watch to detect the failures, and which enforcement layer from this module does it use to actually block the offending address?- A hard link and a symlink both point at a sensitive log file. If you tighten the permissions on the original file's inode, does the hard link reflect the new permissions? Does the symlink? Explain each using the inode concept from module 12.
- Why is
visudoused instead of a plain editor, and what does dropping a single-purpose file into/etc/sudoers.d/let you express in the spirit of least privilege? ss -tulnshows a service listening on0.0.0.0:22. Describe how you'd confirm it's genuinely exposed and how you'd useufwto allow exactly that port before turning the firewall on — and why the ordering matters.df -hreports/is nearly full. Walk through how you'd usedu(module 12) andjournalctl --disk-usage(module 14) to determine whether logs are the culprit and reclaim space.- Explain how modules 13, 14, and 15 all converge in the single command
sudo journalctl -u ssh --since "1 hour ago" | grep -i "fail\|invalid".
Show answers
- The WSL2
ext4.vhdxgrows to accommodate data but doesn't automatically shrink when data is freed, so a journal that grows without bound permanently inflates that virtual disk on the Windows side.sudo journalctl --vacuum-size=<size>(e.g.500M) caps the journal's total size. - Disabling root SSH login forces admins to log in as a named personal account and use
sudo, preserving an audit trail and removing the single all-powerful credential from being directly reachable over the network. Key-based auth still works: your public key goes on the server in that user's~/.ssh/authorized_keys, while the private key never leaves your machine. /var/log/auth.logis the traditional file;journalctl -u ssh(orsshd) is the journal unit. Pipe either throughgrep -i "fail\|invalid"(module 08) to isolate failed/invalid attempts.- Read/write/execute bits (module 03) govern who can read the key; a
644key is readable by group and other, which SSH rejects as insecure. The private key must be600(owner read/write only) and the~/.sshdirectory700(owner-only). fail2banwatches authentication logs —/var/log/auth.log/ the journal from module 14 — for repeated failures, and enforces bans by adding firewall rules (the firewall layer this module covers), temporarily blocking the offending IP.- The hard link is another name for the same inode, and permissions live on the inode, so the hard link reflects the change (they're the same file). The symlink just stores a path to the filename and holds no permissions of its own, so it simply resolves to the same target and shows whatever the target now has.
visudovalidates syntax before saving, preventing a malformed sudoers file from locking everyone out ofsudo. A focused file in/etc/sudoers.d/lets you grant one user permission to run exactly one specific command (optionally NOPASSWD), isolated and easy to remove — capping the blast radius if that account is misused.- Confirm exposure by reading
ss -tuln's Local Address column showing0.0.0.0:22(all interfaces). Runsudo ufw allow OpenSSH(orallow 22) first, thensudo ufw enable. Ordering matters because enabling the firewall immediately enforces rules; if you enable before allowing your own access port, you can cut off the very SSH session you're using. - Use
du -h --max-depth=1 /var/log | sort -rhto see which log files/dirs are largest andjournalctl --disk-usageto see how much the journal specifically consumes. If the journal is the culprit, reclaim with--vacuum-size/--vacuum-time; if a flat log file is, rotation/compression or clearing old archives addresses it. - It uses the SSH server from module 13 (the
sshunit whose auth events are being inspected), module 14'sjournalctlquerying with unit and time filters, module 08'sgrep -itext filtering in a pipe, and module 15's security purpose of hunting for failed/invalid login attempts — a single line spanning services, logging, text processing, and security.
Further reading & sources
man7.org: sudoers(5) - the full sudoers rule syntax beyond theNOPASSWD:single-command pattern used here.- Ubuntu Server docs: UncomplicatedFirewall (ufw) - the official ufw reference, including application profiles (
ufw app list) beyond raw ports. man7.org: sshd_config(5) - full server-side SSH config reference, includingPermitRootLoginand other hardening directives beyond this module's scope.- fail2ban official docs - configuration reference if you want to go past the "installed and running" level this module stops at.
- CIS Benchmarks for Ubuntu Linux - the industry-standard, much more exhaustive hardening checklist for when you administer a real production machine.
Next
Continue to 16-capstone-project, the Log Watchdog project that combines everything from this track — scripting, permissions, systemd, logging, and security — into one piece of work.