Why SSH Security Matters for Ubuntu Servers
SSH, or Secure Shell, is the standard method for accessing Linux servers remotely. It provides a text-based terminal connection to your server, giving you full control over the machine within your user permissions. That level of control is exactly why SSH is the most frequently attacked service on any internet-facing Linux server.
Automated bots constantly scan the internet looking for servers with open SSH ports. They run through common usernames and passwords without any specific target in mind. The approach is purely volume-based: try enough combinations across enough servers, and eventually some will succeed. If your server uses password authentication on the default port, it will receive these automated login attempts within hours of going live. Most are harmless bots, but the door they are probing should be closed before something more serious comes along.
Securing SSH is not an optional hardening step. It is foundational server hygiene, and implementing it properly takes less than an hour. This guide covers the practical measures that make a real difference: port configuration, SSH key authentication, Fail2Ban, IP restrictions, and the supporting steps that keep your configuration effective over time.
Checking Your Current SSH Status on Ubuntu
Before making any changes, confirm SSH is installed and running on your Ubuntu server:
ssh -V
This returns the OpenSSH version number. If the command is not found, install the SSH server:
sudo apt update && sudo apt upgrade -y
sudo apt install openssh-server -y
Start the SSH service and ensure it starts automatically on boot:
sudo systemctl start ssh
sudo systemctl enable ssh
Check your current firewall status. UFW comes pre-configured on many Ubuntu installations and will block your SSH connection if you change the port without updating the firewall rules first. This is one of the most common ways people lock themselves out of remote servers:
sudo ufw status
If UFW is active, note the current allowed ports before making any changes. You need to ensure your new SSH port is permitted in the firewall before you restart the SSH service, or you will lose remote access to the server.
Step 1: Change the Default SSH Port
The default SSH configuration listens on port 22 and permits password authentication. These defaults are practical for initial server setup but are not appropriate for production environments. Moving SSH to a different port eliminates the majority of automated scans because bots target port 22 by default. This does not stop a determined attacker, but it significantly reduces the noise from opportunistic bots.
Edit the SSH daemon configuration file:
sudo nano /etc/ssh/sshd_config
Find the line Port 22 and change it to your chosen port. Select a number between 1024 and 65535 that is not already in use by another service. Port 2222 is a common choice:
# Port 22
Port 2222
Save the file and 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 configuration is working correctly. You can safely close the original session. If it fails, your original session remains active so you can troubleshoot the configuration without losing access to the server.
If you do lock yourself out, most cloud providers offer console access that bypasses the SSH service entirely. AWS EC2 Instance Connect, DigitalOcean VNC console, and similar tools allow you to access the server directly through the provider's interface and fix the firewall rules from there.
Step 2: Set Up SSH Key Authentication
SSH keys use asymmetric cryptography to authenticate connections. You generate a pair of files: a private key that stays on your local computer and a public key that you install on the server. When you connect, the server challenges you to prove you hold the matching private key. This is fundamentally more secure than passwords because private keys cannot be guessed by automated tools, and compromising one would require either physical access to your computer or a cryptographic breakthrough.
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 an extra 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 small step to your login process.
Copy the public key to your server using the SSH key copy utility:
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 the key-based login:
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 and the Bits to 4096. Click Generate and move your mouse over the blank area as instructed to create 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 on the SSH directory and authorized keys file:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Permission errors are a common reason key authentication fails. SSH enforces strict file permissions and will refuse to use authorized_keys files that are readable by group or world. This security measure prevents other users on the same system from reading your authorized keys file.
To use Pageant, which stores your key in memory and provides it automatically to PuTTY connections, 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 reliably, disable password authentication. This step removes the primary attack vector that automated bots exploit.
Edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_config
Find and update these lines:
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
Setting PermitRootLogin no prevents direct root login over SSH. Even if someone obtains your root password, they cannot use it to log in directly. Always log in as a regular user and use sudo for administrative tasks. This limits the potential damage if any single account is compromised.
Reload the SSH configuration to apply the changes:
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, password authentication is properly disabled. If something goes wrong, your existing session remains active so you can investigate and correct 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 beginning with # are comments and inactive. You need to ensure the active (uncommented) lines are set correctly. If PasswordAuthentication yes appears elsewhere in the file, it overrides any commented PasswordAuthentication no you may have added.
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 locations, restrict SSH access to those sources. This makes your SSH service invisible to connection attempts from any other IP address, eliminating the vast majority of automated attacks.
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 need SSH access from multiple locations or have a dynamic IP address that changes regularly, consider using a VPN such as WireGuard to establish a trusted network first, then restrict SSH to the VPN interface only. This is significantly more secure than allowing SSH from arbitrary IPs, but it requires additional configuration.
For small business servers where access typically comes from a fixed office location or known set of locations, IP restriction is the most effective additional layer of protection after key authentication. It requires minimal setup and eliminates an entire category of threat.
Step 5: Protect Against Brute Force with Fail2Ban
Fail2Ban monitors authentication log files and automatically blocks IP addresses that repeatedly fail to authenticate. It is lightweight and effective against sustained dictionary attacks targeting SSH. Even with password authentication disabled, Fail2Ban is worth running because it protects against other types of probes that attempt to identify your server's software version, open ports, or running services.
Install Fail2Ban:
sudo apt install fail2ban -y
Create a local configuration file to override the defaults, which can be overwritten during 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 with settings appropriate for your environment:
[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
The settings work as follows: maxretry = 3 means an IP is banned after three failed login attempts within the time window. bantime = 3600 bans the IP for one hour. findtime = 600 sets the window to 10 minutes. Adjust these values based on your usage patterns. If you occasionally mistype your key passphrase, consider setting maxretry higher to avoid banning your own IP address accidentally.
Start and enable Fail2Ban:
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
Check the status of the SSH jail to see how many IPs are currently banned:
sudo fail2ban-client status sshd
The output shows the number of banned IP addresses and lists them individually. 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 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 service and eliminates 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 country IP range data:
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 xte_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 country IP data directly and build the database manually.
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 or remove countries based on where you have legitimate business access requirements.
Make the rules persistent across reboots:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Review your active iptables rules to verify the configuration:
sudo iptables -L -n --line-numbers
This shows all active rules in order. Verify your GeoIP blocking rules are present and positioned correctly, 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 authentication 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 to verify expected access:
sudo grep "Accepted" /var/log/auth.log | tail -20
Monitor logs in real time during troubleshooting:
sudo tail -f /var/log/auth.log
The journalctl command also provides a filtered view:
sudo journalctl -u ssh | tail -50
Automated monitoring is more reliable than manual log review for production servers. Configure Fail2Ban to send alerts when it bans IP addresses, or use a log monitoring tool that triggers notifications when specific patterns appear, such as failed root logins, logins from unusual IP ranges, or 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 indefinitely 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 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 against the SSH service.
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 thoroughly:
sudo systemctl reload sshd
Ubuntu SSH Hardening Checklist for Production Use
Before deploying a server, apply this checklist of SSH hardening measures. These form the baseline security configuration for any Ubuntu server that will be accessible from the internet:
- Non-default SSH port: Port 22 is automatically scanned by bots. Any other port receives significantly fewer probes and automated attempts.
- SSH key authentication only: Disable password authentication entirely. Keys are the only acceptable authentication method for production servers.
- Root login disabled: No direct root access over SSH. Always log in as a regular user with sudo privileges.
- IP restriction: Allow SSH only from known IP addresses or VPN endpoints where access patterns permit.
- Fail2Ban active: Automated blocking of repeated authentication failures to protect against brute force attempts.
- GeoIP blocking where applicable: Block SSH access from countries where you have no legitimate business requirements.
- Failed authentication monitoring: Regular review of authentication failures in auth.log, or automated alerting configured.
- Idle session timeout: Configure ClientAliveInterval to terminate forgotten sessions and reduce exposure windows.
Apply these measures in a test environment before deploying to production. Document the changes so the configuration can be reviewed and audited independently. A complete Ubuntu server hardening checklist covers additional system-level security measures beyond SSH that complement these configuration changes.
Building an Effective SSH Security Setup
SSH security works best 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 authentication failures. IP restriction and GeoIP blocking add further protection where 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 authentication logs and your Fail2Ban status so you catch anomalies before they become problems.
If you need help reviewing your current SSH configuration or implementing these hardening measures, prepare details of your current setup, the Ubuntu version you are running, and the access patterns your server needs before getting in touch.