Chapter Eighteen

Security and Hardening

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.

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

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.

Table 18.1: Applying least privilege

Pattern How
Never log in as root Use sudo
Run services as their own user systemd User=, DynamicUser=
Drop capabilities AmbientCapabilities=, CapabilityBoundingSet=
Read-only root filesystem systemd ProtectSystem=strict
Private /tmp systemd PrivateTmp=yes
No new privileges systemd NoNewPrivileges=yes
Network restrictions systemd IPAddressAllow/Deny
Filesystem sandboxing systemd ProtectHome=, ReadOnlyPaths=

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.

Table 18.2: SSH hardening checklist (sshd_config)

Setting Recommended Why
PermitRootLogin no (or prohibit-password) Force use of a named user
PasswordAuthentication no Keys only; kills brute-force
PubkeyAuthentication yes Required for key login
PermitEmptyPasswords no Obvious
X11Forwarding no Reduce attack surface
AllowUsers alice bob Allowlist specific accounts
MaxAuthTries 3 Limit guesses per connection
ClientAliveInterval 300 Reap idle sessions
Protocol 2 SSH 1 is insecure
Port Non-default (optional) Reduces noise, not an attacker stopper

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.

Table 18.3: Security tools comparison

Tool Category Role
SELinux MAC Label-based mandatory access control (RHEL default)
AppArmor MAC Path-based MAC (Ubuntu/SUSE default)
fail2ban Intrusion prevention Ban IPs showing failed logins
auditd Auditing Kernel-level event audit log
aide / tripwire File integrity Detect tampering of system files
rkhunter / chkrootkit Rootkit scanning Check for known rootkits
lynis Hardening audit Config scanning and advice
nmap Recon (defensive too) Find open ports on your own hosts
ssh-audit SSH audit Check sshd settings and algorithms

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 such as financial, healthcare, and 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.

Table 18.4: Common attack vectors and defences

Attack How It Works Defence
SSH brute-force Guessing passwords on port 22 Keys only, fail2ban, non-default port
Unpatched CVE Exploit a known kernel/daemon bug Automatic updates, CVE monitoring
Misconfigured service Default creds, open admin ports Firewall, principle of least privilege
Privilege escalation Turn user access into root Minimize setuid, SELinux/AppArmor
Supply-chain Compromised package or dependency Signed packages, SBOMs, pinning
Phishing / social Trick a human with access 2FA, training, hardware tokens
Credential reuse Leaked password used elsewhere Unique passwords, password manager
Kernel LPE Local exploit in kernel code Keep kernel patched; unprivileged user namespaces off

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.

Table 18.5: Ongoing security habits

Habit Frequency
Apply security updates Daily / automatic
Review users with sudo access Monthly
Check /etc/passwd for unknown accounts Monthly
Review authorized_keys files Monthly
Rotate long-lived secrets Quarterly
Test backup restores Quarterly
Review firewall rules Quarterly
Run an external port scan Quarterly
Review audit logs Weekly or alerted
Textbook of Linux — Learn Linux on iPhone — Download on the App Store

Frequently Asked Questions

  1. Why are SSH keys safer than passwords?
  2. What does fail2ban actually do, and is it still worth running?
  3. What is a Linux firewall and how do nftables, iptables, and ufw relate?
  4. What is the principle of least privilege and why does it underpin everything?
  5. What is the difference between discretionary and mandatory access control?
  6. SELinux or AppArmor, what is the practical difference?
  7. What are Linux capabilities and why split up root?
  8. What is seccomp and how do filters reduce kernel attack surface?
  9. What are sudo best practices? Why not just become root?
  10. How do I keep a Linux server patched automatically?
  11. What is LUKS full-disk encryption and when do I need it?
  12. What is public-key cryptography and why does every sysadmin need to understand it?
  13. What is GPG and what should a sysadmin actually use it for?
  14. What is auditd and how do I use it for tamper-evident logging?
  15. What is a CVE and how should I follow Linux security advisories?
  16. How does package signing protect against supply-chain attacks?
  17. What is an SBOM and why do regulators keep asking for one?
  18. Why does it matter that daemons run as non-root users?
  19. What does /etc/security/limits.conf do and when do I need it?
  20. What are the CIS Benchmarks and how do I use them to harden a server?