Any Ubuntu server with port 22 open can start receiving SSH password guesses within minutes of going online. Here’s how to harden SSH using a configuration drop-in file, Ed25519 keys, ssh-audit, UFW, and Fail2Ban.

Run journalctl -u ssh on a cloud server after it has been online for a day, and you may find hundreds of failed login attempts targeting usernames such as admin and oracle and these automated scans are common on internet-facing servers.

Reducing the attack surface means disabling password-based authentication, tightening the SSH daemon configuration, and adding network-level protections.

This guide focuses on Ubuntu 26.04 LTS, with notes for Ubuntu 24.04 where behavior or configuration differs.

TecMint Weekly Newsletter

Get the Learn Linux 7 Days Crash Course free when you join 34,000+ Linux professionals reading every Thursday.

Check your email for a magic link to get started.

Something went wrong. Please try again.

How Ubuntu Loads sshd Configuration Files

Before changing SSH settings, it’s important to understand how sshd processes its configuration because this explains why some hardening changes appear to have no effect.

The main configuration file, /etc/ssh/sshd_config, can include additional files through a directive such as Include /etc/ssh/sshd_config.d/*.conf.


On Ubuntu, these drop-in files are typically processed in lexical order. For most directives, OpenSSH uses the first value obtained, so a setting in an earlier file can override a conflicting value in a later file.

Cloud images may also include a file such as 50-cloud-init.conf that enables password authentication. Adding PasswordAuthentication no at the bottom of the main configuration file may therefore fail to change the effective setting.

To make the hardening settings take precedence, create /etc/ssh/sshd_config.d/00-hardening.conf. Its filename places it before the usual 50-cloud-init.conf drop-in, allowing your settings to take effect when no earlier configuration directive overrides them.

Keep your existing SSH session to server1 (192.168.122.248) open while making these changes. Before applying any configuration, validate it with sshd -t and confirm the effective settings with sshd -T. Test a new SSH connection before closing your existing session to avoid locking yourself out.

1. Switch to Ed25519 Keys with ssh-keygen

Before disabling password authentication, make sure SSH key-based login works. Generate an Ed25519 key pair on the admin machine (192.168.122.1), then copy the public key to server1:

ssh-keygen -t ed25519 -a 100 -C "tecmint@admin"
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

When prompted, set a passphrase to protect the private key if the key file is stolen.

  • -t ed25519 generates an Ed25519 key pair that offers strong security with compact keys and fast operations.
  • -a 100 applies 100 rounds of key derivation when protecting the private key with a passphrase, increasing the cost of offline passphrase guessing.
  • -C adds a descriptive comment to help identify the key.

The ssh-copy-id command installs the public key in the remote account’s ~/.ssh/authorized_keys file. Test the connection before proceeding:

ssh -i ~/.ssh/id_ed25519 [email protected]

Confirm that you can log in successfully without entering the account password. If you configured a key passphrase, SSH may still prompt you for it.

2. Disable Password Logins with PasswordAuthentication

Once key-based login works, connect to server1 and create an SSH configuration drop-in file:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Add these settings:

# /etc/ssh/sshd_config.d/00-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no

These directives close two common authentication paths:

  • PasswordAuthentication no prevents SSH clients from authenticating through the standard password authentication method.
  • KbdInteractiveAuthentication no disables keyboard-interactive authentication, which PAM can otherwise use to prompt users for passwords or other credentials.

 

Save the file, then validate the SSH configuration and inspect the effective values:

sudo sshd -t
sudo sshd -T | grep -Ei '^(passwordauthentication|kbdinteractiveauthentication)'

If the configuration is valid and both settings are disabled, the output should be:

passwordauthentication no
kbdinteractiveauthentication no

If either value is still yes, inspect the main configuration file and all included drop-ins for an earlier conflicting directive.

Remember that OpenSSH generally uses the first value obtained for a directive, so a file named 00-hardening.conf cannot override a conflicting value already read earlier.

After confirming the settings, restart SSH:

sudo systemctl restart ssh

Keep your existing session open. From the admin machine, test a connection with public-key authentication explicitly disabled:

ssh -o PubkeyAuthentication=no \
    -o PreferredAuthentications=password \
    [email protected]

The server should reject the connection because password authentication is disabled. Depending on the server’s available authentication methods and client version, the error may resemble:

Permission denied (publickey).

Once this test confirms the expected behavior and a separate key-based connection still works, you can safely continue with the remaining SSH hardening steps.

Remember: Disabling password authentication is only one layer of SSH security. Restricting network access, auditing the daemon configuration, and monitoring repeated login attempts provide additional protection.

If this explained why your PasswordAuthentication no line was ignored, share it with someone still editing the bottom of sshd_config.

3. Block Direct Root Logins with PermitRootLogin

Even after disabling password authentication, the root account can still log in using an SSH key if PermitRootLogin is set to prohibit-password.

Disabling direct root access ensures administrators connect through named user accounts and use sudo for privileged operations, improving accountability in system logs.

Open the existing hardening file:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Add the following directive:

PermitRootLogin no

Save the file, validate the configuration, and restart SSH:

sudo sshd -t && sudo systemctl restart ssh

The PermitRootLogin no setting blocks SSH login directly as root, regardless of whether the client uses a password or a key. Administrators must connect using an approved user account and elevate privileges with sudo.

Keep your current SSH session open and test a new connection using your administrative account before closing it.

4. Allow Only Approved Users with AllowGroups

Disabling root access and password authentication still leaves other eligible accounts able to connect with SSH keys. Use AllowGroups to restrict SSH access to members of a dedicated group.

First, create the group and add your administrative account:

sudo groupadd sshusers
sudo usermod -aG sshusers tecmint

If the group already exists, skip groupadd. Next, add this directive to /etc/ssh/sshd_config.d/00-hardening.conf:

AllowGroups sshusers

Validate and apply the configuration:

sudo sshd -t && sudo systemctl restart ssh

The AllowGroups directive permits SSH access only to users whose group memberships match the configured group. Users outside sshusers will be denied access, even if their accounts and public keys are otherwise valid.

Important: Confirm that your administrative account belongs to sshusers before applying this restriction. If you administer the server through multiple accounts, add every required account to an approved group first. Existing sessions may remain connected even after a user is no longer eligible to establish a new SSH session.

To verify group membership, run:

id tecmint

To investigate rejected connections, inspect the SSH service journal:

sudo journalctl -u ssh –since “10 minutes ago”

Look for messages indicating that a user’s groups are not permitted by AllowGroups.

5. Slow Down Guessing with MaxAuthTries and LoginGraceTime

With SSH access restricted to approved users, the next step is to limit authentication attempts and prevent unauthenticated connections from occupying resources indefinitely.

Open the hardening file again:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Add the following configuration:

MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60

Here’s what each setting does:

  • MaxAuthTries 3 limits the number of authentication attempts permitted per connection. Clients offering several keys through an SSH agent may hit this limit before reaching the intended key.
  • LoginGraceTime 30 gives a client 30 seconds to complete authentication before the server disconnects it.
  • MaxStartups 10:30:60 limits concurrent unauthenticated connections. Once more than 10 unauthenticated connections are open, the server begins probabilistically dropping new connections, increasing the rejection probability until it reaches 100% at 60 concurrent connections.

Save the file and apply the changes:

sudo sshd -t && sudo systemctl restart ssh

If a client reports too many authentication failures, specify the intended identity explicitly:

ssh -o IdentitiesOnly=yes \
    -i ~/.ssh/id_ed25519 \
    [email protected]

The IdentitiesOnly yes option prevents SSH from offering unrelated identities held by the agent when connecting to this host.

What About PerSourcePenalties?

OpenSSH 9.8 introduced PerSourcePenalties, which can temporarily refuse connections from client addresses that repeatedly fail authentication or exhibit other undesirable behavior. Ubuntu 26.04 LTS ships OpenSSH 10.2p1, which includes this feature.

Ubuntu 24.04 LTS ships OpenSSH 9.6p1 in its original release package, so the feature is not available there by default. Check the installed version and configuration before relying on it.

Check the installed version with:

ssh -V
sudo sshd -T | grep -i '^persourcepenalties'

If supported, inspect the effective PerSourcePenalties configuration before changing its defaults. This feature provides an additional layer of protection, but it does not replace firewall restrictions, key-based authentication, or monitoring.

6. Disable Unused Forwarding with AllowTcpForwarding

An authenticated SSH session can also be used to tunnel traffic into other systems or expose services through the server. If your server does not need these capabilities, disable them to reduce the attack surface.

Open the existing hardening file:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Add the following configuration:

X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no

Each setting controls a different SSH feature:

  • X11Forwarding no disables forwarding graphical applications over SSH. This is useful on headless servers that do not need remote graphical applications.
  • AllowAgentForwarding no prevents SSH clients from forwarding their local authentication agent through the server. This reduces the risk of an authenticated but compromised account being used to access the forwarded agent.
  • AllowTcpForwarding no disables TCP port forwarding, including local (ssh -L) and remote (ssh -R) tunnels.

These restrictions are appropriate for servers that provide shell access only. However, do not disable forwarding blindly on bastion hosts or jump servers.

ProxyJump itself does not require TCP forwarding on the jump host in the same way that application tunnels do, but the SSH connection through the jump host relies on its ability to establish the requested outbound SSH connection. Review your access architecture before applying restrictions.

Save the file, validate the configuration, and restart SSH:

sudo sshd -t && sudo systemctl restart ssh

Keep an existing administrative session open and test a new connection before closing it. If users depend on tunnels, agent forwarding, or graphical applications, verify those workflows separately.

SSH is more than a remote shell: it can also act as a tunnel into your network. Share this with a teammate who manages SSH access or jump hosts.

7. Close Abandoned Sessions with ChannelTimeout

SSH provides keepalive settings that help detect clients that have become unreachable. However, these settings do not automatically terminate a connected user simply because they have stopped typing.

To detect unresponsive clients and place a limit on idle shell sessions, add the following directives to /etc/ssh/sshd_config.d/00-hardening.conf:

ClientAliveInterval 300
ClientAliveCountMax 2
ChannelTimeout session=30m

These settings serve different purposes:

  • ClientAliveInterval 300 sends an encrypted keepalive request to the client after 300 seconds without receiving data from it.
  • ClientAliveCountMax 2 allows two unanswered keepalive requests before the server disconnects the client. With these values, an unresponsive connection is generally disconnected after approximately 10 minutes, depending on when the connection becomes unresponsive and when the checks occur.
  • ChannelTimeout session=30m closes an interactive session channel after 30 minutes without relevant channel activity. It requires OpenSSH 9.2 or later.

The distinction matters: a client that remains connected and responds to keepalive requests can stay connected even if its user is away from the keyboard.

ChannelTimeout provides a separate control for idle session channels, although it is not a universal inactivity timer for every type of SSH channel.

After saving the file, validate and apply the settings:

sudo sshd -t && sudo systemctl restart ssh

Check the effective values with:

sudo sshd -T | grep -Ei '^(clientaliveinterval|clientalivecountmax|channeltimeout)'

If ChannelTimeout is unsupported, sshd -t will report a configuration error. Check the installed OpenSSH version and consult man 5 sshd_config before using it, especially on Ubuntu 24.04 systems with the original OpenSSH 9.6 release.

Operational Note: Closing idle session channels can terminate legitimate long-running interactive work. Test the timeout policy with your administrative workflows before enabling it across production servers.

8. Remove Weak Algorithms with ssh-audit

So far we’ve controlled who connects and for how long, and ssh-audit now checks the encryption. Scan from the admin machine:

sudo apt install ssh-audit
ssh-audit 192.168.122.248

Review the output for weak, deprecated, or otherwise discouraged algorithms. The tool’s fail and warn labels are recommendations based on its security checks, so evaluate each finding against your OpenSSH version, compatibility requirements, and security policy before changing the configuration.

On server1, inspect the effective cryptographic defaults before overriding them:

sudo nano /etc/ssh/sshd_config.d/00-hardening.conf

Add the following configuration:

KexAlgorithms mlkem768x25519-sha256,[email protected],curve25519-sha256
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected]

Here

  • KexAlgorithms prefers post-quantum hybrids, with Curve25519 for older clients. Remove mlkem768x25519-sha256 on Ubuntu 24.04, since it needs OpenSSH 9.9 and sshd -t otherwise fails with Bad SSH2 KexAlgorithms.
  • Ciphers and MACs keep only authenticated and encrypt-then-MAC modes.

Run ssh-audit again after restarting to confirm the warnings are gone.

9. Restrict SSH by Source Address with UFW

The next layer stops unwanted traffic before it reaches sshd. If admins connect from a known network, allow SSH only from there, adding the rule before enabling UFW so your session survives:

sudo ufw allow from 192.168.122.0/24 to any port 22 proto tcp
sudo ufw enable

Servers that must accept SSH from anywhere can use sudo ufw limit 22/tcp instead, which denies an address opening six or more connections in 30 seconds.

If this kept port 22 off the open internet, share it with a colleague who builds new Ubuntu servers.

10. Ban Repeat Offenders with Fail2Ban

UFW handles addresses you can predict, and Fail2Ban bans the rest after reading failed logins from the journal:

sudo apt install fail2ban
sudo nano /etc/fail2ban/jail.local

Add the following configuration:

# /etc/fail2ban/jail.local
[sshd]
enabled  = true
backend  = systemd
maxretry = 3
findtime = 10m
bantime  = 1h
ignoreip = 127.0.0.1/8 192.168.122.1

Here:

  • backend = systemd reads SSH events from the journal.
  • maxretry, findtime, and bantime ban an address for an hour after three failures in ten minutes.
  • ignoreip keeps your admin machine from being banned.

Restart the service and check that the jail lists currently banned entries:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

If it reports Sorry but the jail ‘sshd’ does not exist, test the file with sudo fail2ban-client -t.

Monitor SSH Logins and Recover from Lockouts

Hardening SSH is not a one-time task. Monitor authentication logs regularly to identify failed login attempts, unexpected access, and suspicious activity.

To record the fingerprints of public keys used during successful authentication, add the following directive to /etc/ssh/sshd_config.d/00-hardening.conf:

LogLevel VERBOSE

The VERBOSE logging level records additional authentication details, including the fingerprint of the public key used for a successful public-key login. Validate and apply the change:

With ten layers in place, the remaining job is watching logins. Adding LogLevel VERBOSE to the drop-in file records each key’s fingerprint, and one command shows the day’s activity:

sudo sshd -t && sudo systemctl restart ssh

On Ubuntu, use journalctl to review today’s SSH authentication activity:

sudo journalctl -u ssh --since today \
    | grep -E 'Accepted|Failed|Invalid user'

Look for successful logins from unexpected addresses, repeated authentication failures, and attempts involving nonexistent accounts. Remember that log entries filtered with grep provide only a subset of the available evidence. Review the complete journal when investigating suspicious activity:

sudo journalctl -u ssh --since today

For ongoing monitoring, consider combining journal reviews with Fail2Ban, alerting, and your cloud provider’s security monitoring tools.

Recovering from an SSH Lockout

If a configuration mistake prevents new SSH connections, use an alternative access method such as your cloud provider’s serial console, recovery console, or a VM console.

For a locally managed virtual machine, virsh console may provide access if the guest has a configured serial console.

Once you regain administrative access, move the hardening drop-in out of the configuration directory and validate the remaining configuration:

sudo mv /etc/ssh/sshd_config.d/00-hardening.conf \
    /root/00-hardening.conf.disabled

sudo sshd -t

If validation succeeds, restart SSH:

sudo systemctl restart ssh

Moving the file disables all the directives it contains, so this should be a temporary recovery measure rather than the final fix. Correct the offending setting, restore the intended hardening rules, and validate the configuration before reapplying them.

Confirm that key-based login works, root login is disabled, only approved users can connect, firewall rules permit legitimate administrators, and you have tested a recovery method.

A secure SSH configuration should reduce attack exposure without locking authorized administrators out of their own servers.

Conclusion

SSH hardening works best when multiple controls work together. By enforcing Ed25519 key-based authentication, disabling direct root access, restricting login groups, limiting authentication attempts, and applying firewall rules, you can significantly reduce the attack surface of an Ubuntu server.

Fail2Ban adds another layer by temporarily banning IP addresses that trigger repeated authentication failures.

Keeping your settings in a dedicated sshd_config.d drop-in file makes the configuration easier to maintain across servers. Before deploying it elsewhere, verify that each server supports the configured directives and that the rules match its access requirements.

Use sshd -t to validate the syntax and sshd -T to inspect the effective settings.

Want to go further with SSH certificates, jump hosts, and troubleshooting? Explore our SSH Course, on pro tecmint which covers OpenSSH in 54 practical chapters.

How do you secure SSH on your servers? Which settings do you apply everywhere, and which ones have caused unexpected problems?

Share your 00-hardening.conf configuration or a troubleshooting experience in the comments. Your experience could help another administrator avoid a costly lockout.

If this article helped, with someone on your team.

TecMint Weekly Newsletter

Get the Learn Linux 7 Days Crash Course free when you join 34,000+ Linux professionals reading every Thursday.

Check your email for a magic link to get started.

Something went wrong. Please try again.

Share.
Leave A Reply