Type something to search...
How to secure SSH access on a web server?

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:

  1. Keep your current session open until you've confirmed new settings work.
  2. Test in a second terminal: Open a new SSH connection after every change.
  3. Know your provider's console: Most VPS and cloud providers offer a web or serial console that doesn't depend on SSH.
  4. Validate the config before reloading: Always run sudo sshd -t, which checks for syntax errors.
  5. Reload rather than restart: reload keeps 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
  • MaxAuthTries limits authentication attempts per connection.
  • LoginGraceTime disconnects clients that don't authenticate within 30 seconds.
  • ClientAliveInterval and ClientAliveCountMax close 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:

  1. Allow the new port in your firewall: sudo ufw allow 2222/tcp.
  2. Add Port 2222 to your SSH config.
  3. 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-reload and sudo systemctl restart ssh.socket after changing it. On other systems, reload the SSH service as usual.
  4. 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.

Tags :
Share :

Related Posts

What are the best WordPress security plugins?

What are the best WordPress security plugins?

The best WordPress security plugins for most sites are Wordfence, Sucuri Security, Solid Security, MalCare, All-In-One Security (AIOS), Patchstack, a

Dive Deeper
What are the most common website security threats?

What are the most common website security threats?

The most common website security threats are vulnerable or outdated software, weak and stolen passwords, malware infections, injection attacks like S

Dive Deeper
How does GDPR affect website security?

How does GDPR affect website security?

GDPR affects website security by turning it from a good habit into a legal obligation. If your website collects personal data from people in the EU (

Dive Deeper