Detect WiFi Evil Twins with Python

Build a Python agent that detects evil twin attacks and displays threats on a live terminal dashboard.

Introduction

30 Second Summary

Every server connected to the internet gets hit by automated attacks within minutes. Bots scan for open ports, weak passwords, and default configurations around the clock.

In this project, you will take a fresh Ubuntu Server installation and systematically lock it down using the same commands and techniques that real system administrators use to protect production servers.

What You'll Build

A security-hardened Linux server running locally on your Mac, built from scratch inside a virtual machine using UTM.

By the end of this project, you'll have:

  • A running Ubuntu Server virtual machine with a non-root admin user and hardened SSH access.
  • A configured UFW firewall and Fail2Ban intrusion prevention system blocking brute-force attacks.
  • Proper file permissions, disabled unnecessary services, and a personal hardening checklist documenting every change you made.
  • Secret Mission: Set up automated security monitoring with Lynis and log alerts.

Are there any prerequisites?

None. This project includes full setup instructions for every tool you need, starting from scratch. You just need a Mac with Apple Silicon (M1 or later) and around 40 GB of free disk space.

Before We Start

Before We Start...

Install UTM and Create Your Virtual Machine

To practice real security commands, you need a real Linux environment. A virtual machine gives you a full, isolated server to experiment with safely. Nothing you do inside it can affect your Mac.

In this step, you will install UTM (a free macOS virtualization app) and set up a fresh Ubuntu Server installation inside it. By the end, you will have a working Linux server ready to harden.

In this step, get ready to:
  • Download and install UTM and the Ubuntu Server ISO.
  • Create a virtual machine in UTM with the right settings.
  • Install Ubuntu Server and reach the login prompt.
Download UTM and the Ubuntu Server ISO

You need two downloads before you can create your virtual machine. UTM is the app that runs the VM. The Ubuntu Server ISO is the installer image you will boot from.

  • Open your browser and go to https://mac.getutm.app/.
  • Click Download to get the free version (the GitHub download).
  • Once the download finishes, open the .dmg file.
  • Drag the UTM icon on the left to the Applications folder on the right.
  • Close the DMG window.

What is UTM?

UTM is a free, open-source virtualization app designed specifically for macOS. It uses Apple's Hypervisor framework to run ARM64 operating systems at near-native speed on Apple Silicon Macs.

Think of it as a way to run an entire separate computer inside a window on your Mac. The virtual machine is completely isolated from your host system.

Why the ARM64 version?

Apple Silicon Macs (M1, M2, M3, M4) use ARM processors. The ARM64 ISO matches your hardware, so the VM runs at near-native speed instead of emulating a different architecture.

This download may take a few minutes

The Ubuntu Server ISO is a large file. While it downloads, you can move on to opening UTM and starting the VM setup. You will need the ISO file when you reach the step that asks you to browse to it.

Create a new virtual machine in UTM

With UTM installed and the ISO downloading (or downloaded), it is time to create your VM. You will configure the hardware settings that determine how much of your Mac's resources the VM can use.

  • Press Cmd+Space to open Spotlight search.
  • Type UTM and press Enter to open it.
  • If macOS asks whether you want to open an app downloaded from the internet, click Open.
  • In the UTM window, click the + button to create a new virtual machine.
  • Select Virtualize.
  • Select Linux.
  • Under Boot ISO Image, click Browse and select the Ubuntu Server ISO you downloaded.
  • Click Continue.

What about Apple Virtualization?

Leave the Use Apple Virtualization checkbox unchecked. UTM defaults to the QEMU backend, which is more stable for Linux server installations.

  • On the Hardware screen, set Memory to 4096 MB.
  • Leave CPU Cores on Default.
  • Uncheck Enable hardware OpenGL acceleration (this causes issues with Linux server installations).
  • Click Continue.
  • On the Storage screen, set the size to 32 GB.
  • Click Continue.
  • On the Shared Directory screen, click Continue (you do not need shared folders for this project).
  • On the Summary screen, give your VM a name (e.g. Ubuntu Server) and click Save.

Your VM now appears in the UTM sidebar, ready to start.

Don't see the Browse button for the ISO?

Make sure you selected Virtualize (not Emulate) and then Linux. The Boot ISO Image option only appears on the Linux virtualization path.

If your ISO has not finished downloading, wait for it to complete. You cannot select a partially downloaded file.

Help me set up my VM in UTM

Install Ubuntu Server on the VM

Your VM is configured but empty. Now you will boot it from the Ubuntu ISO and run through the installer. The Ubuntu Server installer is text-based (no graphical interface), so you will navigate it with your keyboard.

  • In UTM, select your VM in the sidebar and click the Play button (the triangle icon) to start it.
  • Wait for the boot menu to appear. Select Try or Install Ubuntu Server and press Enter.

What to expect from the installer

The Ubuntu Server installer is a text-based wizard. You navigate using the arrow keys, Tab to move between options, Space to select checkboxes, and Enter to confirm. It walks you through language, keyboard, network, storage, and user setup.

For most screens, the defaults are correct. The key decisions are your username/password and enabling SSH.

  • Choose your language (English) and press Enter.
  • If the installer offers to update itself, select Continue without updating or Update to the new installer. Either choice works.
  • Accept the default keyboard layout and select Done.
  • On the network screen, accept the defaults and select Done.
  • On the proxy screen, leave it blank and select Done.
  • On the mirror screen, accept the default and select Done.
  • On the storage screen, leave Use an entire disk selected and select Done.
  • Review the storage summary and select Done.
  • Confirm the destructive action by selecting Continue.

Choose a memorable username and password

On the next screen, you will create your admin account. Pick a username and password you can remember. Write them down if needed. You will use these credentials throughout the entire project.

  • On the Profile screen, enter your name, a server name, a username, and a password. Remember these credentials.
  • On the Ubuntu Pro screen, select Skip for now and then Continue.
  • On the SSH screen, check the box for Install OpenSSH server using the Space key. Then select Done.
  • On the Featured Server Snaps screen, do not select anything. Select Done.
  • Wait for the installation to complete. You will see log messages scrolling. This takes a few minutes.
  • When you see Reboot Now at the bottom, do NOT click it yet.

Why not reboot yet?

If you reboot with the ISO still attached, the VM will boot back into the installer instead of your new system. You need to eject the ISO first.

  • In the UTM toolbar at the top of the VM window, click the disc icon (or go to the drive settings).
  • Find the CD/DVD entry and select Clear to eject the ISO.
  • Now select Reboot Now in the installer.
  • Wait for the VM to restart. After a moment, you will see a login prompt with your server name.
  • Log in with the username and password you created during installation.

You should see a command-line prompt showing your username and server name. This confirms Ubuntu Server is installed and running.

VM boots back into the installer?

You forgot to eject the ISO. Close the VM window (this powers it off). In UTM, right-click your VM, select Edit. Under the drives section, find the CD/DVD drive and clear the ISO path. Then start the VM again.

If the screen is blank or shows an error after reboot, wait 30 seconds. The first boot after installation can take longer while the system configures itself.

Help me get my Ubuntu Server VM to boot correctly

Your Linux server is alive and running. In the next step, you will update it, create a dedicated security admin user, and start locking things down.

Update the System and Create a Secure Admin User

Your Ubuntu Server is installed, but it shipped with whatever packages were bundled into the ISO. Those packages may have known vulnerabilities that have since been patched.

In this step, you will bring the system up to date and then create a dedicated admin user. Running day-to-day tasks as your only admin account is risky. A separate hardened user gives you a safety net if one account is ever compromised.

In this step, get ready to:
  • Update all system packages to their latest secure versions.
  • Create a new admin user with sudo privileges.
  • Audit existing accounts and lock any unused ones.
Update your system packages

A fresh install often has outdated packages with known security holes. Updating first ensures every tool you install afterwards is built on a secure foundation.

  • Run a full system update by entering this command:
sudo apt update && sudo apt upgrade -y

What does this command do?

`sudo apt update` refreshes the list of available packages from Ubuntu's repositories. It does not install anything yet.

`sudo apt upgrade -y` downloads and installs newer versions of every package that has an update available. The -y flag automatically confirms the upgrade.

The upgrade may take a few minutes depending on how many packages need updating. You will see a stream of download and installation progress lines.

  • Once it finishes, confirm your system details by running:
uname -a

You should see output showing your Linux kernel version, architecture (aarch64 for ARM64), and the hostname you chose during installation.

  • Check your distribution details by running:
lsb_release -a

You should see output showing Ubuntu 24.04 LTS with codename noble.

Seeing 'No LSB modules are available'?

This message is normal on Ubuntu Server. It appears because the desktop LSB modules are not installed, but your distribution information still prints correctly below it.

If the upgrade command failed with a network error, check that your VM has internet access. In UTM, the default shared/NAT networking should provide connectivity automatically.

Help me troubleshoot my system update

Create a dedicated admin user

Best practice is to avoid using your initial installation account for everything. A dedicated admin user with a strong password provides a cleaner audit trail and limits blast radius if credentials are leaked.

  • Create the new user by running:
sudo adduser securadmin

What will adduser ask me?

The adduser command is interactive. It will prompt you to set a password (enter it twice), then ask for optional profile information: Full Name, Room Number, Work Phone, Home Phone, and Other.

You can press Enter to skip each optional field. When it asks if the information is correct, type Y and press Enter.

  • Grant the new user sudo privileges by running:
sudo usermod -aG sudo securadmin

What does this command do?

`usermod -aG sudo securadmin` appends (-a) the user securadmin to the sudo group (-G). On Ubuntu, membership in the sudo group grants the user permission to run commands as root.

  • Verify the group membership by running:
groups securadmin

You should see sudo listed among the groups for securadmin.

  • Switch to the new user to confirm everything works:
su - securadmin
  • Enter the password you set for securadmin when prompted.
  • Confirm sudo access by running:
sudo whoami

You should see root printed, confirming that securadmin can execute commands as root.

  • Switch back to your original user by running:
exit

'securadmin is not in the sudoers file'?

This means the usermod command did not succeed. Switch back to your original user with exit, then re-run sudo usermod -aG sudo securadmin. Make sure you include the -a flag. Without it, usermod replaces all groups instead of appending.

Help me fix my securadmin sudo permissions

Audit existing user accounts

A fresh Ubuntu Server has many system accounts, but only a few can actually log in. Attackers look for forgotten or unused accounts with weak (or no) passwords. Auditing accounts now establishes a clean baseline.

  • List all accounts that have a real login shell by running:
cat /etc/passwd | grep -v nologin | grep -v false

What does this command do?

`/etc/passwd` lists every user account on the system. Most system accounts have their shell set to /usr/sbin/nologin or /bin/false to prevent interactive login.

By filtering those out with grep -v, you see only accounts that could potentially be used to log in.

You should see your original admin user, securadmin, and root. Any other accounts with login shells are worth investigating.

  • Check for accounts with empty passwords by running:
sudo awk -F: '($2 == "" ) {print $1}' /etc/shadow

What does this command do?

`/etc/shadow` stores password hashes for each user. This awk command checks field 2 (the password hash) for any account where it is empty. An empty password means anyone could log in without credentials.

On a fresh install, this should return nothing. If it prints any usernames, those accounts need immediate attention.

  • If you find any unused accounts that can log in but should not, lock them by running:
sudo usermod -L [[LOCKUSER="username to lock"]]

What does the -L flag do?

The -L flag places an exclamation mark in front of the password hash in /etc/shadow. This prevents password-based login for that account without deleting it. You can unlock the account later with sudo usermod -U if needed.

When in doubt, lock it

On a fresh Ubuntu Server install, you likely will not find any accounts to lock. The important thing is building the habit of auditing. On a production server inherited from another team, this step catches forgotten service accounts or ex-employee logins.

Not sure which accounts to lock?

Never lock root, your original admin user, or securadmin. Only lock accounts that appear in your login-shell audit that you do not recognize and did not create.

Help me decide which accounts to lock

Your system is now fully updated with a secure admin user in place. Next up, you will harden SSH access to control exactly who can connect to your server remotely.

Harden SSH Access

Your server now has a dedicated admin user with sudo privileges. The next priority is locking down how people connect to the server remotely.

SSH is the front door to your server. Right now, it accepts password logins on the default port 22. Automated bots scan the internet for exactly this setup, trying thousands of passwords per hour. You will change the port, disable password login entirely, and require cryptographic keys instead.

In this step, get ready to:
  • Back up and edit the SSH configuration to restrict access.
  • Generate an SSH key pair on your Mac and copy it to the server.
  • Test and apply the new SSH settings.
Back up and edit the SSH configuration

Before changing any system configuration file, you always make a backup. If something goes wrong, you can restore the original and start over.

  • Back up the current SSH configuration by running this command:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup

What does this command do?

`cp` copies a file. You are creating an identical copy called `sshd_config.backup` in the same directory. If you ever need to undo your changes, you can copy this backup back over the edited file.

  • Open the SSH configuration file in the text editor by running:
sudo nano /etc/ssh/sshd_config

The file is full of commented-out settings (lines starting with `#`). You need to find specific lines and either uncomment them or add new values.

  • Find the line that says #PermitRootLogin prohibit-password and replace it with:
PermitRootLogin no
  • Find the line that says #PasswordAuthentication yes and replace it with:
  • If you see a file at /etc/ssh/sshd_config.d/50-cloud-init.conf, you will also need to edit that file later and set PasswordAuthentication no there too.
PasswordAuthentication no
  • Find the line that says #PubkeyAuthentication yes and uncomment it so it reads:
PubkeyAuthentication yes
  • Find the line that says #MaxAuthTries 6 and change it to:
MaxAuthTries 3
  • Find the line that says #Port 22 near the top of the file and change it to:
Port 2222
  • Save the file by pressing Ctrl+O, then Enter, then exit nano with Ctrl+X.

What do these settings do?

  • `PermitRootLogin no` blocks all SSH access for the root account. Attackers always try root first.
  • `PasswordAuthentication no` disables password-based logins entirely. Only cryptographic keys will work.
  • `PubkeyAuthentication yes` explicitly enables key-based authentication.
  • `MaxAuthTries 3` disconnects anyone who fails to authenticate within 3 attempts.
  • `Port 2222` moves SSH off the default port 22, which most automated scanners target.

Can't find a setting in the file?

Use Ctrl+W in nano to search. Type the setting name (like PermitRootLogin) and press Enter to jump to it. If a setting does not exist in the file at all, add it on a new line at the bottom.

help me find and edit settings in sshd_config using nano

Handle the cloud-init override file

Ubuntu Server 24.04 may include a file at /etc/ssh/sshd_config.d/50-cloud-init.conf that overrides your changes. Check if it exists by running cat /etc/ssh/sshd_config.d/50-cloud-init.conf. If it contains PasswordAuthentication yes, either delete the file or change that line to no.

Generate an SSH key pair and copy it to the server

SSH keys work like a lock and key pair. The server holds the lock (public key) and your Mac holds the key (private key). You must set this up before disabling password authentication, or you will lock yourself out.

  • Open a new Terminal window on your Mac. Do not use the VM terminal for this step.
  • Generate your SSH key pair by running this command:
ssh-keygen -t ed25519 -C "[[SSHCOMMENT="yourname@yourserver"]]"

What does this command do?

`ssh-keygen` creates a new key pair. The -t ed25519 flag specifies the Ed25519 algorithm, which is modern and secure. The -C flag adds a comment to identify the key.

When prompted for a file location, press Enter to accept the default. When prompted for a passphrase, either enter one for extra security or press Enter twice to skip it.

  • You should see output mentioning your key fingerprint and a randomart image. This confirms the key was created.
  • Now you need to find your VM's IP address. Switch back to your VM terminal and run:
ip addr show
  • Look for an IP address under the network interface (usually starting with 192.168 or 10.). Note this address down.
  • Switch back to your macOS Terminal and copy your public key to the server by running:
ssh-copy-id -p 22 securadmin@[[VMIP="your VM's IP address"]]

Why use port 22 here?

You have not restarted SSH yet, so it is still listening on the original port 22. You must copy the key now, before you restart the service with the new settings. Otherwise, password login will be disabled and you will have no way to authenticate.

  • When prompted, type yes to accept the server's fingerprint, then enter your securadmin password.
  • Verify the key was copied correctly. Switch to your VM terminal and run:
cat ~/.ssh/authorized_keys
  • You should see a line starting with ssh-ed25519 followed by a long string. This is your public key.

ssh-copy-id says connection refused?

Make sure you are using port 22 (not 2222) in the command. The SSH service has not been restarted yet, so it is still on the old port. Also confirm your VM's IP address is correct by re-running ip addr show on the server.

help me troubleshoot ssh-copy-id connection issues to my Ubuntu VM

Test and apply the new SSH configuration

You never restart SSH blindly. A typo in the config file could lock you out of your own server. You will test first, then restart.

  • Test the SSH configuration for syntax errors by running this command in your VM terminal:
sudo sshd -t
  • If you see no output, the configuration is valid. Any errors will show the line number and problem.
  • Restart the SSH service to apply your changes by running:
sudo systemctl restart ssh
  • Verify SSH is now listening on the new port by running:
ss -tlnp | grep 2222
  • You should see output showing that sshd is listening on port 2222.
  • Now test connecting from your Mac. Switch to your macOS Terminal and run:
ssh -p 2222 securadmin@[[VMIP="your VM's IP address"]]
  • You should connect without being asked for a password. Your SSH key handles the authentication automatically.

What does ss -tlnp do?

`ss` is a socket statistics tool. The flags break down as: -t (TCP only), -l (listening sockets), -n (show port numbers not names), -p (show the process using the port). Piping through grep 2222 filters the output to only your custom SSH port.

Can't connect on port 2222?

First, make sure sudo sshd -t showed no errors before you restarted. If it did, switch back to the VM console (which still works regardless of SSH), fix the config, and restart again.

If ss -tlnp | grep 2222 shows nothing, SSH may have failed to start. Check its status with sudo systemctl status ssh for error details.

help me fix my SSH connection after changing the port to 2222

Your SSH front door is now locked down. Attackers cannot guess passwords, cannot log in as root, and will not even find the service on the default port. Next up, you will add a firewall to control all network traffic in and out of your server.

Configure the Firewall with UFW

Your SSH configuration is locked down. But right now, the server still has no network-level barrier controlling which traffic can reach it.

A firewall sits between your server and the network, deciding which connections are allowed in and which are blocked. In this step, you will install and configure UFW to create strict traffic rules that only permit SSH on your custom port.

In this step, get ready to:
  • Install UFW and set restrictive default policies.
  • Enable the firewall and verify the rules are active.
  • Test that the firewall blocks unwanted traffic while allowing SSH.
Install and configure UFW with restrictive defaults

UFW stands for Uncomplicated Firewall. It is Ubuntu's recommended frontend for managing firewall rules without writing complex iptables commands directly.

  • Install UFW by running this command:
sudo apt install ufw -y
  • Confirm UFW is installed by checking its status:
sudo ufw status

You should see Status: inactive. The firewall is installed but not yet turned on.

Seeing 'command not found'?

Make sure you typed apt (not apt-get) and included sudo at the start. If the install fails with a network error, check your VM's internet connection with ping google.com.

Ask for help: Why can't I install ufw on my Ubuntu server?

Now you need to set up the default policies. These control what happens to traffic that does not match any specific rule.

  • Set the default policies and allow your SSH port by running these commands:
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2222/tcp

What do these commands do?

sudo ufw default deny incoming blocks all incoming connections by default. Nothing gets through unless you explicitly allow it.

sudo ufw default allow outgoing allows your server to make outbound connections (downloading updates, sending responses).

sudo ufw allow 2222/tcp creates an exception for your custom SSH port. Without this rule, enabling the firewall would lock you out of SSH entirely.

Why UFW instead of iptables?

UFW is a simplified frontend that writes iptables rules for you. It is Ubuntu's recommended tool for managing firewall rules.

Raw iptables gives you more granular control, but the syntax is complex and error-prone. UFW covers the vast majority of real-world use cases with much simpler commands.

Enable the firewall and verify your rules

Critical: make sure port 2222 is allowed first

Enabling UFW before allowing your SSH port will immediately cut off your SSH session. You will be locked out of the server.

If you skipped the sudo ufw allow 2222/tcp command above, run it now before proceeding.

  • Enable the firewall by running:
sudo ufw enable

What should I see?

UFW will display: Command may disrupt existing ssh connections. Proceed with operation (y|n)? Type y and press Enter.

You should then see: Firewall is active and enabled on system startup. This confirms the firewall is running and will persist after reboots.

  • Verify your firewall rules are active by running:
sudo ufw status verbose

You should see Status: active at the top, your default policies listed as deny (incoming), allow (outgoing), and port 2222/tcp with ALLOW IN from Anywhere.

  • Compare your firewall rules against what is actually listening on the network by running:
ss -tulpen

What does this command do?

ss -tulpen shows all TCP and UDP ports currently listening, along with the process that owns each one. This lets you cross-reference what your firewall allows against what is actually running.

If you see a service listening on a port that your firewall does not allow, external traffic cannot reach it. That is your firewall doing its job.

SSH connection dropped after enabling UFW?

If your SSH session disconnected, the port 2222 rule was not in place when you enabled the firewall. Connect directly through the UTM VM window (not SSH) and run sudo ufw allow 2222/tcp followed by sudo ufw reload.

Ask for help: I got locked out of SSH after enabling UFW, how do I fix it?

Test that the firewall is working

Rules on paper mean nothing until you verify them in practice. You will now confirm that the firewall is blocking unwanted traffic while still allowing your SSH connection.

  • Open a new Terminal window on your Mac (not the SSH session).
  • Try to ping your server by running:
ping [[VM_IP="your VM's IP address"]]

The ping should hang with no replies. UFW's default deny incoming policy blocks ICMP (ping) packets along with everything else.

  • Press Ctrl+C to stop the ping.
  • Verify SSH still works on port 2222 by running from the same Mac terminal:
ssh -p 2222 securadmin@[[VM_IP="your VM's IP address"]]

You should connect successfully. This proves that your firewall is selectively allowing SSH traffic on port 2222 while blocking everything else.

Why is ping blocked?

Ping uses the ICMP protocol, not TCP or UDP. Your sudo ufw default deny incoming policy blocks all incoming traffic that does not have an explicit allow rule.

You only allowed 2222/tcp, so ICMP packets are dropped. On a production server, blocking ping makes it harder for attackers to discover your machine on the network.

  • Switch back to your SSH session on the server.
  • Enable firewall logging by running:
sudo ufw logging on
  • Watch the firewall log in real time by running:
sudo tail -f /var/log/ufw.log

What does the log show?

This log records every connection attempt that UFW blocks. Each entry shows the source IP, destination port, and protocol of the blocked traffic.

On a public server, you would see automated bots and scanners hitting this log constantly. Logging gives you visibility into what your firewall is protecting you from.

  • Try running another ping from your Mac terminal while the log is open. You should see new blocked entries appear in the log.
  • Press Ctrl+C to stop watching the log.

Not seeing anything in the log?

Make sure you ran sudo ufw logging on first. If the log file does not exist yet, try sending a ping from your Mac and then check again with sudo cat /var/log/ufw.log.

Ask for help: Why isn't my UFW log showing blocked connections?

Your firewall is now installed, configured, and verified. Every incoming connection is blocked by default, with only your custom SSH port allowed through. Next up, you will install Fail2Ban to automatically block attackers who try to brute-force their way past your SSH login.

Install and Configure Fail2Ban

Your firewall is blocking unwanted traffic, but determined attackers will keep hammering the one port you left open.

In this step, you will install Fail2Ban, a tool that monitors login attempts and automatically bans IP addresses that fail too many times. This is the same brute-force protection used on production servers worldwide.

In this step, get ready to:
  • Install Fail2Ban and configure it to protect your custom SSH port.
  • Start the service and verify the SSH jail is active.
  • Test brute-force protection by triggering a ban and then lifting it.
Install Fail2Ban and configure the SSH jail

Fail2Ban works by reading log files for failed authentication attempts. When an IP address exceeds a threshold of failures within a time window, Fail2Ban adds a firewall rule to block that IP.

  • Install Fail2Ban by running this command:
sudo apt install fail2ban -y
  • Confirm the installation completed by checking the package version:
fail2ban-client --version

You should see a version number printed to the terminal.

Package not found?

Run sudo apt update first to refresh your package lists, then retry the install command.

help me install fail2ban on Ubuntu Server

Fail2Ban ships with a default config file at /etc/fail2ban/jail.conf. You should never edit this file directly because package updates will overwrite your changes.

  • Create a local override file that Fail2Ban reads instead:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Why copy instead of creating a new file?

The jail.local file inherits all the defaults from jail.conf and lets you override only the values you need to change.

Fail2Ban always checks for jail.local first. If it exists, those settings take priority over jail.conf.

Now you need to configure the SSH jail to protect your custom port.

  • Open the local config file in nano:
sudo nano /etc/fail2ban/jail.local
  • Press Ctrl+W to open nano's search.
  • Type [sshd] and press Enter to jump to the SSH jail section.
  • Update the [sshd] section so it looks like this:
[sshd]

# Enable the SSH jail
enabled = true

# Monitor our custom SSH port
port = 2222

# Ban after 3 failed attempts
maxretry = 3

# Ban lasts 1 hour (3600 seconds)
bantime = 3600

# Count failures within a 10-minute window
findtime = 600

What does this configuration do?

enabled = true activates this jail. Without it, Fail2Ban ignores SSH entirely.

port = 2222 tells Fail2Ban which port to block when it bans an attacker. This must match your SSH port.

The combination of maxretry = 3 and findtime = 600 means 3 failures within 10 minutes triggers a ban. The bantime = 3600 keeps the attacker locked out for a full hour.

  • Save the file with Ctrl+O, press Enter to confirm, then exit nano with Ctrl+X.

Not sure you edited the right section?

The file has multiple jail sections. Make sure you edited the one labelled [sshd] specifically, not [ssh] or [DEFAULT]. Reopen the file and search again if you are unsure.

help me configure the sshd jail in Fail2Ban

Start Fail2Ban and verify the SSH jail is active
  • Enable Fail2Ban so it starts automatically on boot and start it immediately:
sudo systemctl enable --now fail2ban

What does --now do?

The --now flag combines two actions into one command. It enables the service for future boots AND starts it right now. Without it, you would need to run systemctl enable and systemctl start separately.

  • Verify Fail2Ban is running by checking its service status:
sudo systemctl status fail2ban

You should see active (running) in green text. Press q to exit the status view.

  • Now confirm the SSH jail specifically is active:
sudo fail2ban-client status sshd

You should see output showing the sshd jail with Currently banned: 0 and Total banned: 0. This confirms Fail2Ban is watching your SSH port and ready to act.

Seeing "failed" or "no such jail"?

If the service failed to start, check for config syntax errors by reviewing the Fail2Ban log:

sudo journalctl -u fail2ban --since "5 minutes ago"

Common causes: a typo in jail.local (like a missing = sign) or the [sshd] header being misspelled.

help me fix Fail2Ban not starting

Test brute-force protection by triggering a ban

A security tool you cannot test is a security tool you cannot trust. You will now intentionally trigger Fail2Ban by failing multiple SSH logins, then verify the ban and lift it.

Since password authentication is currently disabled, you need to temporarily re-enable it so you can type wrong passwords and trigger the failure count.

  • In your VM terminal (logged in as securadmin), open the SSH config:
sudo nano /etc/ssh/sshd_config
  • Find the line PasswordAuthentication no and change it to PasswordAuthentication yes.
  • Save with Ctrl+O, press Enter, then exit with Ctrl+X.
  • Restart SSH to apply the change:
sudo systemctl restart ssh

Now switch to your macOS Terminal to simulate a brute-force attack.

  • Open a new Terminal window on your Mac.
  • Attempt to SSH into the server with a wrong password by running:
ssh -p 2222 -o PreferredAuthentications=password securadmin@[[VMIP="your VM's IP address"]]
  • When prompted for a password, type anything wrong and press Enter.
  • Repeat this process 3 times total (run the command again each time it disconnects).

What is PreferredAuthentications=password?

This flag forces the SSH client to use password authentication only, skipping your key. Without it, SSH would succeed using your existing key and you would never trigger a failure.

After the third failed attempt, your next connection attempt should be refused or hang. That means Fail2Ban has banned your Mac's IP.

  • Switch back to your VM terminal and check the ban status:
sudo fail2ban-client status sshd

You should see Currently banned: 1 and your Mac's IP address listed under Banned IP list.

  • Unban your Mac's IP so you can reconnect. Replace the IP with the one shown in the banned list:
sudo fail2ban-client set sshd unbanip [[BANNEDIP="the banned IP from the output above"]]
  • Confirm the ban was lifted by running sudo fail2ban-client status sshd again. The banned count should be back to 0.

What just happened?

Fail2Ban detected 3 failed logins from the same IP within your 10-minute findtime window. It added a UFW rule to block that IP for 3600 seconds (1 hour).

On a real server, you would not unban the attacker. The ban expires automatically after bantime seconds. You only lifted it now because the "attacker" was your own Mac.

Finally, re-lock your SSH configuration now that testing is complete.

  • In your VM terminal, reopen the SSH config:
sudo nano /etc/ssh/sshd_config
  • Change PasswordAuthentication yes back to PasswordAuthentication no.
  • Save with Ctrl+O, press Enter, then exit with Ctrl+X.
  • Restart SSH to re-apply the hardened setting:
sudo systemctl restart ssh
  • Verify password auth is disabled again:
sudo sshd -T | grep passwordauthentication

You should see passwordauthentication no in the output, confirming your server is locked down again.

Can't SSH from your Mac after the test?

If your Mac is still banned, run the unban command from the VM terminal using your Mac's IP. Then try connecting with your key again.

If SSH won't accept your key after re-disabling password auth, confirm your public key is still in ~/.ssh/authorized_keys on the server by running cat ~/.ssh/authorized_keys.

help me reconnect after Fail2Ban testing

Your server now has automated brute-force protection. Any IP that fails 3 login attempts within 10 minutes gets blocked for a full hour. Next up, you will reduce your server's attack surface by disabling unnecessary services and locking down file permissions.

Disable Unnecessary Services and Harden File Permissions

Your server now has Fail2Ban actively blocking brute-force attempts. But there is another angle attackers exploit: unnecessary services.

Every running service is a potential entry point for an attacker. A printing service on a headless server, or a network discovery daemon broadcasting your machine's presence, adds risk for zero benefit. In this step, you will reduce your server's attack surface by disabling unneeded services, locking down file permissions on sensitive configs, and auditing programs that run with elevated privileges.

In this step, get ready to:
  • Identify and disable unnecessary services to shrink your attack surface.
  • Harden file permissions on critical system files.
  • Audit and fix dangerous SUID file permissions.
Identify and disable unnecessary services

A fresh Ubuntu Server installation runs several services by default. Some of these are only useful for desktop environments or local network discovery. On a server, they just widen the window for attackers.

  • List all running services by running this command:
systemctl list-units --type=service --state=running

What does this command do?

This lists every service that is currently active on your server. You will see system-critical services like ssh and systemd alongside potentially unnecessary ones like printing or mDNS discovery.

Look through the list for services your server does not need. Two common candidates on Ubuntu Server are cups (the printing service) and avahi-daemon (mDNS network discovery). A headless server has no printers and does not need to broadcast its presence on the local network.

  • Disable the CUPS printing service by running:
sudo systemctl disable --now cups
  • Disable the Avahi mDNS discovery daemon by running:
sudo systemctl disable --now avahi-daemon

What does disable --now do?

The --now flag combines two actions into one command. It stops the service immediately AND prevents it from starting again on future reboots.

Without --now, the service would keep running until the next reboot. With it, the change takes effect instantly.

  • Verify your running service count dropped by running:
systemctl list-units --type=service --state=running | wc -l

You should see a lower number than before. Fewer services means a smaller attack surface.

Seeing "Unit cups.service not found"?

On a minimal Ubuntu Server install, cups or avahi-daemon may not be installed at all. This is actually good news. It means the service was never present on your server in the first place. You can safely skip that disable command and move on.

Help me figure out which services are safe to disable on my server.

Harden sensitive file permissions

Linux file permissions control who can read, write, and execute every file on the system. Sensitive configuration files like your SSH config and the password shadow file should be locked down so that only the root user can access them.

  • Set restrictive permissions on your three most sensitive files by running these commands:
sudo chmod 600 /etc/ssh/sshd_config
sudo chmod 600 /etc/shadow
sudo chmod 644 /etc/passwd

What do the permission numbers mean?

Each digit represents permissions for a different group: owner, group, and others. The values are: read (4), write (2), and execute (1). You add them together to set the permission level.

So 600 means the owner can read and write (4+2=6), but group and others get nothing (0, 0). Only root can see these files.

And 644 means the owner can read and write (6), while group and others can only read (4, 4). /etc/passwd needs to be readable by all users because programs use it to look up usernames and home directories. It does not contain actual passwords.

  • Verify your permission changes by running:
ls -la /etc/ssh/sshd_config /etc/shadow /etc/passwd

You should see -rw------- for sshd_config and /etc/shadow, and -rw-r--r-- for /etc/passwd.

Permissions don't look right?

Double-check you typed the numeric values correctly. A common mistake is swapping 600 and 644. If you accidentally set /etc/passwd to 600, other users and services will not be able to read it, which can cause login issues. Fix it immediately with sudo chmod 644 /etc/passwd.

Help me fix file permissions on my Linux server.

Audit and remove dangerous SUID permissions

Some programs on Linux have a special permission bit called SUID (Set User ID). When a program has the SUID bit set, it runs with the privileges of the file's owner (usually root), regardless of who executes it. Attackers love SUID binaries because exploiting one gives them instant root access.

  • Find all files with the SUID bit set by running:
sudo find / -perm -4000 -type f 2>/dev/null

What does this command do?

This searches the entire filesystem (/) for files (-type f) that have the SUID permission bit set (-perm -4000). The 2>/dev/null part hides "permission denied" errors for directories you cannot read, so the output stays clean.

You will see a list of file paths. Some are essential system binaries that need SUID to function (like /usr/bin/sudo, /usr/bin/passwd, and /usr/bin/su). Others may be unnecessary and represent a security risk.

Review the list carefully. Programs like sudo, passwd, su, and mount need SUID to work correctly. If you see any programs you do not recognize or do not need, you can remove their SUID bit.

  • Remove the SUID bit from an unnecessary file by running:
sudo chmod u-s [[SUID_FILE="/path/to/unnecessary/suid/file"]]

Which SUID files are safe to remove?

Never remove SUID from /usr/bin/sudo, /usr/bin/su, /usr/bin/passwd, /usr/bin/newgrp, or /usr/bin/chsh. These are essential for user management and privilege escalation that you depend on.

Common safe candidates to remove SUID from include programs you never use, like /usr/bin/chfn (change finger info) or /usr/bin/umount if your server never mounts external filesystems. When in doubt, leave the SUID bit alone.

Accidentally removed SUID from sudo?

If you removed the SUID bit from /usr/bin/sudo and can no longer run sudo commands, you will need to use the root account directly. Log in as your original admin user (the one you created during Ubuntu installation) and run su - to become root, then restore the bit with chmod u+s /usr/bin/sudo.

Help me recover from removing SUID from the wrong file.

Your server now has a much smaller attack surface. You have shut down unnecessary services, locked down sensitive files, and audited the programs that run with elevated privileges. Next up, you will learn how to monitor your server and review logs for suspicious activity.

Monitor Your Server and Review Logs

Your server is now hardened with restricted SSH access, a firewall, Fail2Ban, disabled services, and locked-down file permissions. But security is not a one-time task.

A well-configured server still needs ongoing monitoring. In this step, you will learn how to read authentication logs, spot suspicious processes, and create a reusable hardening checklist that documents everything you have done.

In this step, get ready to:
  • Review authentication logs for suspicious login activity.
  • Monitor system resources and running processes.
  • Create a reusable hardening checklist documenting every change you made.
Check authentication logs for suspicious activity

Authentication logs are the first place to look when investigating whether someone has attempted to access your server. Every SSH login attempt (successful or failed) is recorded by the system journal.

  • Check recent SSH activity by running this command:
sudo journalctl -u ssh --since "1 hour ago"

What does this command do?

journalctl queries the systemd journal, which is where Ubuntu stores all service logs. The -u ssh flag filters to only SSH-related entries. The --since flag limits results to the last hour so you are not overwhelmed by old data.

You should see entries showing your own successful logins. Look for lines containing "Accepted publickey" which confirm key-based authentication is working.

  • View the history of all successful logins by running:
last
  • View failed login attempts by running:
sudo lastb

What is the difference between last and lastb?

last reads from /var/log/wtmp and shows successful logins along with session durations and reboots. lastb reads from /var/log/btmp and shows failed login attempts. It requires sudo because failed login data is sensitive security information.

  • Check who is currently logged into the server right now by running:
who
  • Get a more detailed view of logged-in users and what they are doing by running:
w

What is the difference between who and w?

who shows a concise list of active sessions: username, terminal, and login time. w shows everything who does, plus system load averages, idle time, and the specific command each user is currently running.

You should see your own session listed under securadmin. If you ever see an unfamiliar user here, that is an immediate red flag worth investigating.

Seeing empty output from lastb?

If lastb shows no results, that is actually good news. It means no one has attempted a failed login since the server was set up. If you tested Fail2Ban with intentional failed logins in the previous step, you should see those entries here.

Help me understand my authentication logs.

Monitor system resources and processes

Attackers who gain access to a server often run crypto miners, botnets, or other resource-hungry processes. Knowing your server's normal resource usage makes it easy to spot anything unusual.

  • View the top 20 memory-consuming processes by running:
ps aux --sort=-%mem | head -20

What does this command do?

ps aux lists all running processes with details like CPU usage, memory usage, and the command that started them. The --sort=-%mem flag sorts by memory usage (highest first). Piping to head -20 limits the output to just the top 20 results so you can quickly identify resource hogs.

  • Check disk usage across all mounted file systems by running:
df -h
  • Check memory usage by running:
free -h

What do df and free tell you?

df -h shows disk space usage in human-readable format (GB/MB). A server filling up its disk is a warning sign of log flooding or unauthorized file storage.

free -h shows RAM usage. If memory is nearly full on a server that should be idle, something unexpected is running.

  • Look for unknown or unexpected processes by running this pipeline:
ps aux | grep -v "^\[" | awk '{print $11}' | sort | uniq

What does this pipeline do?

This command lists every unique program name running on your server. It filters out kernel threads (lines starting with [), extracts just the command name column, then sorts and deduplicates the results. Scan the list for anything you do not recognize.

On a freshly hardened server, you should recognize everything in the list. Common entries include sshd, systemd, fail2ban-server, and basic system utilities.

Seeing processes you do not recognize?

Many system services have unfamiliar names. Before removing anything, search for the process name online to confirm whether it is a legitimate system service. Never kill a process unless you are sure it is unauthorized.

Help me identify unknown processes on my server.

Create your hardening checklist

Every security professional keeps documentation of what they have done and why. This checklist becomes your reusable security playbook that you can apply to any new server.

  • Create your hardening checklist document by running:
nano ~/hardening-checklist.md
  • Type the following template into the file, filling in details based on everything you configured in this project:
# Server Hardening Checklist

## 1. System Update
- Command: sudo apt update && sudo apt upgrade -y
- Reason: Patches known vulnerabilities in all installed packages

## 2. Secure Admin User
- Command: sudo adduser securadmin && sudo usermod -aG sudo securadmin
- Reason: Avoid using root directly; dedicated admin with sudo is safer

## 3. SSH Hardening
- Config file: /etc/ssh/sshd_config
- Changes made:
  - PermitRootLogin no (blocks direct root SSH access)
  - PasswordAuthentication no (forces key-based auth only)
  - PubkeyAuthentication yes (enables key-based auth)
  - MaxAuthTries 3 (limits brute-force attempts per connection)
  - Port 2222 (moves SSH off the default port)
- Reason: SSH is the #1 attack vector on public servers

## 4. Firewall (UFW)
- Commands: sudo ufw default deny incoming, sudo ufw allow 2222/tcp
- Reason: Blocks all traffic except explicitly allowed ports

## 5. Fail2Ban
- Config: /etc/fail2ban/jail.local
- Settings: maxretry=3, bantime=3600, findtime=600
- Reason: Automatically bans IPs after repeated failed logins

## 6. Service Hardening
- Disabled: cups, avahi-daemon
- Reason: Fewer running services = smaller attack surface

## 7. File Permissions
- /etc/ssh/sshd_config: 600 (owner read/write only)
- /etc/shadow: 600 (owner read/write only)
- /etc/passwd: 644 (owner read/write, others read)
- Reason: Restricts access to sensitive configuration files

## 8. SUID Audit
- Command: sudo find / -perm -4000 -type f
- Reason: SUID programs run with elevated privileges; remove unnecessary ones

## 9. Monitoring Commands
- Logs: sudo journalctl -u ssh --since "1 hour ago"
- Login history: last, sudo lastb
- Current users: who, w
- Processes: ps aux --sort=-%mem | head -20
- Disk: df -h
- Memory: free -h

What makes a good hardening checklist?

Each entry documents three things: what you changed, the exact command used, and why the change improves security. This lets you (or a colleague) reproduce the same hardening on a new server in minutes.

Feel free to add notes about anything specific to your setup. The more detail you include, the more useful this document becomes as a reference.

  • Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
  • Exit nano by pressing Ctrl+X.
  • Confirm the file was created by running:
ls ~/hardening-checklist.md

You should see /home/securadmin/hardening-checklist.md listed in the output.

Now verify that your key hardening configurations are still in place. This final check confirms your server is locked down as expected.

  • Verify your SSH hardening settings by running:
sudo sshd -T | grep -E "permitroot|passwordauth|port"

You should see output confirming permitrootlogin no, passwordauthentication no, and port 2222.

  • Verify your firewall rules are active by running:
sudo ufw status

You should see Status: active with port 2222/tcp listed under the allowed rules.

SSH settings not showing the expected values?

If sudo sshd -T shows different values than expected, your /etc/ssh/sshd_config file may have a syntax issue or a duplicate directive. Open the file with sudo nano /etc/ssh/sshd_config and check that each setting appears only once and is not commented out with a # at the start of the line.

Help me fix my SSH configuration.

Your server is now hardened, monitored, and fully documented. You have transformed a blank Ubuntu installation into a locked-down system using the same commands that professionals rely on every day.

Secret mission

Automated Security Monitoring with Lynis

You have already hardened your server manually. Now, take it further by running a professional security audit with Lynis to measure your work, then set up automated log monitoring so your server watches itself around the clock.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally on your Mac, so there are no ongoing cloud costs.

Resources you used:

  • Ubuntu Server 24.04 LTS virtual machine in UTM (includes all server-side configurations, Fail2Ban, UFW, Lynis, and your hardening checklist).
  • UTM application installed on macOS.
  • SSH key pair on your macOS host (~/.ssh/id_ed25519 and ~/.ssh/id_ed25519.pub).
  • Ubuntu Server ISO file in your Downloads folder.

Keep everything running

No action needed. Choose this if you are still actively experimenting with your hardened server or plan to add more security configurations.

  • Your VM stays in UTM, ready to start anytime.
  • Your SSH keys remain on your Mac for quick access.
  • The Ubuntu ISO stays in your Downloads folder in case you want to create another VM.

Pause - I'll come back to this later

Shut down your VM to free up memory and CPU on your Mac. Your VM files stay on disk so you can resume later.

  • In your VM terminal, run the following command to shut down gracefully:
sudo shutdown now
  • If the VM is unresponsive, click the stop button in UTM's toolbar instead.
  • To resume later, open UTM and click the play button on your VM.

Delete - I don't want to use this again

Remove all project resources from your Mac entirely. This frees up disk space and removes all traces of the project.

  • Shut down the VM if it is still running. In your VM terminal, run:
sudo shutdown now
  • In UTM, right-click your Ubuntu Server VM and select Delete. Confirm the deletion when prompted. This removes the VM and its virtual disk.
  • Remove the SSH key pair you generated for this project by running this in a macOS Terminal window:
rm ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
  • Delete the Ubuntu Server ISO from your Downloads folder:
rm ~/Downloads/ubuntu-*-live-server-arm64.iso
  • To remove UTM itself, open Finder, go to Applications, and drag UTM to the Trash. Then empty the Trash.

Nice Work!

Nice Work!

You just transformed a blank Ubuntu Server into a hardened security fortress! These are the exact commands and techniques that system administrators use to protect production servers every day.

You've learned how to:

  • Hardened SSH access with key-based authentication, a custom port, and restricted settings using sshd_config, ssh-keygen, and ssh-copy-id.
  • Configured a UFW firewall to control all network traffic and deployed Fail2Ban to automatically block brute-force attackers.
  • Audited running services with systemctl, hardened file permissions on sensitive system files with chmod, and monitored logs for security threats using journalctl and ps.
  • Secret Mission: Ran a professional security audit with Lynis and set up automated log monitoring with a cron job.

Ready to quiz yourself?