Understanding the Attack Surface
SSH stands for Secure Shell. It is the standard protocol for logging into Linux servers remotely and executing commands. When you connect to your server via SSH, you have a text-based terminal session on that machine, which gives you full control over it within your user permissions. Alongside Fail2Ban, a properly configured hardened Ubuntu server environment provides complementary defence at the system level.
That level of access is why SSH is the most targeted service on any Linux server. Automated bots do not care what your server does. They run through lists of known usernames (root, admin, ubuntu, debian) and commonly used passwords. The attack is entirely volume-based. A bot that tries 10,000 credential combinations against 10,000 servers will eventually find the servers with weak credentials. It is a numbers game, and the math is not in your favour if you use password authentication.
The default SSH configuration on Ubuntu listens on port 22 and permits password authentication. These defaults exist for convenience during initial server setup. They are not production settings. An internet-facing server with these settings will receive automated login attempts within hours of going live. Most of these attempts are harmless bots running through dictionaries, but they represent an open door that should be closed.
Securing SSH is not paranoia. It is basic server hygiene, and it takes less than an hour to implement properly.
Checking Your Current SSH Status
Before changing anything, confirm SSH is installed and running:
ssh -V
This returns the OpenSSH version number. If it is not installed, install it:
sudo apt update && sudo apt upgrade -y
sudo apt install openssh-server -y
Start the SSH service and enable it to start on boot:
sudo systemctl start ssh
sudo systemctl enable ssh
Check your current firewall status. UFW is commonly active on new Ubuntu installations and will block your SSH connection if you change the port without updating the firewall rules first. This is a common way to lock yourself out of a remote server:
sudo ufw status
If UFW is active, note the current allowed ports before making changes. You need to ensure your new SSH port is allowed before you restart the SSH service, or you will lose access to the server remotely.
Step 1: Change the Default SSH Port
The first and simplest hardening step is changing the port SSH listens on. Port 22 is scanned constantly because it is the default. Moving to a different port eliminates the majority of automated scans. This does not stop targeted attacks, but it significantly reduces the noise and risk from opportunistic bots.
Edit the SSH configuration file:
sudo nano /etc/ssh/sshd_config
Find the line Port 22 and change it to your chosen port. Choose a number between 1024 and 65535 that is not already in use by another service. A common choice is 2222, though any available port in that range works:
# Port 22
Port 2222
Save the file. Update your firewall to allow the new port before restarting the SSH service:
sudo ufw allow 2222/tcp
sudo ufw delete allow 22/tcp
sudo systemctl restart sshd
Do not close your existing terminal session yet. Open a new terminal window and test the connection to the new port:
ssh -p 2222 username@yourserverip
If the connection succeeds, your new configuration is working. You can now safely close the original session. If it fails, your original session is still active and you can troubleshoot the configuration.
A common mistake is forgetting to update the firewall when changing ports. If you lock yourself out, most cloud providers offer console access (AWS EC2 Instance Connect, DigitalOcean console, etc.) that bypasses the SSH service entirely and allows you to fix the firewall rules directly.
Step 2: Set Up SSH Key Authentication
SSH keys are a pair of cryptographic files: a private key that stays on your computer and a public key that is placed on the server. The server only grants access to clients that can prove they hold the matching private key. This is fundamentally more secure than password authentication because the private key cannot be guessed by automated tools and would require either physical access to your computer or a cryptographic breakthrough to compromise.
Generating a Key Pair on Linux and macOS
Run the key generation command on your local machine:
ssh-keygen -t rsa -b 4096
When prompted for a file location, press Enter to accept the default location. When prompted for a passphrase, enter one. A passphrase adds a layer of protection: even if someone copies your private key file, they cannot use it without the passphrase. It is worth setting this even if it adds a step to your login process.
Copy the public key to your server:
ssh-copy-id -p 2222 username@yourserverip
This command connects to the server, creates the ~/.ssh/ directory if it does not exist, and appends your public key to the ~/.ssh/authorized_keys file. Test logging in with the key:
ssh -p 2222 username@yourserverip
If you are prompted for your key passphrase rather than the server password, key authentication is working correctly.
Generating a Key Pair on Windows Using PuTTY and Pageant
Download and open PuTTYgen. Set the Type to RSA, Bits to 4096. Click Generate and move your mouse over the blank area as instructed to generate randomness. Save the private key to a secure location with a strong passphrase. Copy the public key text displayed in the window.
Connect to your server with your password one more time and add the public key to the authorized keys file:
mkdir -p ~/.ssh
nano ~/.ssh/authorized_keys
Paste the public key text on its own line, then save and close the file. Set correct permissions:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Permission errors are a common reason key authentication fails. SSH is strict about file permissions and will refuse to use authorized_keys files that are readable by anyone other than the owner.
To use Pageant (which stores your key in memory and provides it automatically to PuTTY), run Pageant and add your private key file through the system tray icon. Configure PuTTY to use Pageant for authentication under Connection, SSH, Auth. Load your saved session and connect. Pageant will supply your key automatically without prompting for the passphrase each time, as long as Pageant is running.
Step 3: Disable Password Authentication
Once you have confirmed that key-based login works, disable password authentication. This step removes the attack vector that the automated bots are exploiting.
Edit the SSH configuration file:
sudo nano /etc/ssh/sshd_config
Find and update these lines:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
PermitRootLogin no prevents direct root login. Even if someone obtains your root password, they cannot use it. Always log in as a regular user and use sudo for administrative tasks. This limits the damage if any single account is compromised.
Reload the SSH configuration without restarting the service:
sudo systemctl reload sshd
Keep your existing terminal session open while testing the new configuration. Open a new terminal and attempt to log in with key authentication. If it works, the password authentication is properly disabled. If something goes wrong, your existing session remains active so you can investigate and fix the configuration.
A common error at this step is editing the wrong line in sshd_config. Look carefully at the actual content of the file. Lines that are commented out with a # at the start are inactive. You need to ensure the active (uncommented) lines are set correctly. If PasswordAuthentication yes is active, it overrides any commented PasswordAuthentication no you may have added below it.
Step 4: Restrict SSH Access by IP Address
If your server has a fixed IP address or you access it from a known set of IP addresses, restrict SSH access to those sources. This makes the server completely invisible to SSH connection attempts from any other IP address.
Find your current public IP address:
curl ifconfig.me
Allow SSH only from your IP address:
sudo ufw allow from YOUR.IP.ADDRESS.HERE to any port 2222 proto tcp
If you have a dynamic IP address that changes regularly, or if you need to access the server from multiple locations, this approach is inconvenient. The alternative is to use a VPN such as WireGuard to establish a trusted network first, and then restrict SSH to the VPN interface only. This is significantly more secure than allowing SSH from any IP, but it requires additional setup.
For most small business servers where access is from a fixed office location or a known set of locations, IP restriction is the simplest and most effective layer of protection after key authentication.
Step 5: Protect Against Brute Force with Fail2Ban
Fail2Ban monitors log files and automatically blocks IP addresses that repeatedly fail to authenticate. It is lightweight and effective against the sustained dictionary attacks that target SSH. Even with password authentication disabled, Fail2Ban is worth running because it protects against other types of probes and scans that attempt to identify your server's software version or open ports.
Install Fail2Ban:
sudo apt install fail2ban -y
Create a local configuration file to override the defaults (the defaults file is overwritten on package updates):
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Find the [sshd] section and update it:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
maxretry = 3 means an IP is banned after three failed login attempts within the findtime window. bantime = 3600 bans it for one hour. findtime = 600 sets the window to 10 minutes. Adjust these values to suit your usage pattern. If you yourself occasionally mistype your key passphrase, set maxretry higher to avoid banning your own IP by accident.
Start and enable Fail2Ban:
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
Check the status of the SSH jail:
sudo fail2ban-client status sshd
This shows how many IPs are currently banned and the rules applied. The output lists the banned IP addresses and the total count of banned IPs.
For a detailed walkthrough of configuring Fail2Ban specifically for SSH and HTTP services, including custom filter rules and notification setup, refer to the Fail2Ban SSH and HTTP protection guide.
Step 6: Block SSH Traffic by Country Using GeoIP Blocking
If your business only operates domestically and you do not need remote access from abroad, you can block all SSH connections from foreign IP ranges. This is optional but dramatically reduces the volume of traffic reaching your SSH server and eliminates entire categories of attack traffic that originate from specific countries.
Install the required tools:
sudo apt install xtables-addons-common libtext-csv-xs perl libnet-cidr-lite-perl -y
Create the GeoIP directory and build the database from MaxMind CSV files:
mkdir -p /usr/share/xt_geoip
cd /usr/share/xt_geoip
sudo /usr/lib/xt_geoip/xte_geoip_build -D . /usr/share/xt_geoip/*.csv 2>/dev/null || \
(wget https://download.db-ip.com/free/dbip-country-lite.csv.gz && \
gunzip dbip-country-lite.csv.gz && \
sudo /usr/lib/xt_geoip/xte_geoip_build -D . .)
The xt_geoip_build command processes country IP ranges into a binary format that iptables can use for matching. The exact invocation depends on which CSV package you use. If the automated build fails, download the MaxMind GeoLite2 Country CSV directly from their website, extract it, and build the database from that.
Block traffic from specific countries by IP range. For example, to block China and Russia, which are the source of a disproportionate amount of SSH brute-force traffic:
sudo iptables -A INPUT -p tcp --dport 2222 -m geoip --src-cc CN,RU -j DROP
The --src-cc flag accepts a comma-separated list of two-letter country codes. Add countries as needed based on your legitimate access requirements.
Make the rules persistent across reboots:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Review your active iptables rules:
sudo iptables -L -n --line-numbers
This shows all active rules in order. Verify your GeoIP blocking rules are present and correctly positioned (before any ACCEPT rules for the same port).
Step 7: Monitor SSH Access Logs
Regular log review catches unusual access patterns before they become serious incidents. The main SSH log on Ubuntu is at /var/log/auth.log.
View recent failed login attempts:
sudo grep "Failed password" /var/log/auth.log | tail -20
View successful logins:
sudo grep "Accepted" /var/log/auth.log | tail -20
Monitor logs in real time:
sudo tail -f /var/log/auth.log
Journalctl also provides a filtered view:
sudo journalctl -u ssh | tail -50
Automated monitoring is more reliable than manual log review. Configure Fail2Ban to send alerts when it bans IPs, or use a log monitoring tool that triggers notifications when specific patterns appear (failed root logins, logins from unusual IP ranges, authentication failures from known hostile networks).
Step 8: Additional SSH Hardening Options
Several other settings in /etc/ssh/sshd_config improve security with minimal disruption to normal use. Edit the file and consider adding these options:
LoginGraceTime 60
This sets a 60-second limit for completing authentication. The default is 120 seconds, which is longer than necessary for key or password authentication and can be exploited to hold connections open (a slow-loris style attack against the SSH daemon).
PermitEmptyPasswords no
Refuse authentication attempts with empty passwords. This is already the default in most OpenSSH installations, but setting it explicitly prevents issues if the default changes in a future update.
MaxStartups 10:30:100
Limits the number of concurrent unauthenticated connections. The format is start:rate:full: 10 unauthenticated connections are allowed, after which new connections are randomly dropped with a 30 percent probability, scaling up to 100 at which point all new connections are refused. This mitigates connection exhaustion attacks.
ClientAliveInterval 300
ClientAliveCountMax 2
These settings terminate idle connections after 5 minutes of inactivity. Useful if you want to ensure sessions do not remain open indefinitely when you forget to disconnect.
After changing these settings, reload SSH and test:
sudo systemctl reload sshd
OpenSSH Hardening Checklist
Before deploying a server, apply this checklist of SSH hardening measures:
- Non-default port: Port 22 is automatically scanned. Any other port receives significantly fewer probes.
- SSH key authentication only: Disable password authentication entirely. Keys are the only acceptable authentication method.
- Root login disabled: No direct root access. Always log in as a regular user with sudo privileges.
- IP restriction: Allow SSH only from known IP addresses or VPN endpoints.
- Fail2Ban active: Automated blocking of repeated authentication failures.
- GeoIP blocking (if applicable): Block SSH access from countries where you have no legitimate business.
- Failed auth log monitoring: Regular review of authentication failures in auth.log.
- Idle session timeout: Configure ClientAliveInterval to terminate forgotten sessions.
Apply these in a test environment before deploying to production. Document the changes so the configuration can be reviewed and audited by someone other than the person who made the changes.
Putting the Layers Together
SSH security works as a layered approach. Each control on its own provides limited protection. Together, they make your server significantly harder to compromise and significantly less interesting to automated attackers.
The minimum effective configuration for any internet-facing server is: a non-default port, SSH key authentication only, password authentication disabled, and Fail2Ban monitoring for repeated failures. IP restriction and GeoIP blocking add further protection if your access patterns allow them.
Test each change before moving to the next. Keep one terminal session open while testing each modification so you can recover if something breaks. Document your configuration so the hardening is reproducible and auditable. Schedule a regular review of your auth logs and your Fail2Ban status so you catch anomalies before they become problems.
For broader server security beyond SSH, consider reviewing the Ubuntu server hardening guide, which covers additional measures such as automatic security updates, firewall configuration, and system-level access controls that complement your SSH hardening.