Module 18 · Security 65 min

Plug a brand-new Linux server into the internet and within minutes, robots are trying to break in. They guess passwords and try the same handful of known holes against every machine they can find. This module is the day-one checklist that keeps them out: a firewall, an auto-banner for bad guesses, careful user accounts, and the habit of checking what's on your machine before an attacker does.

By the end of this module, you will:

  • Configure UFW to allow only the ports your server needs
  • Install and tune fail2ban to block brute-force attacks automatically
  • Keep software up to date with unattended-upgrades
  • Apply least-privilege user and sudo hardening
  • Find SUID binaries, world-writable files, and run a Lynis audit
  • Verify file integrity with SHA256 checksums and GPG signatures
  • Encrypt and decrypt sensitive files with GPG
  • Apply a 10-step end-of-course security checklist to any new Linux machine

The security mindset: leave less stuff on

Windows comes with a firewall switched on, and apps ask it for permission. Linux servers work the other way round: every door is open unless you shut it. So every program you leave running, every port you leave open, every account you don't really use — they're all a way in. Good security is mostly about turning things off: fewer programs running, fewer open ports, fewer admin accounts. It's a habit, not a one-time job.

Why a brand-new server gets attacked so fast

Robots are constantly knocking on every address on the internet to see what answers. A fresh server with a public address usually gets its first knock within five minutes of going online. The robots aren't picking on you — they try the same default usernames, known holes and weak passwords on millions of machines at once. It's not personal, it's automatic. The defences in this module stop those robots cold, so you can get on with your actual work.

UFW — the easy firewall

UFW stands for "Uncomplicated Firewall", and it really is the easy one. Underneath, Linux has a powerful but fiddly firewall called iptables; UFW is the friendly front door to it. The drill is always the same: look at which doors are open, allow the ones you actually need, then switch the firewall on.

Checking open ports before enabling the firewall
# ── See what is actually listening right now ──────────────
root@server:~# ss -tuln
Netid State Recv-Q Send-Q Local Address:Port
tcp LISTEN 0 128 0.0.0.0:22 # SSH
tcp LISTEN 0 511 0.0.0.0:80 # HTTP
tcp LISTEN 0 511 0.0.0.0:443 # HTTPS

# ── Check UFW status ──────────────────────────────────────
root@server:~# sudo ufw status verbose
Status: inactive

Allow SSH first — or you'll lock yourself out

If you're logged into a server from another computer and you switch on UFW without allowing SSH first, the firewall will instantly shut your own connection. You'll be locked out and won't be able to log back in. Always run sudo ufw allow ssh before you turn UFW on. If you've moved SSH to a non-standard port like 2222, allow that one instead: sudo ufw allow 2222/tcp.

Enabling UFW and adding rules
# ── Step 1: allow what you need BEFORE enabling ───────────
root@server:~# sudo ufw allow ssh
Rules updated
root@server:~# sudo ufw allow http
Rules updated
root@server:~# sudo ufw allow https
Rules updated

# ── Step 2: enable the firewall ───────────────────────────
root@server:~# sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

# ── Verify the rules ──────────────────────────────────────
root@server:~# sudo ufw status numbered
Status: active
To Action From
-- ------ ----
[ 1] 22/tcp ALLOW IN Anywhere
[ 2] 80/tcp ALLOW IN Anywhere
[ 3] 443/tcp ALLOW IN Anywhere

# ── Block a specific IP (e.g., persistent scanner) ────────
root@server:~# sudo ufw deny from 203.0.113.47
Rules updated

# ── Remove a rule by number ───────────────────────────────
root@server:~# sudo ufw delete 4
sudo ufw status verbose
Show what rules are set and whether the firewall is on
sudo ufw allow 8080/tcp
Open one specific port (here, 8080 over TCP)
sudo ufw deny from <IP>
Block all traffic from one specific address
sudo ufw reset
Wipe every rule and turn UFW off (careful!)
sudo ufw logging on
Start writing firewall events to /var/log/ufw.log
ss -tuln
List every port your machine is listening on

fail2ban — automatically bans bad guessers

UFW is a doorman who only checks which doors are open or shut. fail2ban is the watchful one. It reads the log files in real time, and when it sees the same address fail to log in over and over, it adds a temporary block. Out of the box: 5 bad SSH guesses gets that address banned for 10 minutes. Most robots see the door slam and just go knock on someone else's machine.

Installing and starting fail2ban
root@server:~# sudo apt install fail2ban -y
Reading package lists... Done
Setting up fail2ban (1.0.2-3) ...

root@server:~# sudo systemctl enable --now fail2ban
Created symlink /etc/systemd/system/multi-user.target.wants/fail2ban.service

root@server:~# sudo systemctl status fail2ban
● fail2ban.service - Fail2Ban Service
Loaded: loaded (/lib/systemd/system/fail2ban.service; enabled)
Active: active (running) since ...

Don't edit /etc/fail2ban/jail.conf directly — every time you update fail2ban, that file gets overwritten and your changes vanish. Make a copy called jail.local instead and put your settings there:

/etc/fail2ban/jail.local
root@server:~# sudo nano /etc/fail2ban/jail.local

# ── Global defaults ───────────────────────────────────────
[DEFAULT]
bantime = 1h
# ban duration (1 hour)
findtime = 10m
# observation window
maxretry = 5
# failures before ban
ignoreip = 127.0.0.1
# never ban localhost

# ── SSH jail ──────────────────────────────────────────────
[sshd]
enabled = true
port = ssh
logpath = %(sshd_log)s
backend = %(sshd_backend)s
maxretry = 3
Checking bans and managing jails
# ── Apply config changes ──────────────────────────────────
root@server:~# sudo systemctl reload fail2ban

# ── Check jail status and banned IPs ─────────────────────
root@server:~# sudo fail2ban-client status sshd
Status for the jail: sshd
|- Filter
| |- Currently failed: 2
| `- Total failed: 47
`- Actions
|- Currently banned: 1
`- Banned IP list: 203.0.113.47

# ── Unban an IP (e.g., if you locked yourself out) ────────
root@server:~# sudo fail2ban-client set sshd unbanip 203.0.113.47
1

# ── Watch live fail2ban activity ──────────────────────────
root@server:~# sudo tail -f /var/log/fail2ban.log
... fail2ban.actions [INFO] Ban 45.33.32.156

Be stricter on public servers

If your server faces the open internet, turn the dials up. Set bantime = 24h (block them for a whole day) and maxretry = 3 (block them after 3 bad tries, not 5). The same robots keep coming back over and over — longer bans mean less noise in your logs. Newer fail2ban versions also have bantime.increment = true, which makes the ban get longer and longer each time the same address comes back.

Keep everything up to date

Most servers that get hacked aren't hit with some fancy new attack — they get hit through a hole that had a fix available weeks or months earlier. Nobody updated the software. So keeping your stuff up to date is the single best thing you can do for security. On Ubuntu and Debian, a tool called unattended-upgrades installs the security fixes for you, in the background, automatically.

Enabling automatic security updates
# ── Install unattended-upgrades ───────────────────────────
root@server:~# sudo apt install unattended-upgrades apt-listchanges -y

# ── Run the interactive configuration wizard ──────────────
root@server:~# sudo dpkg-reconfigure -plow unattended-upgrades
Automatically download and install stable updates? [yes]

# ── Test the configuration (dry-run) ─────────────────────
root@server:~# sudo unattended-upgrade --dry-run --debug
Initial blacklisted packages:
Packages that will be upgraded: openssh-server

# ── Check what services need restarting after updates ─────
root@server:~# sudo needrestart -b
NEEDRESTART-VER: 3.6
NEEDRESTART-SVC: nginx.service
NEEDRESTART-KSTA: 3
# KSTA=3 means kernel needs reboot
sudo apt update && sudo apt upgrade
Check for updates, then install them
sudo apt full-upgrade
Update even when it means removing some old packages
sudo apt autoremove --purge
Clean up leftover packages and their settings
sudo needrestart
After updates, see which programs need a restart

Restart the server on its own

By default, unattended-upgrades won't restart the server, even when an update needs one (like a new Linux kernel). You can switch that on in /etc/apt/apt.conf.d/50unattended-upgrades — set Unattended-Upgrade::Automatic-Reboot "true"; and Unattended-Upgrade::Automatic-Reboot-Time "03:30"; so it restarts at 3:30 in the morning when nobody's using it.

Users and sudo — give out as little power as possible

The rule is simple: everyone and everything gets the smallest amount of power they need to do their job, and not a drop more. On a normal server, that means: nobody logs in as root, you have one admin account that uses sudo for the dangerous stuff, and every other account is either locked or deleted. Check who's logged in and who can use sudo every now and then.

Reviewing users, sudo access, and login history
# ── Who has sudo access? ──────────────────────────────────
root@server:~# grep -Po '^sudo.+:\K.*$' /etc/group
alice,bob

# ── Full sudoers inspection ───────────────────────────────
root@server:~# sudo visudo -c
/etc/sudoers: parsed OK

# ── Lock an account that should not log in ───────────────
root@server:~# sudo usermod -L deploybot
# -L locks the password (prepends ! in /etc/shadow)

# ── Who logged in recently? ───────────────────────────────
root@server:~# last -n 10
alice pts/0 192.168.1.5 Mon Apr 20 09:14 still logged in
root pts/0 10.0.0.1 Sun Apr 19 22:07 - 22:09 (00:02)

# ── Failed login attempts (brute-force evidence) ──────────
root@server:~# sudo lastb | head -20
root ssh:notty 45.33.32.156 Mon Apr 20 03:12 - 03:12
admin ssh:notty 45.33.32.156 Mon Apr 20 03:12 - 03:12
ubuntu ssh:notty 45.33.32.156 Mon Apr 20 03:11 - 03:11

# ── Check authentication log directly ─────────────────────
root@server:~# sudo grep "Failed password" /var/log/auth.log | tail -5
Apr 20 03:12:01 server sshd[4821]: Failed password for root from 45.33.32.156

Don't let root log in over SSH

Every Linux server has a root account — the all-powerful admin — and the robots know it. It's the first username they try, every time. So just don't let root log in over SSH at all. In the file /etc/ssh/sshd_config, set PermitRootLogin no. Make yourself a normal user account with sudo instead. That one change blocks the most common attack on the internet in one go.

sudo adduser alice
Make a new user (it asks you the questions one by one)
sudo usermod -aG sudo alice
Let a user run sudo
sudo usermod -L username
Lock an account — they can't log in with a password any more
sudo deluser --remove-home username
Delete a user and their home folder
who
See who's logged in right now
sudo passwd -e username
Force a user to pick a new password next time they log in

Checking your files and scanning the system

Some Linux programs run with the powers of the person who owns the file, not the person running them — they're called SUID programs. That's how sudo and passwd work. But if a sneaky one slips in, anyone can use it to become root. World-writable files and folders are also bad: any user on the system can change them. A tool called Lynis checks hundreds of these things for you in one go and gives you a list of what to fix first.

Finding SUID binaries and world-writable files
# ── List all SUID/SGID binaries on the system ────────────
root@server:~# find / -perm /6000 -type f 2>/dev/null | sort
/usr/bin/chfn
/usr/bin/passwd
/usr/bin/sudo
/usr/sbin/exim4
# Review anything unfamiliar in this list

# ── Find world-writable files (not in /proc or /sys) ──────
root@server:~# find / -xdev -type f -perm -0002 2>/dev/null
# Any results outside /tmp are suspicious

# ── World-writable directories ────────────────────────────
root@server:~# find / -xdev -type d -perm -0002 2>/dev/null
/tmp
/var/tmp
/run/lock
auditd — Kernel-level system call auditing
root@server:~# sudo apt install auditd audispd-plugins -y
root@server:~# sudo systemctl enable --now auditd

# ── Add a rule: watch /etc/passwd for all writes/attrs ────
root@server:~# sudo auditctl -w /etc/passwd -p wa -k passwd_changes

# ── Search audit log for that key ─────────────────────────
root@server:~# sudo ausearch -k passwd_changes | aureport -f
File Report
===========================
1. Mon Apr 20 09:14:22 2026 /etc/passwd alice 4820 passwd
Lynis — automated security audit
root@server:~# sudo apt install lynis -y

# ── Run a full system audit ───────────────────────────────
root@server:~# sudo lynis audit system
[...]
[ SUGGESTION ]
* Consider hardening SSH configuration [SSH-7408]
- Details : AllowTcpForwarding (set YES to NO)
* Install a package audit tool [PKGS-7398]
[...]
Hardening index : 68 [############## ]
Tests performed : 248
Plugins enabled : 0

# ── Review just the warnings and suggestions ─────────────
root@server:~# sudo lynis audit system --quick 2>&1 | grep -E "WARNING|SUGGESTION"

Lynis gives your server a score

A brand-new Ubuntu server usually scores 60-70 out of 100. Switch on UFW, install fail2ban, stop root logging in over SSH, and uninstall stuff you don't use — and you can push that score over 80. Each suggestion Lynis gives you has a code like SSH-7408 — search the Lynis documentation for that code to see exactly how to fix it. Fix the WARNINGs first; SUGGESTIONs are the nice-to-haves.

Takeaway 1 — Switch on UFW first, every time

The rule for any server is: block everything, then open only the doors you actually need. Run ss -tuln to see which doors are open. Allow those in UFW. Then turn UFW on. Every time you install a new program that listens for connections, come back and check the rules.

Takeaway 2 — fail2ban deals with the robots for you

Once fail2ban is running with your settings in jail.local, the noise in your logs drops to almost nothing within hours. Run fail2ban-client status sshd the day after installing it and you'll be shocked how many addresses got banned overnight while you slept.

Takeaway 3 — Update automatically, restart on a schedule

Switch on unattended-upgrades for security fixes and pick a quiet time of week for restarts. Most of the hacks you hear about in the news exploited a hole that already had a fix. Staying up to date kills a huge chunk of real risk for basically zero effort on your part.

Takeaway 4 — No root over SSH, keep sudo small

Put PermitRootLogin no in sshd_config and check the sudo group every now and then. Use last to see who's logged in lately and lastb to see who's tried and failed. Lock the accounts you don't use with usermod -L. Giving out less power, consistently, beats any single security tool you can install.

Takeaway 5 — Check, don't guess

Run lynis audit system on every new server, and again after any big change. Add the SUID search and the world-writable file scan to your regular checks. The robots look for exactly these slip-ups — find them first, with the same tools they use.

Desktop security — extra rules for work laptops

Everything above was about servers. A Linux laptop on your desk has a few more rules to follow — especially if you work for the French government or a big company. France's cyber-security agency (ANSSI) has guidelines that say what laptops have to do, and these are the most important ones.

Lock the screen when you walk away

Set your laptop to lock its own screen when you stop using it for a while. Go to Settings → Privacy → Screen Lock, set "Blank Screen Delay" to 5 minutes, and turn "Automatic Screen Lock" on. To enforce this on lots of work laptops at once, use gsettings set org.gnome.desktop.screensaver lock-enabled true and gsettings set org.gnome.desktop.session idle-delay 300. And whenever you get up from your desk, lock the screen yourself with Super+L.

Is your disk encrypted?

Full-disk encryption (Linux calls it LUKS) scrambles everything on the drive so that if someone steals your laptop, they can't read your files. ANSSI says mobile government workers have to have it. Ubuntu offers it as a tick-box when you install. To check if yours is already encrypted, type lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT. If you see crypto_LUKS in the list, you're encrypted. If not, you have to reinstall to turn it on for the whole disk. If reinstalling isn't possible, you can at least encrypt your personal folder: sudo apt install ecryptfs-utils then ecryptfs-migrate-home -u username.

Check yours right now

Open a terminal and run this — it shows your drives and tells you which ones are encrypted:

$ lsblk -o NAME,SIZE,FSTYPE,MOUNTPOINT
NAME SIZE FSTYPE MOUNTPOINT
sda 256G
├─sda1 512M vfat /boot/efi
└─sda2 255G crypto_LUKS ← encrypted!
└─dm-0 255G ext4 /

AppArmor — building on what you learned in Module 10

In Module 10 you learned about Linux's normal permissions — owner, group, others, read, write, execute. AppArmor works on top of those. Even if a program technically has permission to open a file, AppArmor can still stop it if the file isn't part of that program's job. Think of AppArmor as a job description that Linux enforces: a web server's job is to serve web pages — not to read your SSH keys. AppArmor makes sure it sticks to its job, no matter what permissions say.

AppArmor — checking and managing profiles
# Check AppArmor status and how many profiles are enforced
user@ubuntu:~$ sudo aa-status
apparmor module is loaded.
34 profiles are loaded.
34 profiles are in enforce mode.

# See if AppArmor blocked anything — look for DENIED in kernel journal
user@ubuntu:~$ sudo journalctl -k | grep "apparmor" | grep "DENIED"
# Blank = nothing blocked. Entries = AppArmor stopped something.

# Complain mode: log violations but don't block (useful for diagnosing)
user@ubuntu:~$ sudo aa-complain /usr/sbin/nginx
# Switch back to enforcing mode once diagnosis is complete
user@ubuntu:~$ sudo aa-enforce /usr/sbin/nginx

Reading the SUID list — what's normal, what's not

When you run the SUID search (find / -perm -4000 -type f 2>/dev/null), the list it gives back can look scary at first. Most of it is totally normal Linux stuff. Here's how to tell the boring from the bad:

Totally fine — these are on every Ubuntu

/usr/bin/sudo  ·  /usr/bin/passwd  ·  /usr/bin/su  ·  /usr/bin/mount  ·  /usr/bin/umount  ·  /usr/bin/ping  ·  /usr/lib/openssh/ssh-keysign. These are normal Linux tools that genuinely need extra power to do their jobs (changing passwords, mounting disks, and so on). They come with Ubuntu — nobody's messed with them.

Worth a look — find out where it came from

Anything in the list you don't recognise. Run dpkg -S /path/to/binary to ask Linux which installed package this file belongs to. If dpkg says nothing — it doesn't know — then that file did not come from the normal Linux software store. Find out why it's there.

Red flag — fix this now

Any shell with the SUID bit set is bad news. If /bin/bash, /bin/sh or /usr/bin/python3 shows up in the list, anyone on the system can use them to become root instantly. Also worth checking: find / -perm -4000 -newer /etc/passwd 2>/dev/null — this shows SUID files that were changed after your system was first set up. Any of those deserve a hard look.

GPG — checking downloads and scrambling secret files

GPG (it stands for "GNU Privacy Guard") is Linux's all-purpose crypto tool. Two everyday things to know it for: checking that a file you downloaded hasn't been tampered with, and scrambling a secret file so only the right person can read it. It's already installed on every Ubuntu desktop.

Use 1 — Make sure your download wasn't tampered with

Whenever you download Linux software from a serious project, they also publish a tiny SHA256 number — a sort of fingerprint of the file. If your copy's fingerprint matches theirs, it's the same file. Some projects also sign that fingerprint with GPG. Together these two steps catch both broken downloads and downloads where someone swapped the file out for a bad one.

checksum + GPG signature verification
# Step 1 — calculate the SHA256 of the file you downloaded
user@ubuntu:~$ sha256sum ubuntu-24.04-desktop-amd64.iso
e2e3c5...d8f1 ubuntu-24.04-desktop-amd64.iso

# Compare with the value on the official download page. Match? Good — file isn't corrupt.

# Step 2 — verify the publisher's GPG signature on the SHA256SUMS file
user@ubuntu:~$ gpg --keyid-format long --verify SHA256SUMS.gpg SHA256SUMS
gpg: Good signature from "Ubuntu CD Image Automatic Signing Key <cdimage@ubuntu.com>"

# If you see "BAD signature" — DO NOT install. The file was tampered with or corrupted.

# If you see "Can't check signature: No public key" — import the publisher's key first
user@ubuntu:~$ gpg --keyserver hkps://keyserver.ubuntu.com --recv-keys 843938DF228D22F7B3742BC0D94AA3F0EFE21092

Use 2 — Scramble a sensitive file with a password

Need to email a tax return or store some passwords? Scramble the file first using a password. This is the simple way to use GPG — no fancy key setup, just a password you and the other person already know. (Tell them the password by phone or in person — never in the same email.)

gpg symmetric encrypt / decrypt
# Encrypt — produces secrets.txt.gpg (the original is unchanged; delete it after if needed)
user@ubuntu:~$ gpg --symmetric --cipher-algo AES256 secrets.txt
Enter passphrase:
Repeat passphrase:

# Decrypt back to the original
user@ubuntu:~$ gpg --decrypt secrets.txt.gpg > secrets.txt

# Decrypt to stdout (read once, never write to disk)
user@ubuntu:~$ gpg --decrypt secrets.txt.gpg

If you want to send something secret to a person and not have to share a password with them first, GPG has a more advanced mode — you each make a key pair (gpg --full-gen-key) and swap your "public" half. It's a bit more setup, but stronger. The GnuPG handbook walks you through it.

The end-of-course security checklist

Go through these ten checks on any Linux machine you plan to use seriously. Most of them take under a minute. Tick all ten and you're ahead of 95% of the Linux machines on the internet.

# Check How
1All updates installedsudo apt update && sudo apt upgrade -y
2Security updates run by themselvessudo dpkg-reconfigure unattended-upgrades
3Firewall on; only the doors you need are opensudo ufw status · only allow what your machine actually serves
4SSH only takes keys, not passwordsSet PasswordAuthentication no in /etc/ssh/sshd_config
5Root can't log in over SSHSet PermitRootLogin no in /etc/ssh/sshd_config
6fail2ban is runningsystemctl status fail2ban · sudo fail2ban-client status sshd
7Disk encryptedlsblk -o NAME,FSTYPE — look for crypto_LUKS
8Screen locks when you walk awaySettings → Privacy → Screen Lock — switch it on
9Backups exist AND you've tested oneDéjà Dup / Timeshift / rsync — restore a single file to make sure it works
10Lynis score is good enoughsudo lynis audit system — aim for 70 or higher

Re-do #1, #2 and #10 once a month. Re-do the rest whenever you make a big change — installing new software, adding a user, or changing the network. Security is a habit, not a one-off project.