Chapter Eighteen

Security and Hardening

Learning Objectives
  1. Apply the principle of least privilege to users, processes, and services
  2. Harden SSH configuration and deploy fail2ban to block brute-force attacks
  3. Compare SELinux and AppArmor as mandatory access control frameworks
  4. Manage secrets, automatic updates, and audit logs responsibly
  5. Recognise common attack patterns and the defences that matter most

Linux security is not a feature you switch on. It is a discipline of habits, configurations, and vigilance, built up over the life of a system. A default install of any reputable distribution is reasonably secure on day one, but by day one hundred it will have accumulated open ports, unnecessary packages, forgotten user accounts, and stale configurations that together make it an interesting target. This chapter is a survey of the most important security practices — the things a competent Linux user should have in muscle memory, and the things an administrator responsible for real systems must understand.

The Principle of Least Privilege

Almost every security principle in this chapter is a specialisation of a single idea: give every user and every program exactly the privilege it needs, and no more. This is the principle of least privilege, and it is the single most important concept in systems security.

Running a web server as root is a violation. If attackers find a bug in the web server, they have root. Running a web server as an unprivileged user called www-data is the correct choice: a bug now gets attackers only the ability to read and write the web server's files, which is bad but not catastrophic. Every step up the privilege ladder is one that an attacker has to climb separately.

The same principle applies to file permissions (don't give world-read to secrets), to sudo rules (don't grant full root where a single command would do), to SSH keys (don't put developer keys on production), to container capabilities (don't give containers more than they need), and to everything else in this chapter.

Patching and Updates

The single most effective security practice is: keep your system patched. The vast majority of real-world compromises exploit known vulnerabilities for which patches have already been released. Attackers do not usually need zero-days; they need unpatched machines.

On Debian and Ubuntu, enable the unattended-upgrades package:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades

This will install security updates automatically every night. On Fedora and RHEL, the equivalent is dnf-automatic. Do not let weeks go by without updates.

Security advisories are tracked by the distributions themselves and by databases like the CVE (Common Vulnerabilities and Exposures) system. A typical CVE ID looks like CVE-2024-3094, referring to the xz backdoor discovered in March 2024 — a sobering reminder that the supply chain for open source software is itself an attack surface.

SSH Hardening

SSH is the front door of most Linux servers, and attackers scan the entire IPv4 address space looking for it. A default Ubuntu install logged to the internet will see thousands of brute-force login attempts per day on port 22. Harden it.

The most important settings live in /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
AllowUsers alice bob

PermitRootLogin no disables root logins entirely; root access is only via sudo from a regular user. PasswordAuthentication no disables password logins entirely, forcing the use of SSH keys — this is by far the most impactful single change you can make. AllowUsers whitelists which accounts may log in at all.

After editing, restart the service:

sudo systemctl reload ssh

Test from a different terminal window before disconnecting your current session. If you have made a mistake and cannot reconnect, at least your original session is still working while you fix it.

Changing the SSH port from 22 is sometimes recommended as a way to reduce brute-force noise in the logs. It does not provide real security — any determined attacker will portscan — but it does cut down on the background noise in your logs, which makes real attacks easier to spot.

fail2ban: Intrusion Prevention

fail2ban watches log files for repeated failed login attempts and temporarily bans the offending IP addresses via the firewall. It is the standard defence against brute-force attacks.

sudo apt install fail2ban
sudo systemctl enable --now fail2ban

The default configuration in /etc/fail2ban/jail.conf covers SSH and many other services. Override settings in /etc/fail2ban/jail.local:

[sshd]
enabled = true
maxretry = 3
findtime = 600
bantime = 3600

Three failed attempts in ten minutes earns a one-hour ban. You can tune the thresholds, add email notifications, and extend fail2ban to other services (web authentication, FTP, SMTP) by writing "jail" definitions.

Mandatory Access Control: SELinux and AppArmor

Traditional Unix permissions are discretionary: the owner of a file decides who can read it. If a program is running as a user, it can do anything that user can do. That is not good enough for serious security, because a compromised program will happily open files its author never intended.

Mandatory access control (MAC) adds a second layer of enforcement that sits above the discretionary permissions. The system administrator, not the file owner, writes policies that say "the web server process may read files in /var/www and write log files in /var/log/nginx, and nothing else". Even if the web server is compromised and running as root inside its sandbox, it cannot step outside the policy.

Linux has two main MAC frameworks.

SELinux, developed by the NSA and the Red Hat community, is powerful and comprehensive. It labels every file, process, and network port with a security context and enforces rules written in a policy language. Fedora and RHEL enable SELinux in enforcing mode by default. Its reputation for being difficult to configure is partly justified — the policy language is complex — but most of the time you are using pre-built policies for common services, and you only need to know a few commands:

getenforce              # Enforcing, Permissive, or Disabled
sudo setenforce 0       # temporarily switch to permissive
sestatus                # status
ls -Z                   # show SELinux contexts on files
ps -Z                   # show contexts on processes
sudo restorecon -Rv /var/www    # reset contexts to policy defaults

When an SELinux policy denies something, look at /var/log/audit/audit.log or run sudo ausearch -m avc. Tools like audit2allow can generate policy patches from observed denials.

AppArmor, maintained by Canonical, is path-based rather than label-based and is generally considered simpler to write policies for. Ubuntu and openSUSE enable AppArmor by default. Profiles live in /etc/apparmor.d/:

sudo aa-status
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx    # put into learning mode
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx     # switch to enforcement

Which is better? It depends who you ask. SELinux is more thorough but harder; AppArmor is friendlier but less strict. Use whichever your distribution ships with by default, and do not turn it off unless you have a specific reason.

Secrets Management

Hard-coding passwords, API tokens, or private keys in shell scripts and configuration files is the most common security mistake. When the script ends up in Git, or gets copied to a backup, the secret escapes with it.

For small-scale use, a few practices help:

  • Use environment variables and set them from a file that is never committed to version control. The .env file idiom is widespread.
  • File permissions. Put secrets in files with chmod 600, owned by the user that needs them.
  • Separate config from secrets. Your main config file can live in Git; a sibling secrets.conf with chmod 600 cannot.

For larger systems, dedicated tools are worth the investment:

  • HashiCorp Vault — a centralised secret store with fine-grained access control and dynamic, short-lived credentials.
  • age and sops — command-line tools for encrypting secrets at rest.
  • Cloud-native secret services like AWS Secrets Manager and GCP Secret Manager.

And remember: if a secret has been exposed, it is compromised. Rotate it. Do not assume that because you quickly deleted the file, nobody fetched it in the meantime.

File and Permission Audits

A quick audit can turn up surprises:

# Find world-writable files
find / -xdev -type f -perm -0002 2>/dev/null

# Find setuid binaries
find / -xdev -type f -perm -4000 2>/dev/null

# Find files with no owner
find / -xdev \( -nouser -o -nogroup \) 2>/dev/null

World-writable files are frequently a mistake. Setuid binaries are fine when they are in /usr/bin/passwd and friends, but an unexpected one could be a backdoor. Files with no owner often mean a deleted user left data behind.

The Audit Framework

Linux includes an audit framework that logs kernel events: system calls, file accesses, authentication attempts. It is configured with auditctl and queried with ausearch and aureport.

sudo auditctl -w /etc/passwd -p wa -k passwd_changes
sudo ausearch -k passwd_changes

The first line sets a watch on /etc/passwd for writes and attribute changes, tagged with the key passwd_changes. The second queries for events matching that key. For high-security environments — financial, healthcare, government — the audit framework is how you demonstrate compliance with logging requirements.

Common Attacks and Defences

A short survey of attack categories and what helps against them.

Brute-force SSH. Defence: disable password authentication, use keys, run fail2ban, consider port-knocking.

Web application vulnerabilities. Defence: keep software patched, run the web server as an unprivileged user, apply AppArmor/SELinux profiles, use a web application firewall.

Privilege escalation from a compromised service. Defence: least privilege, MAC, kernel hardening (kernel.unprivileged_userns_clone=0, for example).

Malicious package. Defence: only install from trusted repositories, pin versions, verify signatures, use reproducible builds when possible. The xz backdoor of 2024 showed that even trusted packages can go bad.

Physical access. Defence: encrypt disks with LUKS, lock your BIOS, require a boot password. If someone has your unlocked laptop, all software defences are meaningless.

Data exfiltration. Defence: egress firewall rules, DNS logging, IDS systems like Suricata, monitoring for unusual traffic patterns.

Ransomware. Defence: backups, read-only mounts where possible, careful management of admin credentials, network segmentation.

Good Habits

Security is habits, not configurations. A few to cultivate:

  • Read auth.log or its journal equivalent periodically. You will recognise normal patterns quickly, and anomalies will stand out.
  • Review last output to see recent logins. Unusual IPs, unusual times, accounts you did not remember creating — all worth investigating.
  • Check ss -tlnp every so often to see what is listening. New services can appear without your noticing.
  • Audit cron jobs. A line added to crontab is an easy way to persist a compromise.
  • Know your software supply chain. Which repositories do your packages come from? Which containers do your services run? Which base image are those built on? Who maintains them?

Linux is a secure platform when you use it with care. It is an insecure platform when you don't. The tools in this chapter are the ones that, in my experience, pay back the time invested in them many times over. Put them into practice on the machines you care about, and you will sleep better at night.