↓ Skip to main content
  1. Posts/

Hardening SSH on a hosting server: what's worth doing

Table of Contents

Changing the port is not hardening
#

Moving SSH from 22 to 2222 or 48392 cuts about 99% of the automated noise in your logs. Against a targeted attacker it helps 0%, because a full-range port scan of a /32 with masscan takes around 90 seconds.

What a non-standard port does get you is cleaner logs and less work for fail2ban. That’s worth something, but on its own it doesn’t make the server any harder to get into.

The list below is what we’d do instead, roughly in order of impact.

1. Disable password authentication
#

# /etc/ssh/sshd_config
PasswordAuthentication no
ChallengeResponseAuthentication no
PermitRootLogin prohibit-password
UsePAM yes
AuthenticationMethods publickey

This is the biggest win. Overnight, 100% of the SSH brute-force attempts in your logs become useless. You can still enforce 2FA on top.

You lose the ability to log in from a random laptop with just a password, which is intended. Carry your key.

2. Use short, strong key types
#

# on your workstation, once
ssh-keygen -t ed25519 -C "you@wherever"
  • Ed25519: fast, short (68 chars), post-2013 crypto. Use it for anything new.
  • RSA 4096: acceptable if you have to support legacy clients.
  • RSA 2048: rotate it out.
  • DSA and ECDSA (nistp256): don’t use them.

On the server, disable the old host keys:

# /etc/ssh/sshd_config
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# remove any dsa / ecdsa HostKey lines

3. Restrict sshd to modern algorithms
#

The default sshd on Debian 12 is already decent, but tighten it:

# /etc/ssh/sshd_config
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512
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,umac-128-etm@openssh.com

Then verify from the outside with ssh-audit (github.com/jtesta/ssh-audit):

ssh-audit your.server.com
# aim for all green, no yellow, no red

4. Limit which accounts can log in
#

# /etc/ssh/sshd_config
AllowUsers deploy sysadmin
# or by group
AllowGroups ssh-users

A user who isn’t meant to SSH in then can’t, even if their account exists for mail or FTP. An explicit allowlist is more reliable than implicit denial.

5. Configure fail2ban instead of running the defaults
#

A bare install works, but the defaults are too permissive. Our starting point:

# /etc/fail2ban/jail.d/sshd.local
[sshd]
enabled   = true
mode      = aggressive
maxretry  = 3
findtime  = 10m
bantime   = 24h
# recidive: stack bans for repeat offenders

Also enable the recidive jail, which bans IPs that keep coming back for weeks:

# /etc/fail2ban/jail.d/recidive.local
[recidive]
enabled  = true
bantime  = 1w
findtime = 1d
maxretry = 5

With key-only auth this is mostly cosmetic, since the bots can’t get in anyway, but less log noise is useful on its own.

6. Rate-limit new SSH connections at the firewall
#

A second layer on top of fail2ban, using nftables:

table inet filter {
    chain input {
        type filter hook input priority filter; policy drop;
        ct state established,related accept
        iif lo accept
        # rate-limit new SSH: 5 per minute per source
        tcp dport 22 ct state new limit rate 5/minute burst 5 packets accept
        tcp dport 22 drop
        tcp dport { 80, 443 } accept
        # ...
    }
}

This throttles even a decent brute-force script before fail2ban has to react.

7. Send auth logs off the server
#

If an attacker ever gets root, you can’t trust the local logs. Ship /var/log/auth.log off the server in real time, to a central log host, a SIEM or anywhere else. rsyslog forwarding to remote syslog works, as does vector / fluent-bit feeding Loki or Elastic.

8. Smaller sshd settings worth checking
#

  • UpdateHostKeys yes rotates host keys cleanly over time.
  • PermitEmptyPasswords no is obvious, but double-check it.
  • X11Forwarding no on servers.
  • LoginGraceTime 20, because 2 minutes is a generous window to give a bot.
  • MaxAuthTries 3
  • ClientAliveInterval 300 and ClientAliveCountMax 2 kill forgotten sessions.

9. Test the config before reloading
#

Run sshd -t before restarting. One bad config can lock you out of a remote box.

sshd -t && systemctl reload ssh

Also keep a second terminal logged in while you test, and close it only once a new session gets in.

Port knocking, SSO and bastion hosts
#

  • Port knocking mostly solves the same problem as moving the port, and in 2026 it’s rarely worth the operational cost.
  • SSO (Okta, Google, Teleport, etc.) works well at scale and is overkill for small operations. Start with keys and central logging.
  • A bastion host makes sense once you have more than about 5 servers. SSH to the other servers goes through that one hardened host with ProxyJump, which also gives you a single place to audit.

Checking the effective config
#

To see what sshd is actually running with right now:

sshd -T | egrep -i '^(port|protocol|password|permitroot|pubkey|kbdinteractive|allow|authenticationmethods|ciphers|kexalgorithms|macs|loginracetime|maxauthtries)'

sshd -T prints the effective config after includes and defaults. If anything in the output surprises you, your config file isn’t doing what you think.

Related