Build a Hardened Linux Server on AWS
Harden an Ubuntu EC2 server with SSH lockdown, UFW firewall, and login monitoring.
Introduction
30 Second Summary
Every server connected to the internet is a target. Automated bots scan for weak passwords and open ports within minutes of a new machine going live.
In this project, you will launch an Ubuntu server on AWS and harden it against real-world attacks. You'll disable insecure defaults, configure a firewall, and build a monitoring script that detects suspicious login attempts.
What You'll Build
A production-style hardened Linux server on EC2 with SSH lockdown, firewall configuration, and automated security monitoring.
By the end of this project, you'll have:
- SSH hardened with key-only authentication and root login disabled.
- A UFW firewall configured to deny all incoming traffic except rate-limited SSH.
- A bash script that parses auth.log and reports failed login attempts with attacker IP addresses.
- Secret Mission: Automated IP banning with Fail2Ban after repeated failed SSH attempts.
Do I need Linux experience?
No prior Linux experience is required. This project starts from zero and walks you through every command. You just need an AWS account that is free tier eligible.
Launch and Connect to Your EC2 Instance
Every server you work with in your career starts the same way. You provision a machine, lock it down, and connect to it remotely.
In this step, you'll launch an Ubuntu 24.04 server on AWS EC2 and connect to it over SSH. This gives you a fresh Linux environment ready to be hardened in the steps ahead.
In this step, get ready to:
- Launch a t2.micro Ubuntu 24.04 LTS instance on AWS EC2.
- Create an RSA key pair and configure a security group for SSH access.
- Connect to your instance from Windows Terminal and verify the setup.
Launch your Ubuntu EC2 instance
An EC2 instance is a virtual server in the cloud. You'll configure yours with Ubuntu 24.04 LTS, the current long-term support release with 5 years of security updates.
- Open your browser and go to the AWS Management Console at https://console.aws.amazon.com.
- Sign in with your AWS account credentials.
- In the search bar at the top, type EC2 and select EC2 from the results.
- Click Launch instance from the EC2 dashboard.
The launch instance wizard opens. Configure the following settings:
- Under Name and tags, enter hardened-server as the name.
- Under Application and OS Images (Amazon Machine Image), click Quick Start, then select Ubuntu.
- From the Amazon Machine Image (AMI) dropdown, select the Ubuntu Server 24.04 LTS image marked Free tier eligible.
- Under Instance type, select t2.micro (it should be selected by default).
What is an AMI?
An AMI (Amazon Machine Image) is a template containing the operating system and software your instance starts with. Selecting Ubuntu 24.04 LTS means your server boots with a fresh Ubuntu installation ready for you to configure.
Now create your SSH key pair:
- Under Key pair (login), click Create new key pair.
- Enter a name for your key pair (for example, hardened-server-key).
- Set Key pair type to RSA.
- Set Private key file format to .pem.
- Click Create key pair. Your browser automatically downloads the .pem file.
What is a key pair?
A key pair consists of a public key (stored on your EC2 instance) and a private key (the .pem file you just downloaded). When you SSH into your server, your private key proves your identity without needing a password.
Keep this .pem file safe. Anyone with this file can access your server.
Finally, configure the security group to restrict who can connect:
- Under Network settings, click Edit.
- Make sure Allow SSH traffic from is checked.
- Change the source dropdown from Anywhere to My IP. This restricts SSH access to your current IP address only.
- Leave all other settings at their defaults.
- In the Summary panel on the right, click Launch instance.
Why My IP instead of Anywhere?
Selecting My IP means only your current public IP address can SSH into this server. If you left it on Anywhere, every IP on the internet could attempt to connect. This is the first layer of defense you're applying.
- Click View all instances to go to the Instances dashboard.
- Wait for your instance's Instance state to show Running and the Status check to show passing. This takes 1-2 minutes.
Connect to your instance via SSH
Your instance is running in the cloud. Now you need to connect to it from your local machine using SSH and your downloaded key file.
- On the Instances dashboard, click on your instance's Instance ID to open its details.
- Copy the Public IPv4 address shown in the instance details. You'll need this for the SSH command.
- Press the Windows key to open your search bar.
- Type Terminal and press Enter to open Windows Terminal.
macOS or Linux users
If you're on macOS, press Cmd+Space and type Terminal to open your terminal. The SSH commands below work the same way on all platforms.
Before connecting, you need to set the correct permissions on your key file so SSH accepts it.
- Navigate to your Downloads folder where the .pem file was saved by running:
cd ~/Downloads
- Connect to your EC2 instance by running:
ssh -i [[PEMPATH="your-key-file.pem"]] ubuntu@[[EC2_IP="your EC2 public IPv4 address"]]
What does this command do?
- ssh opens a secure connection to a remote server.
- -i specifies the private key file for authentication.
- ubuntu is the default user on Ubuntu EC2 instances.
- The address after @ is your instance's public IP.
The first time you connect, you'll see a message asking if you want to continue connecting. Type yes and press Enter.
Permission denied or unprotected key file error?
If you see a "Permissions for your key are too open" error, your key file permissions need to be restricted. On Windows, right-click the .pem file, go to Properties then Security then Advanced. Remove all users except your own account and disable inheritance.
On macOS/Linux, run chmod 400 your-key-file.pem before retrying the SSH command.
help me fix the SSH permission error for my .pem key file
Verify your connection works
You should now see a command prompt that looks different from your local terminal. This means you're connected to your remote Ubuntu server.
- Confirm you're running Ubuntu 24.04 by running:
cat /etc/os-release
- Confirm which user you're logged in as by running:
whoami
What should I see?
The cat /etc/os-release command should display version information including VERSION_ID="24.04" and PRETTY_NAME="Ubuntu 24.04...". The whoami command should output ubuntu.
Connection timed out?
If your SSH connection hangs or times out, your security group may not be allowing traffic from your current IP. Go back to the EC2 console, select your instance, click the Security tab, and click on the security group link. Verify the inbound rule shows your current public IP for port 22.
Your public IP may have changed since you launched the instance (common on home networks). Update the security group rule with your new IP.
help me troubleshoot why my SSH connection to EC2 is timing out
Your Ubuntu 24.04 server is live and you're connected. Next up, you'll create a dedicated admin user and start locking down access.
Create a Secure Admin User and Lock Down Root
Your EC2 instance is live and you are connected as the default ubuntu user. That is a great start.
Running everything as a single default user is risky. If that account is compromised, the attacker has full access. In this step, you will create a dedicated admin user with sudo privileges and copy your SSH key over so they can log in securely.
In this step, get ready to:
- Create a new admin user and grant sudo privileges.
- Copy your SSH key to the new user.
- Test login as the new user and verify sudo works.
Create a new admin user and grant sudo access
The ubuntu user is a shared default account that every Ubuntu EC2 instance ships with. A dedicated admin user gives you accountability and lets you disable the default account later.
- Create a new user called devops-admin by running this command:
sudo adduser devops-admin
What will this command ask me?
The adduser command is interactive. It will prompt you to set a password (enter it twice), then ask for Full Name, Room Number, Work Phone, and Home Phone.
Choose a password you can remember. For the other fields, you can press Enter to leave each one blank. When asked to confirm the information is correct, type Y and press Enter.
- Add the new user to the sudo group by running:
sudo usermod -aG sudo devops-admin
What does this command do?
The -aG flag means "append to group." This adds devops-admin to the sudo group without removing them from any other groups. Members of the sudo group can run commands as root.
- Verify the user was added to the sudo group by running:
groups devops-admin
You should see output like devops-admin : devops-admin sudo confirming the user belongs to both the devops-admin and sudo groups.
Don't see sudo in the output?
Double-check that you typed sudo (not sudoer or sudoers) in the usermod command. Run sudo usermod -aG sudo devops-admin again and then re-check with groups devops-admin.
help me fix my user group assignment
Copy your SSH key to the new user
Your devops-admin user exists, but they cannot log in over SSH yet. SSH looks for an authorized_keys file in the user's .ssh directory. You need to copy the key from the ubuntu user to devops-admin.
- Create the .ssh directory for the new user and copy the key file by running these commands:
sudo mkdir -p /home/devops-admin/.ssh
sudo cp /home/ubuntu/.ssh/authorized_keys /home/devops-admin/.ssh/
- Fix the ownership and permissions so only devops-admin can access these files by running:
sudo chown -R devops-admin:devops-admin /home/devops-admin/.ssh
sudo chmod 700 /home/devops-admin/.ssh
sudo chmod 600 /home/devops-admin/.ssh/authorized_keys
Why do permissions matter here?
SSH is very strict about file permissions. If anyone other than the owner can read the authorized_keys file, SSH will refuse to use it.
Permission 700 means only the owner can read, write, and enter the .ssh directory. Permission 600 means only the owner can read and write the key file. The chown command makes devops-admin the owner of both.
- Verify the permissions are correct by running:
ls -la /home/devops-admin/.ssh/
You should see devops-admin devops-admin as the owner and group for both entries. The authorized_keys file should show -rw------- (600) and the .ssh directory should show drwx------ (700).
Seeing permission denied errors?
Make sure every command starts with sudo. The ubuntu user does not own the devops-admin home directory, so you need root privileges to create files there.
help me fix SSH key copy permissions
Test the new user login
The real test is whether devops-admin can actually SSH in using your key. You will open a brand new terminal window to test this while keeping your current session open as a safety net.
- Open a new terminal window. Do NOT close your current SSH session.
- Connect as devops-admin by running this command in the new window:
ssh -i [[PEMPATH="path/to/your-key.pem"]] devops-admin@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
Why keep the old session open?
If something went wrong with the key copy, you could lock yourself out. Your original session as ubuntu is your backup. Only close it after you have confirmed devops-admin can log in successfully.
- Once connected as devops-admin, confirm sudo works by running:
sudo whoami
You will be prompted for the password you set during adduser. Enter it. The output should be root. This confirms devops-admin has full administrative privileges.
Connection refused or key rejected?
Check that the .pem file path in your command points to the same key you used to connect as ubuntu. Also verify the ownership and permissions by switching to your original session and running ls -la /home/devops-admin/.ssh/.
help me troubleshoot SSH login as devops-admin
Your dedicated admin user is ready and verified. In the next step, you will harden the SSH configuration to lock out root login entirely and restrict access to only devops-admin.
Harden SSH Configuration
In Step 2, you created a dedicated admin user with full sudo access and verified they can log in over SSH. Now it's time to lock the front door.
SSH is the only way into your server. If an attacker can brute-force a password or log in as root, your server is compromised. In this step, you'll configure SSH to reject every login method except key-based authentication from your specific admin user.
In this step, get ready to:
- Back up the SSH config and create a hardening configuration file.
- Restart the SSH service to apply your changes.
- Test that your admin user can connect and the default user is blocked.
Back up and create the SSH hardening config
Before changing any config file on a Linux server, you always create a backup first. If something breaks, you can restore the original in seconds.
- Back up the original SSH config by running this command:
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
- Confirm the backup was created by running:
ls /etc/ssh/sshd_config.backup
You should see the file path printed back to you, confirming the backup exists.
Why not edit sshd_config directly?
Ubuntu 24.04 includes the line Include /etc/ssh/sshd_config.d/*.conf at the top of its main SSH config. Files in the sshd_config.d directory take precedence due to first-match-wins semantics.
This means you can add a small, focused config file without touching the 80+ line main config. If anything goes wrong, you just delete your one file instead of restoring the entire config.
Now you'll create a hardening config file that locks down SSH. The file needs five directives that together block the most common attack vectors.
- Open a new file in nano by running this command:
sudo nano /etc/ssh/sshd_config.d/hardening.conf
- Paste the following content into the editor:
# SSH Hardening Configuration
# DevSecOps Project 1
# Disable root login
PermitRootLogin no
# Disable password authentication (key-only)
PasswordAuthentication no
# Limit authentication attempts
MaxAuthTries 3
# Reduce login grace time from 120s to 20s
LoginGraceTime 20
# Only allow specific users
AllowUsers devops-admin
What does each directive do?
- PermitRootLogin no prevents anyone from logging in directly as root. This forces accountability because every action traces back to a named user.
- PasswordAuthentication no disables password-based login entirely. Only SSH key holders can connect, which eliminates brute-force password attacks.
- MaxAuthTries 3 disconnects a client after 3 failed authentication attempts per session.
- LoginGraceTime 20 gives a connecting client only 20 seconds to authenticate (down from the default 120 seconds). This prevents attackers from holding connections open.
- AllowUsers devops-admin restricts SSH access to only the devops-admin user. Every other user on the system is blocked from SSH login.
- Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
- Exit nano by pressing Ctrl+X.
- Verify the file was saved by running:
cat /etc/ssh/sshd_config.d/hardening.conf
You should see all five directives printed back to you exactly as you pasted them.
File is empty or not found?
- Make sure you pressed Ctrl+O (the letter O, not zero) to save before exiting nano.
- Check the path is exactly /etc/ssh/sshd_config.d/hardening.conf. A typo in the directory name means SSH won't read the file.
- If you need to redo it, run the sudo nano command again and paste the content fresh.
Why is my hardening.conf file empty after saving in nano?
✔️ Awesome, I've got everything!
Great. Double check you see all five directives when you run cat on the file, then move on to applying the changes.
ⓧ I'd like to double check the full code
Here's the complete content that should be in /etc/ssh/sshd_config.d/hardening.conf:
# SSH Hardening Configuration
# DevSecOps Project 1
# Disable root login
PermitRootLogin no
# Disable password authentication (key-only)
PasswordAuthentication no
# Limit authentication attempts
MaxAuthTries 3
# Reduce login grace time from 120s to 20s
LoginGraceTime 20
# Only allow specific users
AllowUsers devops-admin
Apply the changes and verify SSH is running
Do NOT close your current terminal session
Keep your current SSH session open as a safety net. If something goes wrong with the config, this existing connection stays alive and lets you fix the problem. Only close it after you confirm the new config works from a separate terminal.
- Restart the SSH service to load your new config by running:
sudo systemctl restart ssh
- Confirm the service restarted successfully by running:
sudo systemctl status ssh
You should see active (running) in green text. This confirms SSH accepted your config without errors.
SSH service failed to start?
- If you see failed instead of active (running), there's a typo in your config file. Check for misspelled directives.
- Run sudo sshd -t to test the config for syntax errors. It will tell you the exact line that's wrong.
- If you can't fix it, restore the backup: sudo rm /etc/ssh/sshd_config.d/hardening.conf then sudo systemctl restart ssh.
Why is my SSH service failing after I edited the config?
Test the hardened configuration
Your config is live, but you need to prove it works. You'll open a brand new terminal window and test two scenarios: your admin user can still get in, and the default ubuntu user is locked out.
- Open a brand new terminal window. Do NOT close your existing session.
- Connect as devops-admin by running:
ssh -i [[PEMPATH="path to your .pem key file"]] devops-admin@[[EC2_IP="your EC2 public IPv4 address"]]
You should land in a shell as devops-admin. This confirms your admin user still has access through the hardened config.
- Confirm sudo still works by running:
sudo whoami
You should see root printed back. Your admin user has full privileges.
Now test that the ubuntu user is blocked.
- Open another new terminal window and try connecting as the ubuntu user by running:
ssh -i [[PEMPATH="path to your .pem key file"]] ubuntu@[[EC2_IP="your EC2 public IPv4 address"]]
The connection should be denied. You'll see a Permission denied error. This proves your AllowUsers directive is working correctly.
Why is this important?
On a production server, attackers often try common usernames like ubuntu, admin, and root. Your AllowUsers directive means none of those common guesses will ever work. Only the specific user you've authorized can connect.
devops-admin connection also denied?
- Check that AllowUsers devops-admin is spelled exactly right in your config. The username is case-sensitive.
- Make sure you're using the same .pem key file that you used in Step 2 to set up devops-admin's SSH keys.
- Go back to your original session (which should still be open) and run cat /etc/ssh/sshd_config.d/hardening.conf to verify the file contents.
Why can't I connect as devops-admin after hardening SSH?
- You can now close your original ubuntu session. From now on, you'll use devops-admin for all SSH access to this server.
Your SSH configuration is locked down tight. Attackers can't guess passwords, can't log in as root, and can't use common usernames. Next up, you'll add a firewall to control which network traffic can even reach your server in the first place.
Configure UFW Firewall
Your SSH configuration is hardened. But right now, every other network port on your server is still wide open to the internet.
UFW (Uncomplicated Firewall) is Ubuntu's built-in tool for managing network traffic rules. In this step, you'll configure it to block all incoming traffic except SSH, with rate limiting to stop brute-force attacks.
In this step, get ready to:
- Set UFW default policies to deny all incoming traffic.
- Add a rate-limited SSH rule to protect against brute-force attacks.
- Enable and verify the firewall is active.
Check UFW status and set default policies
UFW comes pre-installed on Ubuntu 24.04 but is disabled by default. You'll set the default policies first to establish a "deny everything incoming" baseline.
- Check UFW's current status by running this command:
sudo ufw status
You should see Status: inactive in the output. This confirms UFW is installed but not yet running.
- Set the default policies to deny all incoming and allow all outgoing traffic by running these commands:
sudo ufw default deny incoming
sudo ufw default allow outgoing
What do these defaults do?
These two commands establish the baseline security posture for your server.
- sudo ufw default deny incoming blocks all traffic trying to reach your server unless you explicitly allow it.
- sudo ufw default allow outgoing lets your server make outbound requests (for updates, downloads, etc.).
You won't see the firewall blocking traffic yet. UFW needs to be enabled first, which you'll do next.
Seeing 'command not found'?
- Make sure you include sudo before each command. UFW requires root privileges.
- Confirm you are logged in as devops-admin by running whoami.
Why isn't my UFW command working?
Allow SSH with rate limiting and enable the firewall
Do not enable UFW without allowing SSH first
If you enable UFW before adding an SSH rule, the "deny incoming" default will block your SSH connection. You will be permanently locked out of your server. Always add the SSH rule before enabling.
- Allow SSH on port 22 with rate limiting by running this command:
sudo ufw limit 22/tcp comment 'Rate-limited SSH'
What is rate limiting?
The limit keyword tells UFW to deny connections from any IP address that attempts 6 or more connections within 30 seconds. This protects against brute-force attacks where an attacker tries thousands of SSH login attempts in rapid succession.
Legitimate users connecting once or twice will never hit this limit. Attackers running automated scripts will get blocked quickly.
- Enable the firewall by running this command:
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. Because you already added the SSH rule, your connection will stay active.
Your SSH session should still be active after enabling. If you can still type commands, the firewall correctly allowed your SSH connection through.
Lost your SSH connection?
If your terminal froze or disconnected, the SSH rule may not have been added correctly before enabling UFW. To recover:
- Go to the AWS EC2 Console and stop then start your instance. UFW rules persist across reboots, so you may need to use EC2 Instance Connect from the AWS Console to regain access and fix the rules.
I got disconnected from my EC2 instance after enabling UFW. How do I recover?
Verify the firewall is active
- Verify the complete firewall configuration by running this command:
sudo ufw status verbose
You should see output confirming the firewall is active. Look for these key details:
- Status: active at the top.
- Default: deny (incoming), allow (outgoing) showing your baseline policies.
- A rule showing 22/tcp with LIMIT IN and Anywhere as the source.
Why is this better than just the Security Group?
Your AWS Security Group already restricts SSH to your IP. UFW adds a second layer of defense directly on the server itself. If the Security Group is ever misconfigured (or if your IP changes), UFW still blocks everything and rate-limits SSH.
This is called defense in depth. Production servers never rely on a single security control.
Not seeing the LIMIT rule?
- Check that you used limit (not allow) in your SSH rule command. If you used allow, the Action column will show ALLOW IN instead of LIMIT IN.
- Make sure you included verbose in the status command. Without it, UFW shows a simplified view.
My UFW status isn't showing the expected rules. How do I fix this?
Your server's network is now locked down. Only SSH traffic can reach it, and even that is rate-limited against brute-force attacks. Next up, you'll build a monitoring script that watches for suspicious login attempts.
Build a Login Monitor Script
Your server is now locked behind a firewall and hardened SSH config. But how do you know if someone is trying to break in?
Every SSH login attempt on your server gets recorded in /var/log/auth.log. In this step, you will write a bash script that parses this log file and generates a security report showing failed attempts, attacker IPs, and suspicious usernames.
In this step, get ready to:
- Create a bash script that parses SSH authentication logs.
- Run the script to generate a security report.
- Test detection by generating a failed login attempt.
Create the monitoring script
Linux stores every authentication event in a log file called /var/log/auth.log. This includes successful logins, failed password attempts, and invalid username guesses.
Your script will use standard Linux tools like grep, sort, and uniq to extract and summarize this data into a readable report.
- Open a new file in nano by running this command:
sudo nano /home/devops-admin/login-monitor.sh
- Paste the following script into the editor:
#!/bin/bash
# Login Monitor Script - Detects failed SSH login attempts
# DevSecOps Project 1: Linux Security Hardening
echo "========================================"
echo " SSH Login Security Report"
echo " Generated: $(date)"
echo "========================================"
echo ""
AUTH_LOG="/var/log/auth.log"
if [ ! -f "$AUTH_LOG" ]; then
echo "ERROR: $AUTH_LOG not found"
exit 1
fi
echo "[1] Failed SSH Login Attempts (Last 24 Hours)"
echo "------------------------------------------------"
FAILED_COUNT=$(grep "Failed password" "$AUTH_LOG" | grep "$(date --date='1 day ago' '+%b %d')\|$(date '+%b %d')" | wc -l)
echo "Total failed attempts: $FAILED_COUNT"
echo ""
echo "[2] Top Offending IP Addresses"
echo "------------------------------------------------"
grep "Failed password" "$AUTH_LOG" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "[3] Invalid User Attempts (Username Guessing)"
echo "------------------------------------------------"
grep "Invalid user" "$AUTH_LOG" | grep -oE 'Invalid user \S+' | sort | uniq -c | sort -rn | head -10
echo ""
echo "[4] Successful Logins"
echo "------------------------------------------------"
grep "Accepted" "$AUTH_LOG" | tail -5
echo ""
echo "[5] Current SSH Sessions"
echo "------------------------------------------------"
who
echo ""
echo "========================================"
echo " Report Complete"
echo "========================================"
- Save the file by pressing Ctrl+O, then press Enter to confirm the filename.
- Exit nano by pressing Ctrl+X.
What does this script do?
The script reads /var/log/auth.log and generates five sections of output.
- Section 1 counts failed password attempts from the last 24 hours using date filtering.
- Section 2 extracts IP addresses from failed attempts and ranks them by frequency.
- Section 3 finds invalid username guesses, which indicates attackers trying common usernames like admin or root.
- Section 4 shows recent successful logins so you can verify they are legitimate.
- Section 5 displays who is currently connected to the server.
- Verify the file was created by running:
ls -la /home/devops-admin/login-monitor.sh
You should see the file listed with its size and ownership details.
File not showing up?
- Make sure you pressed Ctrl+O (the letter O, not zero) to save before exiting nano.
- Check you included sudo before the nano command. Without it, you may not have write permission to the directory.
Still stuck? Help me figure out why my login-monitor.sh file wasn't saved in nano.
Make the script executable and run it
Linux files are not executable by default. You need to add the execute permission before you can run the script directly.
- Add execute permission to the script by running:
chmod +x /home/devops-admin/login-monitor.sh
- Run the script by executing:
./login-monitor.sh
You should see the SSH Login Security Report header followed by five numbered sections. The failed attempt count will likely be 0 if nobody has tried to connect with a wrong password yet.
Why is my failed count zero?
Your server is brand new and uses key-only authentication. Nobody has tried (and failed) to log in with a password yet. You will generate a test failure in the next substep to see the detection in action.
Seeing 'Permission denied' when running the script?
- Make sure you ran the chmod +x command first. Without it, the file is not marked as executable.
- Confirm you are in the right directory. Run cd /home/devops-admin first, then try ./login-monitor.sh again.
Need more help? Why am I getting permission denied when running my login-monitor.sh script?
Test detection with a failed login attempt
A security monitoring script is only useful if it actually catches suspicious activity. You will now deliberately fail a login to verify the script detects it.
- Open a new terminal window on your local machine. Keep your current SSH session open.
- Attempt to SSH in with a fake username that does not exist on the server. Run this from your local machine:
ssh -i [[PEMPATH="path to your .pem file"]] fakeuser@[[EC2_PUBLIC_IP="your EC2 public IPv4 address"]]
This connection will be rejected because fakeuser does not exist and is not in the AllowUsers list. That is exactly what you want.
- Switch back to your SSH session as devops-admin.
- Run the monitoring script again:
./login-monitor.sh
This time, Section 3 (Invalid User Attempts) should show fakeuser in the output. Your script successfully detected the unauthorized login attempt.
Don't see the failed attempt in the report?
- Make sure the failed SSH attempt was made to the correct EC2 public IP address.
- Run sudo grep "Invalid user" /var/log/auth.log to check if the attempt was logged at all. If nothing appears, the connection may not have reached the server.
- Your security group must allow SSH from your current IP. If your IP changed, update the inbound rule in the EC2 console.
Still not working? Help me figure out why my failed SSH attempt isn't showing in the login-monitor.sh report.
✔️ Awesome, I've got everything!
Great. Double check you have saved login-monitor.sh and confirmed the script detects your test failure.
ⓧ I'd like to double check the full code
Here is the complete login-monitor.sh script for comparison:
#!/bin/bash
# Login Monitor Script - Detects failed SSH login attempts
# DevSecOps Project 1: Linux Security Hardening
echo "========================================"
echo " SSH Login Security Report"
echo " Generated: $(date)"
echo "========================================"
echo ""
AUTH_LOG="/var/log/auth.log"
if [ ! -f "$AUTH_LOG" ]; then
echo "ERROR: $AUTH_LOG not found"
exit 1
fi
echo "[1] Failed SSH Login Attempts (Last 24 Hours)"
echo "------------------------------------------------"
FAILED_COUNT=$(grep "Failed password" "$AUTH_LOG" | grep "$(date --date='1 day ago' '+%b %d')\|$(date '+%b %d')" | wc -l)
echo "Total failed attempts: $FAILED_COUNT"
echo ""
echo "[2] Top Offending IP Addresses"
echo "------------------------------------------------"
grep "Failed password" "$AUTH_LOG" | grep -oE '([0-9]{1,3}\.){3}[0-9]{1,3}' | sort | uniq -c | sort -rn | head -10
echo ""
echo "[3] Invalid User Attempts (Username Guessing)"
echo "------------------------------------------------"
grep "Invalid user" "$AUTH_LOG" | grep -oE 'Invalid user \S+' | sort | uniq -c | sort -rn | head -10
echo ""
echo "[4] Successful Logins"
echo "------------------------------------------------"
grep "Accepted" "$AUTH_LOG" | tail -5
echo ""
echo "[5] Current SSH Sessions"
echo "------------------------------------------------"
who
echo ""
echo "========================================"
echo " Report Complete"
echo "========================================"
Secret mission
Install and Configure Fail2Ban
Your server is hardened, but what about automated defense? Fail2Ban monitors your auth.log in real time and automatically bans IP addresses that fail SSH login too many times. Install and configure it to ban any IP after 5 failed attempts.
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 uses the AWS Free Tier, so costs depend on your account age and usage.
Cost warning
If your AWS account was created before July 15, 2025, your t2.micro instance is free for 750 hours/month for 12 months. If your account was created on or after July 15, 2025, your free tier lasts 6 months or until credits are used.
Once your free tier expires, a running t2.micro instance will incur charges. Stop or terminate the instance if you are unsure about your free tier eligibility.
Resources you used:
- 1x EC2 t2.micro instance (Ubuntu 24.04 LTS).
- 1x key pair .pem file stored on your local machine.
- 1x security group configured for SSH access.
Keep everything running
No action needed. Choose this if you are still actively building or plan to use this server for the next project (Shell Scripting for Security Automation).
- Your instance costs $0 while you remain within the free tier limits.
- Your .pem key file stays on your local machine for future connections.
- Your security group remains attached to the instance.
Pause - I'll come back to this later
Stop the instance to avoid compute charges while keeping your server's configuration intact. You can restart it anytime.
- Open the AWS Console and navigate to the EC2 service.
- In the left sidebar, click Instances.
- Select the checkbox next to your instance.
- Click the Instance state dropdown at the top.
- Select Stop instance.
- Confirm by clicking Stop.
What about storage charges?
A stopped instance still has an EBS volume attached. The free tier includes 30 GB of EBS storage, so a small root volume will not cost anything while your account is in the free tier period.
Delete - I don't want to use this again
Remove all project resources permanently. This cannot be undone.
Terminate the EC2 instance:
- Open the AWS Console and navigate to the EC2 service.
- In the left sidebar, click Instances.
- Select the checkbox next to your instance.
- Click the Instance state dropdown at the top.
- Select Terminate instance.
- Confirm by clicking Terminate. This permanently deletes the instance and its attached storage.
Delete the security group:
- In the EC2 left sidebar, click Security Groups under Network & Security.
- Select the security group you created for this project.
- Click Actions, then Delete security groups.
- Confirm the deletion.
Delete the key pair from AWS:
- In the EC2 left sidebar, click Key Pairs under Network & Security.
- Select your key pair and click Actions, then Delete.
- Confirm the deletion.
Delete the local .pem file:
- On macOS or Linux, run:
rm ~/Downloads/your-key.pem
- On Windows, run:
Remove-Item $env:USERPROFILE\Downloads\your-key.pem
Nice Work!
Nice Work!
You just hardened a Linux server like a DevSecOps professional. This is the exact baseline every production server needs before it goes live.
Here's what you learned:
- Created a dedicated admin user with sudo privileges and locked down SSH with key-only authentication, restricted user access, and reduced login grace time.
- Configured a UFW firewall with default-deny policies and rate-limited SSH to block brute-force connection attempts.
- Built a bash monitoring script that parses /var/log/auth.log and reports failed login attempts with attacker IP addresses.
- Secret Mission: Installed Fail2Ban for automated IP banning after repeated failed SSH login attempts.
Ready to quiz yourself?