Harden SSH Login on Ubuntu Server
/ 8 min read
Table of Contents
Overview
A VPS will usually receive automated SSH login attempts within minutes or hours of being online. Most of these attempts are background internet noise, but if you’re like me and prefer the default password-based SSH login provided by cloud service providers to avoid the headache, that makes your server much easier to attack.
This guide serves as a record and walks through the practical hardening steps:
- Inspect SSH logs — confirm whether the server is receiving login attacks.
- Review successful logins — look for unfamiliar access before changing settings.
- Move to SSH keys — generate and use SSH key from local disk.
- Disable password login — remove the main brute-force target.
- Enable host-level protection — turn on UFW and add Fail2ban.
- Tighten optional controls — restrict users or change the SSH port when useful.
Assumptions
The commands below use these placeholders (since it’s my set-up,and you should easily find the corresponding commands for other systems simply by asking AI tools):
- Server OS: Ubuntu 24.04 LTS
- Local computer: macOS Sequoia
- SSH user:
youruser - Server IP:
your_server_ip
Replace youruser, your_server_ip, and any other placeholder values with your own details before running the commands.
Before You Begin
If your server is hosted by a cloud provider, also confirm that you have a recovery option, such as a web console, serial console, snapshot, or rescue mode.
Check Recent SSH Activity
Start by reading recent SSH logs:
sudo journalctl -u ssh --since today --no-pagerTo show failed SSH login attempts from today:
sudo journalctl -u ssh --since today --no-pager \ | grep -iE "failed|invalid user|authentication failure"To check the past 7 days:
sudo journalctl -u ssh --since "7 days ago" --no-pager \ | grep -iE "failed password|invalid user|authentication failure"Common signs of brute-force attempts look like this:
Failed password for invalid user admin from 203.0.113.10 port 51234 ssh2Failed password for root from 203.0.113.20 port 49822 ssh2Invalid user test from 203.0.113.30These usually mean bots are guessing usernames and passwords.
Find the Most Frequent Attacking IP Addresses
Use this command to list the top IP addresses causing failed password attempts:
sudo journalctl -u ssh --since "7 days ago" --no-pager \ | awk '/Failed password/ { for (i = 1; i <= NF; i++) if ($i == "from") print $(i + 1) }' \ | sort | uniq -c | sort -nr | head -20Example output:
128 203.0.113.10 64 198.51.100.25 31 192.0.2.44The first number is the number of failed attempts. The second value is the source IP address.
Check for Successful SSH Logins
Failed attempts are annoying. Unknown successful logins are serious.
Run:
sudo journalctl -u ssh --since "30 days ago" --no-pager \ | grep -E "Accepted (password|publickey)"You may see output like this:
Accepted password for youruser from 203.0.113.10 port 53422 ssh2Accepted publickey for youruser from 203.0.113.10 port 53422 ssh2Pay close attention to successful password logins:
Accepted passwordYou can also check current and previous login sessions:
whowlast -a | head -50lastlogUse this quick interpretation table:
| Log entry | Meaning |
|---|---|
Failed password |
Someone tried a password and failed |
Invalid user |
Someone tried a username that does not exist |
Accepted password |
Someone successfully logged in with a password |
Accepted publickey |
Someone successfully logged in with an SSH key |
Unknown IP with Accepted password |
Investigate immediately |
Generate an SSH Key on macOS
Password login is the main risk. The better approach is to use SSH key authentication.
On your Mac, open Terminal and generate an Ed25519 key:
ssh-keygen -t ed25519 -a 64 -C "mac-to-server"When prompted for a file location, press Enter to accept the default:
/Users/YOU/.ssh/id_ed25519When prompted for a passphrase, use a strong one.
An SSH key uses a private key on your local machine and a public key on the remote server. A passphrase adds another layer of protection if the private key file is ever copied or exposed.
Add the SSH Key to the macOS ssh-agent
Start the ssh-agent:
eval "$(ssh-agent -s)"Create or edit your local SSH config:
touch ~/.ssh/configopen ~/.ssh/configAdd this block:
Host my-server HostName your_server_ip User youruser AddKeysToAgent yes UseKeychain yes IdentityFile ~/.ssh/id_ed25519Then add your private key to the agent and macOS Keychain:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519After this, you can connect with:
ssh my-serverCopy Your Public Key to the Server
If your Mac has ssh-copy-id, run:
ssh-copy-id youruser@your_server_ipIf not, use this command:
cat ~/.ssh/id_ed25519.pub | ssh youruser@your_server_ip \ 'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'Now test key-based login from a new Terminal window:
ssh youruser@your_server_ipOr, if you added the Host my-server block:
ssh my-serverDisable SSH Password Login
Once SSH key login works, disable password authentication.
Create a dedicated OpenSSH configuration snippet:
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'PubkeyAuthentication yesPasswordAuthentication noKbdInteractiveAuthentication noPermitRootLogin noMaxAuthTries 3EOFValidate the configuration:
sudo sshd -tIf there is no output, the configuration is valid.
Reload SSH:
sudo systemctl reload ssh.serviceTest again from a new Terminal window:
ssh youruser@your_server_ipCheck the active SSH settings:
sudo sshd -T | grep -E 'passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication|maxauthtries'Expected output:
passwordauthentication nokbdinteractiveauthentication nopermitrootlogin nopubkeyauthentication yesmaxauthtries 3At this point, password-based SSH brute-force attempts should no longer work.
Enable the Firewall with UFW
Ubuntu’s default firewall tool is ufw, short for Uncomplicated Firewall. It provides a simpler way to manage host-based firewall rules.
Allow SSH before enabling the firewall:
sudo ufw allow OpenSSHEnable firewall logging:
sudo ufw logging onEnable UFW:
sudo ufw enableCheck the firewall status:
sudo ufw status verboseYou should see SSH allowed.
If your local IP address is stable, you can restrict SSH to only that IP:
sudo ufw allow from YOUR_PUBLIC_IP to any port 22 proto tcpThen remove the broad SSH rule:
sudo ufw status numberedsudo ufw delete RULE_NUMBERInstall Fail2ban
Fail2ban watches logs for repeated failed login attempts and temporarily bans abusive IP addresses.
Install it:
sudo apt updatesudo apt install fail2banEnable and start the service:
sudo systemctl enable --now fail2banCreate an SSH jail config:
sudo tee /etc/fail2ban/jail.d/sshd.local >/dev/null <<'EOF'[sshd]enabled = truebackend = systemdport = sshmaxretry = 5findtime = 10mbantime = 1hbantime.increment = trueEOFRestart Fail2ban:
sudo systemctl restart fail2banCheck status:
sudo fail2ban-client statussudo fail2ban-client status sshdOptional: Allow Only One SSH User
If only one user should be able to SSH into the server, add an AllowUsers rule:
sudo tee -a /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'AllowUsers youruserEOFValidate and reload SSH:
sudo sshd -tsudo systemctl reload ssh.serviceTest from a new Terminal window:
ssh youruser@your_server_ipThis prevents other local users from logging in over SSH.
Optional: Change the SSH Port
Changing the SSH port can reduce log noise, but it is not a real security boundary. Bots can scan all ports. SSH keys and disabled password login matter much more.
If you still want to change the port, add the new port while keeping port 22 available during testing:
sudo tee -a /etc/ssh/sshd_config.d/99-hardening.conf >/dev/null <<'EOF'Port 22Port 2222EOFAllow the new port through the firewall:
sudo ufw allow 2222/tcpValidate and reload SSH:
sudo sshd -tsudo systemctl reload ssh.serviceTest from a new Terminal window:
ssh -p 2222 youruser@your_server_ipOnly after confirming the new port works should you remove port 22 from the SSH config and delete the old firewall rule.
If You Find a Suspicious Successful Login
If you see an unfamiliar Accepted password entry, assume the account password may be compromised.
Check local users:
awk -F: '$3 == 0 || $3 >= 1000 {print $1 ":" $3 ":" $7}' /etc/passwdCheck sudo users:
getent group sudoCheck authorized SSH keys:
sudo find /home /root -maxdepth 3 -name authorized_keys -type f \ -exec ls -l {} \; \ -exec sed -n '1,40p' {} \;Check recent sudo activity:
sudo journalctl --since "14 days ago" --no-pager | grep -E "sudo:|COMMAND="Check successful SSH logins again:
sudo journalctl -u ssh --since "30 days ago" --no-pager \ | grep "Accepted"Then rotate credentials:
passwdsudo passwd -l rootUpdate the system:
sudo apt updatesudo apt full-upgradeFinal Hardening Checklist
Use this checklist when setting up a new Ubuntu server:
[ ] Create a normal sudo user[ ] Generate an Ed25519 SSH key on your Mac[ ] Add the key to the macOS ssh-agent[ ] Copy the public key to the server[ ] Confirm key-based SSH login works[ ] Disable SSH password login[ ] Disable keyboard-interactive login[ ] Disable root SSH login[ ] Set MaxAuthTries 3[ ] Enable UFW[ ] Allow only the ports you need[ ] Install and enable Fail2ban[ ] Check logs for suspicious successful logins[ ] Keep one recovery path available before changing SSH settingsThe most important SSH hardening file from this tutorial is /etc/ssh/sshd_config.d/99-hardening.conf:
PubkeyAuthentication yesPasswordAuthentication noKbdInteractiveAuthentication noPermitRootLogin noMaxAuthTries 3Once password login is disabled and key-based login is working, most SSH password attacks become harmless noise instead of a serious login risk.