To secure an SSH server, switch to SSH key authentication, disable password and root logins in /etc/ssh/sshd_config, limit which users can log in, firewall port 22 to trusted addresses where possible, add fail2ban, and keep OpenSSH updated. Always test the config with sudo sshd -t and keep your current session open until a new login works.
Any server with SSH open to the internet gets a steady stream of automated login attempts, often within minutes of going live. Most are bots trying common usernames and passwords. A few configuration changes make those attempts pointless.
This guide is for Ubuntu and Debian, but the sshd_config settings apply to almost any Linux or BSD system running OpenSSH. Every step is defensive and applies to servers you manage.
In this guide:
- Before you start: don't lock yourself out
- Step 1: update OpenSSH and the system
- Step 2: create a non-root user with sudo
- Step 3: set up SSH key authentication
- Step 4: understand where sshd reads its settings
- Steps 5 to 9: the hardening settings
- Step 10: test and apply the changes
- Step 11: firewall SSH with UFW
- Step 12: add fail2ban
- Should you change the SSH port?
- Ongoing checks
- Optional: add two-factor authentication to SSH
- Quick hardening checklist
- Frequently asked questions
Before you start: don't lock yourself out
Changing SSH settings on a remote server is one of the few ways to lock yourself out completely. Follow these rules every time:
- Keep your current SSH session open while you make changes. Existing sessions usually survive a restart of the SSH service.
- Test the config before restarting:
sudo sshd -t prints nothing if the file is valid. - Open a second terminal and log in again to confirm the new settings work, before you close the first.
- Know your provider's console. Most cloud hosts (AWS, DigitalOcean, Hetzner, Linode and others) offer a web or serial console that works even when SSH doesn't.
- Take a snapshot or back up
/etc/ssh/sshd_config first: sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.
Step 1: update OpenSSH and the system
sudo apt update && sudo apt upgrade
ssh -V
Security fixes for OpenSSH arrive through normal system updates. On Ubuntu and Debian you can install the unattended-upgrades package so security patches are applied automatically:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Step 2: create a non-root user with sudo
Don't log in as root day to day. Create a normal user and give it sudo rights:
sudo adduser alice
sudo usermod -aG sudo alice
Log in as that user in a new terminal and check sudo whoami prints root before you go further.
Step 3: set up SSH key authentication
SSH keys replace passwords with a key pair: a private key that stays on your computer and a public key you put on the server. They're far harder to guess or steal than a password. On your own computer (not the server):
# Create an Ed25519 key, protected with a passphrase
ssh-keygen -t ed25519 -C "alice@laptop"
# Copy the public key to the server
ssh-copy-id alice@server.example.com
# Test: this should log in without asking for the server password
ssh alice@server.example.com
| File | Where | Share it? |
|---|
| ~/.ssh/id_ed25519 | Your computer | Never: this is the private key |
| ~/.ssh/id_ed25519.pub | Your computer | Yes: this goes on servers |
| ~/.ssh/authorized_keys | The server, in the user's home | Lists public keys allowed to log in |
On Windows 10 and 11, the same ssh-keygen command works in PowerShell. If ssh-copy-id isn't available, paste the contents of the .pub file into ~/.ssh/authorized_keys on the server and run chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys.
Use a passphrase on the key. If you need a strong one, our password generator can create a memorable passphrase.
Step 4: understand where sshd reads its settings
OpenSSH's server reads /etc/ssh/sshd_config. On current Ubuntu and Debian releases, that file starts with an Include /etc/ssh/sshd_config.d/*.conf line. For most settings, the first value sshd finds wins, so a file in sshd_config.d can override what you set further down the main file.
The cleanest approach is to put your hardening in its own drop-in file with a low number so it's read first:
sudo nano /etc/ssh/sshd_config.d/00-hardening.conf
Also check for existing drop-ins, such as one added by cloud-init that sets PasswordAuthentication yes:
ls /etc/ssh/sshd_config.d/
sudo sshd -T | grep -Ei 'passwordauth|permitroot|kbdinteractive|maxauth'
sshd -T prints the settings sshd will actually use, which is the best way to confirm your changes have taken effect.
Steps 5 to 9: the hardening settings
Put these lines in your drop-in file, replacing alice with your username(s):
# /etc/ssh/sshd_config.d/00-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
AllowUsers alice
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
| Setting | OpenSSH default | Hardened value | Why |
|---|
| PermitRootLogin | prohibit-password | no | Attackers always try "root"; log in as a normal user and use sudo |
| PasswordAuthentication | yes | no | Stops password-guessing attacks entirely |
| KbdInteractiveAuthentication | yes | no | Closes another route for password prompts |
| AllowUsers | (all users) | named users only | Service and test accounts can't log in over SSH |
| MaxAuthTries | 6 | 3 | Fewer attempts per connection |
| LoginGraceTime | 120 seconds | 30 seconds | Drops half-open login attempts sooner |
| X11Forwarding | no (often yes in distro configs) | no | Turn off features you don't use |
| ClientAliveInterval / CountMax | 0 / 3 | 300 / 2 | Closes dead sessions after about 10 minutes |
The defaults above come from the official sshd_config manual; your distribution's shipped config may differ. Recent OpenSSH versions also include PerSourcePenalties, which slows down addresses that repeatedly fail to authenticate.
Step 10: test and apply the changes
# 1. Check syntax: no output means OK
sudo sshd -t
# 2. Confirm the effective settings
sudo sshd -T | grep -Ei 'passwordauth|permitroot|allowusers'
# 3. Reload SSH (Ubuntu/Debian service name is "ssh")
sudo systemctl reload ssh
# 4. In a NEW terminal, confirm you can still log in
ssh alice@server.example.com
# 5. Confirm password logins are refused
ssh -o PubkeyAuthentication=no alice@server.example.com
# Expect: Permission denied (publickey).
On some systems the service is called sshd rather than ssh. Recent Ubuntu releases also use socket activation for SSH, which matters if you change the port (see below).
Step 11: firewall SSH with UFW
A firewall limits who can even reach the SSH port. On Ubuntu, UFW makes this simple. Allow SSH before enabling the firewall, or you'll cut yourself off:
sudo ufw allow OpenSSH # or: sudo ufw limit OpenSSH
sudo ufw enable
sudo ufw status verbose
| Rule | Effect | Best for |
|---|
ufw allow OpenSSH | Allows port 22 from anywhere | When you connect from changing locations |
ufw limit OpenSSH | Allows port 22 but blocks addresses making repeated connections in a short time | A simple brute-force brake |
ufw allow from 203.0.113.10 to any port 22 | Only one IP can connect | Fixed office or home IPs |
| VPN or Tailscale only | SSH isn't exposed to the internet at all | The strongest option for small teams |
Cloud servers often have a provider firewall or security group as well. Restricting port 22 there gives you a second layer.
Step 12: add fail2ban
fail2ban watches logs and temporarily bans IP addresses that keep failing to log in. With passwords disabled it's less critical, but it cuts down log noise and wasted resources.
sudo apt install fail2ban
sudo nano /etc/fail2ban/jail.local
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
sudo systemctl restart fail2ban
sudo fail2ban-client status sshd
Put your own changes in jail.local rather than editing jail.conf, so updates don't overwrite them. If you lock out your own IP, unban it with sudo fail2ban-client set sshd unbanip YOUR.IP.
Should you change the SSH port?
Moving SSH from port 22 to, say, 2222 doesn't make it more secure against a determined attacker, because a port scan finds it quickly. What it does do is cut automated noise in your logs a great deal. If you change it:
- Allow the new port in the firewall first:
sudo ufw allow 2222/tcp. - Set
Port 2222 in sshd's config. On Ubuntu releases that use ssh.socket, the listening port is controlled by the socket unit, so check Ubuntu's documentation for your release and run sudo systemctl daemon-reload and sudo systemctl restart ssh.socket. - Test with
ssh -p 2222 alice@server before removing the old firewall rule.
Treat a changed port as tidiness, not protection. Key-only logins do the real work. You can see which ports a server exposes with our Nmap basics tutorial.
Ongoing checks
| Task | Command | How often |
|---|
| See recent successful logins | last -a | head | Weekly |
| See failed attempts | sudo journalctl -u ssh --since today | grep -i fail | When investigating |
| Review authorised keys | cat ~/.ssh/authorized_keys | When people leave or laptops change |
| Check effective config | sudo sshd -T | After updates |
| List supported algorithms | ssh -Q kex, ssh -Q cipher | When auditing |
| Check fail2ban bans | sudo fail2ban-client status sshd | Monthly |
Remove keys for anyone who no longer needs access. A forgotten key in authorized_keys is a forgotten way in.
Optional: add two-factor authentication to SSH
For extra protection on important servers, you can require a second factor as well as a key. Two common approaches:
| Method | How it works | Trade-off |
|---|
FIDO2 security key (ssh-keygen -t ed25519-sk) | The private key needs a tap on a hardware key such as a YubiKey | Very strong; needs OpenSSH 8.2 or later on both sides and a key per person |
| TOTP via PAM (e.g. libpam-google-authenticator) | Asks for a 6-digit code after the key | Works with any phone; more configuration, and needs KbdInteractiveAuthentication turned back on for that step |
| Bastion or jump host | Users connect through one hardened server with ssh -J | One place to monitor and patch |
For most small servers, a passphrase-protected Ed25519 key plus the firewall and fail2ban steps above is already a strong setup. Add hardware keys when several people have admin access or the server holds sensitive data.
Quick hardening checklist
| Check | Done when |
|---|
| System updated | apt upgrade shows nothing pending; unattended-upgrades enabled |
| Non-root sudo user | You can log in as that user and run sudo |
| Key login works | SSH logs in without asking for the server password |
| Passwords off | sshd -T shows passwordauthentication no |
| Root login off | sshd -T shows permitrootlogin no |
| Users limited | AllowUsers lists only the people who need access |
| Firewall on | ufw status shows SSH allowed and the firewall active |
| fail2ban running | fail2ban-client status sshd shows the jail |
| Recovery route known | You've opened your provider's console at least once |
Work down the table on every new server you build, and save your drop-in config file so the next one takes minutes.
Frequently asked questions
What is the most important step to secure an SSH server?
Switch to SSH key authentication and set PasswordAuthentication no. That stops password-guessing attacks, which make up most automated SSH attacks.
How do I disable root login over SSH?
Add PermitRootLogin no to /etc/ssh/sshd_config or a drop-in file in /etc/ssh/sshd_config.d, test with sudo sshd -t, then reload the service with sudo systemctl reload ssh.
Why didn't my sshd_config change take effect?
On Ubuntu and Debian, files in /etc/ssh/sshd_config.d are included at the top of the main file, and the first value sshd reads usually wins. Run sudo sshd -T to see the settings actually in use.
Does changing the SSH port improve security?
Only a little. It reduces automated log noise, but a port scan finds the new port quickly. Key-only logins, firewall rules and updates provide the real protection.
Is fail2ban still needed if passwords are disabled?
It is less critical, but it still reduces log noise and blocks persistent scanners. It is a useful extra layer rather than a replacement for key authentication.
Which SSH key type should I use?
Ed25519 is a good default for modern systems: ssh-keygen -t ed25519. Protect the private key with a passphrase.
How do I avoid locking myself out when hardening SSH?
Keep your current session open, test with sudo sshd -t, open a second terminal to confirm you can log in, allow SSH in the firewall before enabling it, and know how to reach your provider's console.
How do I check who has logged in to my server over SSH?
Use last -a to see recent logins and sudo journalctl -u ssh to review SSH logs, including failed attempts.
Last checked: 20 October 2026. Figures, settings and guidance can change, so confirm anything important with the sources linked above.