
How to secure SSH access on a web server?
To secure SSH access on a web server, use key-based authentication and disable password logins, block direct root login, allow only specific users or groups to connect, limit authentication attempts, restrict SSH to trusted IP addresses where possible, keep OpenSSH updated, and monitor or automatically block repeated failed logins with a tool like Fail2Ban. For higher-risk servers, add two-factor authentication or put SSH behind a VPN or bastion host.
SSH is the front door to your server. If an attacker gets an SSH login, they can usually read your code, your database credentials, and anything else on the machine. This guide focuses on hardening the SSH server itself, explaining each important sshd_config setting, how to apply it safely, and which extra layers are worth adding.
Why SSH Is a Prime Target
Any server with port 22 open to the internet will see automated login attempts constantly. Bots try common usernames like root, admin, and ubuntu with lists of leaked or weak passwords. Most of these attacks are unsophisticated, but they only need to succeed once.
The good news is that a handful of configuration changes make SSH extremely hard to break into.
Safety First: Avoid Locking Yourself Out
SSH changes can lock you out of your own server. Before you start:
- Keep your current session open until you've confirmed new settings work.
- Test in a second terminal: Open a new SSH connection after every change.
- Know your provider's console: Most VPS and cloud providers offer a web or serial console that doesn't depend on SSH.
- Validate the config before reloading: Always run
sudo sshd -t, which checks for syntax errors. - Reload rather than restart:
reloadkeeps existing sessions alive.
Where SSH Settings Live
The main configuration file is /etc/ssh/sshd_config. On current Ubuntu and Debian releases, the main file includes everything in /etc/ssh/sshd_config.d/*.conf, which is the cleanest place for your changes because package upgrades won't overwrite them.
Create your own file:
sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
For most options, OpenSSH uses the first value it encounters. The Include line near the top of the main file means drop-in files are read first, in alphabetical order. That's why a low number like 10- is a good prefix. Some cloud images include files like 50-cloud-init.conf that enable password authentication, so check for conflicts:
sudo grep -rEi "passwordauthentication|permitrootlogin" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
Step 1: Use Key-Based Authentication
Before you turn off passwords, make sure you can log in with an SSH key. Setting keys up is covered in detail in its own guide, but in short:
# On your local machine
ssh-keygen -t ed25519 -C "you@example.com"
ssh-copy-id deploy@your-server-ip
ssh deploy@your-server-ip
Only continue once key login works for every account that needs access.
Step 2: Disable Password Authentication
Add to your hardening file:
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication replaced the older ChallengeResponseAuthentication option in recent OpenSSH versions. Disabling both password methods means only keys (or other methods you explicitly configure) can log in.
Step 3: Disable Root Login
PermitRootLogin no
Log in as a normal user and use sudo for administrative tasks. This forces attackers to guess a username as well as defeat key authentication, and it gives you a clear audit trail of who ran what.
If you have automation that genuinely needs root, PermitRootLogin prohibit-password allows root with keys only. A non-root user with specific sudo rules is still the better approach.
Step 4: Restrict Who Can Log In
Allow only the accounts that need SSH:
AllowUsers deploy alice
Or use a group, which is easier to manage as a team grows:
AllowGroups sshusers
Then create the group and add users:
sudo groupadd sshusers
sudo usermod -aG sshusers deploy
Make sure your own account is included before reloading, or you'll lock yourself out.
Step 5: Limit Attempts and Idle Sessions
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
MaxAuthTrieslimits authentication attempts per connection.LoginGraceTimedisconnects clients that don't authenticate within 30 seconds.ClientAliveIntervalandClientAliveCountMaxclose idle sessions after about 10 minutes of no response, so forgotten sessions don't stay open indefinitely.
Step 6: Disable Features You Don't Use
X11Forwarding no
AllowAgentForwarding no
PermitEmptyPasswords no
PermitUserEnvironment no
If you use SSH tunnels for tasks like connecting to a remote database, keep AllowTcpForwarding yes. Otherwise, you can set it to no. Agent forwarding is convenient but risky: anyone with root on the server could use your forwarded agent while you're connected. Use ProxyJump on the client side instead when you need to hop between servers.
Step 7: Use Modern Cryptography
Current OpenSSH versions already default to strong algorithms and have removed many weak ones. You can make your intent explicit by limiting host keys to modern types:
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
If you want to restrict key exchange and ciphers further, check which algorithms your OpenSSH version supports first:
ssh -Q kex
ssh -Q cipher
ssh -Q mac
A conservative modern set looks like this:
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
Only list algorithms that appear in the output of the commands above, and make sure your clients support them. Newer OpenSSH releases also offer the post-quantum mlkem768x25519-sha256 key exchange, which you can add if your version lists it.
Step 8: Apply and Verify
Your complete hardening file might look like this:
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
AllowGroups sshusers
MaxAuthTries 3
MaxSessions 4
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowAgentForwarding no
PermitUserEnvironment no
Validate and reload:
sudo sshd -t
sudo systemctl reload ssh
On RHEL, Rocky Linux, and AlmaLinux, the service is sshd:
sudo systemctl reload sshd
Check the effective configuration:
sudo sshd -T | grep -E "permitrootlogin|passwordauthentication|allowgroups|maxauthtries"
Now open a new terminal and confirm you can still log in. From another machine, you can also confirm password authentication is refused:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no deploy@your-server-ip
You should see Permission denied (publickey).
Step 9: Restrict SSH by IP Address
If you connect from fixed IP addresses, only allow SSH from those. With UFW:
sudo ufw allow from 203.0.113.10 to any port 22 proto tcp
sudo ufw delete allow OpenSSH
sudo ufw status numbered
Add the new rule before deleting the general one. If your home IP changes often, this approach can lock you out, so use it only with static addresses, a VPN, or a provider console as backup.
You can also restrict per user inside sshd_config using a Match block. Place Match blocks at the end of the file:
Match Address 203.0.113.10
PermitRootLogin prohibit-password
Step 10: Rate Limit and Ban Attackers
Even with keys only, repeated attempts fill your logs and consume resources. Two easy options:
UFW's built-in rate limiting blocks IPs that open six or more connections within 30 seconds:
sudo ufw limit OpenSSH
Fail2Ban bans IPs after repeated failures:
[sshd]
enabled = true
maxretry = 5
findtime = 10m
bantime = 1h
backend = systemd
Save this in /etc/fail2ban/jail.local and restart Fail2Ban.
Step 11: Consider Changing the Port
Moving SSH off port 22 (for example to 2222) cuts down on log noise from automated scans. It is not a real security control, since port scanners find it quickly, but some administrators find the quieter logs useful.
If you do it, the order matters:
- Allow the new port in your firewall:
sudo ufw allow 2222/tcp. - Add
Port 2222to your SSH config. - On Ubuntu 24.04 and later, SSH is socket-activated by default, and the listening port is generated from your SSH config. Run
sudo systemctl daemon-reloadandsudo systemctl restart ssh.socketafter changing it. On other systems, reload the SSH service as usual. - Test the new port in a second session before removing the old firewall rule.
Step 12: Add Two-Factor Authentication
For sensitive servers, you can require a time-based one-time code in addition to your key. The common approach uses the Google Authenticator PAM module:
sudo apt install libpam-google-authenticator
Each user runs google-authenticator to set up their app, then you configure PAM and SSH:
In /etc/pam.d/sshd, add:
auth required pam_google_authenticator.so
In your SSH hardening file:
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
With PasswordAuthentication no still set and @include common-auth commented out in /etc/pam.d/sshd, this requires both a key and a code. Test this very carefully in a second session, because a mistake here is an easy way to lock yourself out.
Hardware security keys are another strong option. OpenSSH supports FIDO2 keys natively with the ed25519-sk key type.
Step 13: Use a Bastion Host or VPN for Larger Setups
If you manage several servers, don't expose SSH on all of them. Instead:
- Bastion host: One hardened server accepts SSH from the internet, and others only accept SSH from it. Clients connect with
ssh -J bastion.example.com app-server. - VPN or mesh network: Tools like WireGuard or Tailscale put SSH on a private network, so port 22 isn't open to the internet at all.
- Provider access tools: Many cloud providers offer browser-based or identity-aware SSH access that avoids exposing the port.
Step 14: Keep OpenSSH Updated and Monitor Logins
- Keep automatic security updates enabled so OpenSSH patches apply quickly.
- Review successful logins regularly:
last -a | head -20
sudo journalctl -u ssh --since "24 hours ago" | grep -i "accepted"
- Audit authorized keys periodically. Remove keys belonging to people who no longer need access:
sudo find /home /root -name authorized_keys -exec sh -c 'echo "== $1"; cat "$1"' _ {} \;
FAQ: Securing SSH Access
It can be, if you use key-only authentication, disable root login, restrict users, and keep OpenSSH updated. For extra safety, limit SSH to trusted IPs or put it behind a VPN.
Only slightly. It reduces automated scan noise but doesn't stop a determined attacker. Treat it as a minor convenience, not a replacement for key-based authentication.
Use your hosting provider's web or serial console to log in and fix the configuration. That is why you should always know how to reach it before changing SSH or firewall settings.
Use no whenever possible and log in as a regular user with sudo. Use prohibit-password only if a specific tool requires root access with keys.
It isn't strictly required, since key-only SSH can't be brute-forced in practice, but Fail2Ban reduces log noise and resource use and protects other services too.
Yes. You can combine SSH keys with a TOTP code via a PAM module, or use FIDO2 hardware security keys that OpenSSH supports natively.
Conclusion
Securing SSH comes down to a few decisive settings: key-only authentication, no root login, a short list of allowed users, and limits on attempts and idle sessions. Apply them through a drop-in config file, validate with sshd -t, reload carefully, and always test in a second session before closing the first.
Once the basics are in place, add layers that fit your risk level, such as IP restrictions, rate limiting with UFW or Fail2Ban, two-factor authentication, or a VPN or bastion host. Combined with regular updates and occasional audits of authorized keys, these steps make SSH one of the strongest parts of your server rather than its weakest.


