DEV Community

Cover image for SSH Hardening Checklist: How to Configure Access and Verify the Result
Aeza
Aeza

Posted on

SSH Hardening Checklist: How to Configure Access and Verify the Result

An SSH hardening checklist helps eliminate unnecessary login methods on a VPS. Hardening means configuring the server to reduce unnecessary exposure and improve access security. A secure sshd_config setup comes down to leaving only the authentication methods that are actually required and making sure access can be restored if something goes wrong.

The examples below are intended for Ubuntu 24.04 LTS with OpenSSH from the operating system repositories. If you use another OS or have specific audit requirements, verify the relevant parameters separately.

Which sshd_config parameters matter most during an audit?

The main parameters are PermitRootLogin for direct root access, PasswordAuthentication and KbdInteractiveAuthentication for authentication methods, and AllowUsers or AllowGroups for restricting who is allowed to connect.

When multi-factor authentication is used, AuthenticationMethods becomes important because it defines which authentication methods must succeed before access is granted.

The configuration should be verified with real login attempts for both permitted and prohibited scenarios.

What Exactly Needs to Be Protected

A secure sshd_config configuration starts with identifying everyone who actually needs SSH access: the VPS owner, colleagues, automation, backup systems, and other service accounts.

Passwords can be brute-forced, keys can be stolen, and former employees may retain working access long after it should have been revoked.

Key-only authentication removes password guessing as an attack path. A stolen key still has to be revoked, unnecessary accounts need to be disabled, and a second factor such as a one-time code can reduce the risk associated with a compromised key.

Another risk is losing trustworthy logs. If SSH authentication events exist only on the same server, an attacker who obtains root privileges may modify or remove them, making later incident reconstruction much harder.

Configuration errors can also lock out the administrator who is applying the hardening.

Before changing anything, verify that the provider console gives you administrative access to the filesystem or an equivalent recovery mechanism. In a team environment, it may be useful to explicitly assign someone responsibility for confirming that recovery access exists.

Check Which Configuration the Server Is Actually Using

An SSH audit checklist should include the operating system version, OpenSSH version, and effective configuration before any changes are made.

You can inspect them with:

cat /etc/os-release
sudo /usr/sbin/sshd -V
sudo /usr/sbin/sshd -T
Enter fullscreen mode Exit fullscreen mode

On Ubuntu, /etc/ssh/sshd_config includes files from /etc/ssh/sshd_config.d/.

Usually, the first value read for a parameter takes precedence, and the order depends on the names of included files. Because of that, adding a setting near the end of the main configuration file may not produce the result you expect.

More details are available in the Ubuntu OpenSSH documentation.

Match blocks create exceptions for specific users, addresses, or other connection properties.

The -C option lets you evaluate the effective configuration for a particular connection:

sudo /usr/sbin/sshd -T \
  -C user=admin,addr=198.51.100.10,host=client.example

sudo ss -ltnp
Enter fullscreen mode Exit fullscreen mode

Replace the username, client address, and hostname with the values from your environment.

If a Match condition also depends on the server address or port, include laddr and lport.

The output of sshd -T represents the configuration read from files on disk.

Existing SSH sessions continue operating under the conditions established when they were created, so changes must be tested using a new connection.

Listening ports shown by ss should also be compared against firewall rules on the VPS and any network filtering configured in the provider control panel.

Identify Who Owns Every Account and SSH Key

Create an inventory of all accounts that can log in and execute commands.

Separately identify users that can obtain administrative privileges through sudo.

The authorized_keys file contains public keys that are permitted to authenticate. Every key should have a known owner. A comment written next to a public key is useful metadata, but it does not prove who actually controls the corresponding private key.

Disabling SSH password login should generally happen only after the account and key inventory has been completed.

Personal administrator accounts make it easier to distinguish individual users in logs.

Automation should use dedicated keys. For example, a backup process can have its own key rather than sharing an administrator credential.

The command= option in authorized_keys can restrict a key to a specific command, while restrict disables additional capabilities, including forwarding.

After configuring these restrictions, verify that the intended automation still works.

When rotating an SSH key, first install and test the replacement key. Only after confirming that the new key works should the previous one be removed from all servers.

Deleting a key does not terminate sessions that were authenticated earlier, so compromised active sessions may require separate investigation and termination.

How Do You Disable Password Login Without Losing Access?

First open a new SSH session using a key with a dedicated user that has working sudo access.

Keep the existing session open.

Before applying any changes, validate the configuration, verify provider console access, and prepare a rollback procedure.

After applying the new settings, open another new connection and confirm that key authentication still works.

If multi-factor authentication is enabled, verify the second factor as well.

Decide Which Authentication Methods Should Remain

The following example permits authentication only with public keys.

Replace admin with the username for which you have already tested SSH access and sudo:

# Key-only authentication; no second factor through PAM
PubkeyAuthentication yes
AuthenticationMethods publickey
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowUsers admin
Enter fullscreen mode Exit fullscreen mode

AllowUsers should contain every human administrator and service account that genuinely needs access.

If group-based management is more convenient, use AllowGroups instead.

MFA and an SSH allowlist can work together: the allowlist restricts which accounts are eligible to connect, while the second factor reduces the risk of a stolen key.

PermitRootLogin no disables direct root login completely.

PermitRootLogin prohibit-password, by contrast, still permits root authentication with a public key.

If authentication requires both a public key and a one-time code through PAM, use:

UsePAM yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive:pam
Enter fullscreen mode Exit fullscreen mode

This form of multi-factor authentication only works if an appropriate PAM module has already been configured.

keyboard-interactive itself does not necessarily mean MFA. Depending on the PAM stack, it may still prompt for a normal password.

For that reason, setting only:

PasswordAuthentication no
Enter fullscreen mode Exit fullscreen mode

is not sufficient to guarantee that every password-based authentication path has been disabled.

How to Apply Changes and Roll Them Back Safely

Before modifying the server, verify key-based login from a new connection.

Run the following from your own machine:

ssh -o ControlPath=none -o PreferredAuthentications=publickey \
  admin@SERVER
Enter fullscreen mode Exit fullscreen mode

This creates a new connection and permits only public-key authentication.

If MFA through PAM is used, specify:

publickey,keyboard-interactive
Enter fullscreen mode Exit fullscreen mode

in PreferredAuthentications.

Inside the new session, verify that sudo works.

Keep the old session open.

In this example, configuration changes are placed in a new file:

/etc/ssh/sshd_config.d/00-local-access.conf
Enter fullscreen mode Exit fullscreen mode

Make sure both this name and the corresponding .disabled filename are unused.

Save the output of sshd -T before making changes, then compare the effective values after modification, including any relevant Match conditions.

You should also determine how SSH is being started on the system.

Run:

systemctl is-enabled ssh.socket
Enter fullscreen mode Exit fullscreen mode

On Ubuntu, socket activation has been enabled by default since version 22.10.

When socket activation is used, sshd is started for incoming connections and reads the configuration for new connections, so a service reload is not required. Testing with a new session is enough.

If socket activation is disabled and a persistent service is running, apply the changes with:

systemctl is-enabled ssh.socket
sudo /usr/sbin/sshd -t && sudo systemctl reload ssh.service
Enter fullscreen mode Exit fullscreen mode

sshd -t prints nothing when the configuration is valid.

If validation fails, the command after && will not execute.

Afterward, verify the root login restriction, normal key authentication, sudo, and every login scenario that should be denied.

For rollback, rename the added configuration file from the existing session or provider console so that it no longer matches *.conf:

sudo mv /etc/ssh/sshd_config.d/00-local-access.conf \
  /etc/ssh/sshd_config.d/00-local-access.conf.disabled

sudo /usr/sbin/sshd -t
Enter fullscreen mode Exit fullscreen mode

This rollback affects only the configuration file introduced in this example.

PAM settings, firewall rules, and SSH port changes need their own rollback procedures.

Do not close the original working SSH session until every expected login and rollback scenario has been tested successfully.

What to Do With Tunneling and Cryptographic Algorithms

SSH security on a VPS also depends on what users are allowed to do after authentication.

An SSH tunnel forwards another application's traffic through the encrypted SSH connection.

AllowTcpForwarding controls TCP forwarding.

X11Forwarding allows graphical applications running on the server to display windows on the client.

AllowAgentForwarding allows processes on the server to interact with the user's local SSH agent.

Capabilities that are not required should be disabled.

However, these settings should not be treated as complete containment. A user with a normal shell may be able to implement equivalent traffic forwarding using another program.

With a supported OpenSSH version, it is usually better to start from the default cryptographic settings.

Before manually changing cipher, MAC, or key exchange algorithm lists, verify client compatibility.

Regular updates are generally more useful than maintaining a custom cryptographic configuration without a specific requirement.

MaxAuthTries limits authentication attempts within one connection.

Setting it too low may cause legitimate clients to fail before they have a chance to offer the correct key.

ClientAliveInterval controls how often the server checks whether the client is still reachable. A user simply pausing while typing commands does not by itself cause the connection to be terminated.

Are Fail2ban and a Non-Standard SSH Port Enough?

No.

Moving SSH to another port often reduces automated login attempts, but a port scan can still discover the service.

Blocking addresses after repeated authentication failures can reduce brute-force traffic, but it does not help when an attacker already has a valid private key.

A stolen working key can authenticate successfully on the first attempt.

Proper SSH hardening therefore requires more than hiding the port or installing Fail2ban.

You still need account and key management, access restrictions, updates, reliable logging, and, depending on the risk model, multi-factor authentication.

Restrict the Network and Preserve Authentication Events

A firewall can allow SSH only from administrator networks.

Test access both from an allowed network and from an external network that should be rejected.

Do not forget IPv6 if it is enabled.

Fail2ban can temporarily block addresses after repeated failed authentication attempts.

After installation, verify that it is actually reading SSH logs and applying bans.

Be careful when testing: your own failed login attempts can trigger a block against your client address.

Logs should record successful logins, failed authentication attempts, and administrative actions performed through sudo.

Recent Ubuntu events can be inspected with:

sudo journalctl -u ssh.service --since "30 minutes ago"
sudo journalctl SYSLOG_IDENTIFIER=sudo --since "30 minutes ago"
Enter fullscreen mode Exit fullscreen mode

An attacker with root access can alter local logs.

Keeping a copy on another system makes later incident reconstruction more reliable.

Verify that the records are actually arriving at the remote logging destination, that clocks on both systems are synchronized through NTP, and that logs are retained for as long as required by your audit or operational policy.

Administrators of the VPS ideally should not have permission to delete the remote copy of those logs.

How to Prove That the Hardening Works

An audit report should contain the operating system and OpenSSH versions, the list of included configuration files, the inventory of users and keys, and the configuration values before and after modification.

When comparing configuration files, include the reason for each change.

Private keys and second-factor secrets must never be included in the report.

The collected configuration and test results form the audit evidence that demonstrates whether the access policy is actually enforced.

Test the following scenarios on the server:

  • Administrator listed in AllowUsers with a valid key: login succeeds and sudo works
  • The same user using only a password: login is rejected
  • Root with a valid key: login is rejected
  • User outside AllowUsers with a valid key: login is rejected
  • Connection from a prohibited network when firewall filtering is configured: blocked by the firewall
  • Rollback to the previous configuration: new connections work according to the previous rules

To test password-based authentication from your own computer:

ssh -o ControlPath=none -o PubkeyAuthentication=no \
  -o PreferredAuthentications=password,keyboard-interactive \
  admin@SERVER
Enter fullscreen mode Exit fullscreen mode

This test disables public-key authentication.

When testing a user outside AllowUsers, use a valid key. Otherwise, the reason for rejection will remain ambiguous because the connection could simply be failing due to invalid credentials.

For MFA configurations, test a correct code, an incorrect code, and a missing code.

Record the date, operator, client address, and corresponding log entry alongside each result.

A firewall rejection can also be confirmed by checking whether the packet or connection counter for the relevant rule increases.

Repeat the access verification after system updates and whenever the composition of the administrator team changes.

A good result is simple: permitted users can log in, prohibited scenarios are rejected, and rollback has already been tested instead of being improvised during an outage.

Keep the SSH hardening checklist together with the recovery procedure.

Every exception should have a reason, an owner, and a review date.

That allows the next administrator to distinguish a legitimate operational requirement from a temporary rule that was added months earlier and forgotten.

Top comments (0)