How to Secure SSH Access: Keys, Fail2ban, and Network Restrictions
SSH is a top target for attackers. Learn how to secure SSH with key-based authentication, fail2ban, firewall rules, and bastion hosts to prevent brute force attacks.
Why SSH is a top target for attackers
SSH (Secure Shell) is the standard way to remotely manage Linux and Unix servers. Because it provides full command-line access to a system, it is one of the highest-value targets for attackers.
If you run a server with SSH exposed to the internet, it is being attacked right now. Check your auth log:
# See recent failed SSH login attempts
grep "Failed password" /var/log/auth.log | tail -20
# Count total failed attempts
grep -c "Failed password" /var/log/auth.log
It is common to see thousands or tens of thousands of brute force attempts per day on a publicly exposed SSH port. Automated botnets continuously scan the internet for SSH servers and try common username/password combinations.
Step 1: Disable password authentication
The single most important step. Password authentication is vulnerable to brute force attacks, credential stuffing, and password reuse. SSH key authentication is cryptographically strong and immune to these attacks.
Generate an SSH key pair
On your local machine (not the server):
# Generate an Ed25519 key (recommended -- fast and secure)
ssh-keygen -t ed25519 -C "your_email@yourcompany.com"
# Or RSA 4096 if you need compatibility with older systems
ssh-keygen -t rsa -b 4096 -C "your_email@yourcompany.com"
When prompted, save the key to the default location (~/.ssh/id_ed25519) and set a strong passphrase.
Deploy the public key to the server
# Copy your public key to the server (easiest method)
ssh-copy-id user@your-server-ip
# Or manually:
cat ~/.ssh/id_ed25519.pub | ssh user@your-server-ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Test key-based login
# This should log you in without asking for a password
ssh user@your-server-ip
Disable password authentication
Once you have confirmed key-based login works, edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_config
Set these directives:
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM no
Restart the SSH service:
sudo systemctl restart sshd
Important: Do not close your current SSH session until you have confirmed you can open a new session with key-based auth. Otherwise, you may lock yourself out.
Step 2: Disable root login
Never allow direct root login over SSH. Use a regular user and sudo instead:
PermitRootLogin no
If you absolutely need root SSH access (not recommended), at minimum restrict it to key-based authentication:
PermitRootLogin prohibit-password
Step 3: Change the default port
Changing SSH from port 22 to a non-standard port reduces automated scanning noise significantly. This is not a security measure on its own -- a determined attacker will find the port -- but it eliminates the vast majority of bot traffic.
Port 2222
After restarting SSH, connect with:
ssh -p 2222 user@your-server-ip
Warning: Make sure you update your firewall rules to allow the new port before restarting SSH, or you will lock yourself out.
# Allow the new port before restarting SSH
sudo ufw allow 2222/tcp
sudo systemctl restart sshd
# Once confirmed working, block the old port
sudo ufw deny 22/tcp
For more on managing open ports and their security implications, see our guide on open ports security.
Step 4: Install and configure fail2ban
fail2ban monitors log files and bans IP addresses that show signs of brute force attacks. It dynamically adds firewall rules to block offending IPs.
Install
# Debian/Ubuntu
sudo apt update && sudo apt install fail2ban -y
# RHEL/CentOS/Alma
sudo dnf install fail2ban -y
Configure for SSH
Create a local configuration file (never edit the defaults directly):
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
port = ssh
# Use the port you configured, e.g., port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
This bans an IP for 1 hour after 3 failed attempts within 10 minutes.
Start and enable
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check status
sudo fail2ban-client status sshd
Verify it is working
# See currently banned IPs
sudo fail2ban-client status sshd
# Unban an IP if needed (e.g., you locked yourself out)
sudo fail2ban-client set sshd unbanip 203.0.113.5
Step 5: Restrict SSH to specific IPs
If your team only connects from known IP addresses or IP ranges, restrict SSH access at the firewall level.
Using UFW (Ubuntu)
# Allow SSH only from your office IP
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp
# Allow from a VPN exit IP
sudo ufw allow from 198.51.100.10 to any port 22 proto tcp
# Deny SSH from everywhere else (default deny handles this if enabled)
sudo ufw default deny incoming
sudo ufw enable
Using iptables
# Allow SSH from specific IP
sudo iptables -A INPUT -p tcp -s 203.0.113.0/24 --dport 22 -j ACCEPT
# Drop SSH from everywhere else
sudo iptables -A INPUT -p tcp --dport 22 -j DROP
# Save rules
sudo iptables-save | sudo tee /etc/iptables/rules.v4
Using cloud security groups
If you are running in AWS, Azure, or GCP, configure SSH access in your cloud security group or network security group. Only allow inbound port 22 from specific IP addresses -- never from 0.0.0.0/0.
Step 6: Use a VPN or bastion host
The most secure approach is to not expose SSH to the public internet at all.
VPN approach
Run a VPN server (WireGuard is the modern choice) and only allow SSH from the VPN subnet:
# Allow SSH only from WireGuard subnet
sudo ufw allow from 10.0.0.0/24 to any port 22 proto tcp
Your team connects to the VPN first, then SSHs to servers over the private network.
Bastion host (jump server)
A bastion host is a single hardened server that acts as the gateway to your infrastructure. All SSH access goes through the bastion:
Internet --> Bastion Host --> Internal Servers
Internal servers only allow SSH from the bastion's IP. The bastion itself is heavily monitored and locked down.
Connect through a bastion with SSH ProxyJump:
# Direct jump
ssh -J user@bastion.yourcompany.com user@internal-server
# Or configure in ~/.ssh/config
Host internal-server
HostName 10.0.1.5
User admin
ProxyJump user@bastion.yourcompany.com
Additional hardening
Limit allowed users
Restrict which users can log in via SSH:
AllowUsers deploy admin
Set idle timeout
Disconnect idle sessions to reduce the window of opportunity if a session is left open:
ClientAliveInterval 300
ClientAliveCountMax 2
This disconnects after 10 minutes of inactivity.
Use SSH certificates (advanced)
For larger teams, SSH certificates simplify key management. Instead of distributing public keys to every server, a certificate authority signs user keys, and servers trust the CA.
Enable two-factor authentication
For an extra layer, add TOTP (Google Authenticator) to SSH:
sudo apt install libpam-google-authenticator
This requires both an SSH key and a TOTP code to log in.
Summary: SSH hardening checklist
- SSH key authentication enabled
- Password authentication disabled
- Root login disabled
- Non-standard port configured
- fail2ban installed and active
- Firewall restricts SSH to known IPs
- VPN or bastion host in place (for production)
- Idle timeout configured
- AllowUsers directive set
- SSH daemon is regularly updated
Also review your broader network exposure -- our guide on open ports security covers other commonly exposed services, and weak TLS cipher suites explains how to harden encrypted connections.
How SurfaceScan helps
SurfaceScan detects SSH services exposed on the internet during every scan -- on the default port 22 and on non-standard ports. It flags password authentication that is still enabled, outdated SSH protocol versions, and SSH running on hosts where it likely should not be publicly accessible. Findings appear in the Network Security section with details on the port, protocol version, and authentication methods detected, helping you verify that your SSH hardening is actually working from the outside.
Related articles
Open Ports: Which Ones Are Dangerous and How to Close Them
Not all open ports are a problem, but some should never be exposed to the internet. Learn which ports are dangerous, why, and how to close them safely.
Weak TLS Cipher Suites: How to Fix and Harden Your HTTPS
Weak TLS cipher suites like RC4 and 3DES leave your HTTPS connections vulnerable. Learn how to identify weak ciphers and configure strong ones on Nginx and Apache.