Skip to content
elephantoo

Security basics & server hardening

Lesson 31 of 31 17 min read

Updates, least privilege, SSH hardening, firewalls, fail2ban, auditing and a hardening checklist.


The moment a server gets a public IP address, bots start probing it: within minutes you'll see SSH login attempts in the logs. Security is not one product you install; it's a set of habits applied in layers (defence in depth), so that a single mistake doesn't become a breach. This final lesson brings together everything from the course into a practical hardening checklist for Ubuntu/Debian servers, with Fedora/RHEL equivalents.

The principles#

  • Least privilege. Users, services and keys get only the access they need, and nothing runs as root that doesn't have to.
  • Minimise the attack surface. Fewer installed packages, fewer listening ports, fewer accounts.
  • Defence in depth. Firewall and SSH keys and updates and monitoring, so that one failure isn't fatal.
  • Visibility. If you can't see logins, changes and failures, you can't respond to them.
  • Assume you'll need to rebuild. Backups and documented, automated setup turn a disaster into an inconvenience.

1. Keep the system updated#

Most real-world compromises exploit known vulnerabilities that already have patches.

Terminal
sudo apt update && sudo apt upgrade        # Fedora/RHEL: sudo dnf upgrade
sudo apt install unattended-upgrades       # preinstalled on Ubuntu
sudo dpkg-reconfigure -plow unattended-upgrades
cat /var/run/reboot-required 2>/dev/null   # Ubuntu: a kernel or libc update needs a reboot

On Fedora/RHEL, install dnf-automatic (or dnf5-plugin-automatic on Fedora 41+), set apply_updates = yes in /etc/dnf/automatic.conf, and run sudo systemctl enable --now dnf-automatic.timer.

Kernel updates only take effect after a reboot. Schedule regular maintenance windows, or use live patching (Ubuntu Pro Livepatch, kpatch on RHEL).

2. Users and sudo#

  • Create a personal account for each admin, with sudo rights (usermod -aG sudo ada on Ubuntu, the wheel group on Fedora/RHEL). Never share accounts.
  • Disable direct root login; use sudo, which logs who did what.
  • Remove or lock accounts that are no longer needed: sudo usermod -L olduser and sudo usermod -s /usr/sbin/nologin olduser, or sudo userdel -r olduser.
  • Give service accounts no shell and no password: useradd --system --shell /usr/sbin/nologin myapp.
  • Review who has power:
Terminal
getent group sudo            # Fedora/RHEL: getent group wheel
sudo ls /etc/sudoers.d/      # extra sudo rules
awk -F: '$3 == 0 {print $1}' /etc/passwd    # accounts with UID 0 (should be only root)
Output
sudo:x:27:
README
ada
root

This output is from a test machine. The sudo group has no members because sudo access is granted by a file in /etc/sudoers.d/ instead, so always check both places. The UID-0 check lists only root, which is what you want: any other account with UID 0 is a full root account and a classic sign of compromise.

3. Harden SSH#

SSH is the front door, so lock it properly. First make sure key login works (see the SSH lesson). Then create a drop-in config:

/etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 30
AllowUsers ada deploy
X11Forwarding no
Terminal
sudo sshd -t                        # test the configuration: no output means OK
sudo systemctl reload ssh           # 'sshd' on Fedora/RHEL

Keep your current session open and test a new login from a second terminal before you close it. Changing the port (Port 2222) reduces log noise from bots but isn't real security; keys and no passwords are.

💡 On cloud images, check whether a file in /etc/ssh/sshd_config.d/ (e.g. 50-cloud-init.conf) already sets PasswordAuthentication yes. The first value read wins in sshd, which is why the hardening file uses a low number such as 10-. Confirm the effective settings with sudo sshd -T | grep -E 'passwordauth|permitroot'.

4. Firewall: deny by default#

From the firewall lesson:

Terminal
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw limit OpenSSH
sudo ufw allow 443/tcp
sudo ufw enable

Then compare what's listening with what's allowed:

Terminal
sudo ss -tlnp

Anything listening on 0.0.0.0 that shouldn't be public should either be bound to 127.0.0.1 in its config, or blocked. Databases, caches and admin panels should never face the internet.

5. fail2ban: ban brute-force attackers#

Terminal
sudo apt install fail2ban                  # Fedora: sudo dnf install fail2ban

Configure it in a .local file (never edit jail.conf, which upgrades overwrite):

/etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5
backend  = systemd
ignoreip = 127.0.0.1/8 203.0.113.50

[sshd]
enabled = true
Terminal
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip 198.51.100.23
Output
Status for the jail: sshd
|- Filter
|  |- Currently failed:	2
|  |- Total failed:	347
|  `- Journal matches:	_SYSTEMD_UNIT=sshd.service + _COMM=sshd
`- Actions
   |- Currently banned:	4
   |- Total banned:	61
   `- Banned IP list:	198.51.100.23 192.0.2.77 203.0.113.201 192.0.2.140

Put your own static IP in ignoreip so you can't ban yourself.

6. Minimise what's installed and running#

Terminal
systemctl list-units --type=service --state=running   # what's running?
sudo ss -tulpn                                         # what's listening (TCP and UDP)?
apt list --installed | wc -l
sudo apt purge telnetd rsh-server                      # remove what you don't need
sudo systemctl disable --now cups avahi-daemon         # e.g. printing/mDNS on a server

Every service you remove is one you never have to patch or secure.

7. File permissions and secrets#

  • Keep secrets (.env, API keys, DB passwords) at 600 or 640, owned by the service user, and out of Git.
  • Look for world-writable files and unexpected setuid binaries:
Terminal
sudo find / -xdev -type f -perm -0002 -ls 2>/dev/null | head      # world-writable files
sudo find / -xdev -type f -perm -4000 2>/dev/null                  # setuid programs
  • Mount options can help: /tmp with nodev,nosuid,noexec blocks some attack techniques.
  • On a fresh install, check the home directories aren't readable by everyone: ls -ld /home/* (Ubuntu 21.04+ creates them as 750).

8. Mandatory access control: AppArmor and SELinux#

These kernel security modules confine programs even if they're running as root:

  • AppArmor (Ubuntu, Debian, openSUSE) uses per-program profiles: sudo aa-status.
  • SELinux (Fedora, RHEL, Rocky, Alma) uses labels on every file and process: getenforce should print Enforcing.

Leave them enabled. When SELinux blocks something legitimate (say nginx serving files from /srv/www), fix the label instead of disabling SELinux:

Terminal
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
sudo restorecon -Rv /srv/www
sudo ausearch -m avc -ts recent        # see recent denials

9. Logging, auditing and integrity#

  • Review logins: last, sudo lastb (failed logins), journalctl -u ssh --since today, /var/log/auth.log.
  • Review sudo use: journalctl _COMM=sudo.
  • auditd (sudo apt install auditd) records security-relevant events. For example, sudo auditctl -w /etc/passwd -p wa -k passwd-changes logs writes to /etc/passwd, and sudo ausearch -k passwd-changes shows them.
  • Lynis (sudo apt install lynis, then sudo lynis audit system) scans the system and prints a prioritised list of hardening suggestions. It's a great way to check your work.
  • Ship logs off the server: an attacker who gets root can edit local logs.

10. Backups and recovery#

Ransomware and mistakes are both answered by backups:

  • Follow the 3-2-1 rule: 3 copies, on 2 kinds of storage, 1 off-site.
  • Automate them (cron/systemd timers + rsync, restic or borg, plus database dumps).
  • Test restores regularly. A backup you've never restored is a hope, not a backup.
  • Keep server setup in code (shell scripts, Ansible, cloud-init) so you can rebuild a compromised machine quickly instead of trying to clean it.

The hardening checklist#

✔Item
☐System fully updated; automatic security updates enabled
☐Personal admin accounts with sudo; no shared accounts; root login disabled
☐SSH: keys only, no passwords, no root, AllowUsers set; config tested
☐Firewall: default deny incoming; only required ports open; DBs not public
☐fail2ban (or equivalent) protecting SSH and other login endpoints
☐Unneeded packages and services removed or disabled
☐Services run as dedicated non-root users (systemd User=, hardening options)
☐Secrets stored with tight permissions, outside Git
☐AppArmor/SELinux enabled and enforcing
☐Logs reviewed/centralised; disk and auth alerts configured
☐Backups automated, off-site, and restore-tested
☐Lynis run, findings reviewed

Common mistakes#

  • Locking yourself out: disabling passwords before keys work, or enabling the firewall without SSH. Always keep a second session (or the provider's console) available.
  • Disabling SELinux/AppArmor "because it was in the way" instead of fixing the policy.
  • chmod 777 and running everything as root to make errors go away.
  • Exposing databases, Redis or admin dashboards to the internet "temporarily".
  • Postponing reboots forever after kernel updates.
  • Security through obscurity alone, like changing the SSH port and calling it done.
  • No backups, or untested ones.

What's next#

Congratulations, you've completed the Linux course! 🎉 You can navigate and manage a Linux system, automate it with bash, and run and secure servers. From here, put it into practice: set up a small cloud VM, deploy an app behind nginx with a systemd service, automate its backups with a timer, and harden it with this checklist. Natural next steps are Docker and containers, Git, and configuration management with Ansible.

Check your understanding

Quick quiz

0/3 answered
  1. 1.What is the single most effective routine security measure for a Linux server?

  2. 2.What does fail2ban do?

  3. 3.Which principle says every user and service should have only the permissions it needs?

Finished reading?

Mark this lesson complete to track your progress.