Linux Hardening: SSH

2025-03-06 DFIR SSH hardening linux hardening SSH PAM

Introduction

This is a basic guide to hardening OpenSSH systems with a focus on Debian and Ubuntu systems. Most of these settings will also work on other Linux distributions and Unix-like systems, but some topics such as removing the Debian banner or enabling unattended-upgrades are specific to these distributions. As such, you need to use your head when applying these settings; some may not work well for you as written or need to be altered to whatever distribution you are using.

Some of these settings are basic or touted as “security by obscurity”, but can significantly reduce the chances of your hosts being targeted for attack.

⚠ Warning:
Keep in mind that OpenSSH settings may vary over time. These options may behave differently on newer or older versions or may not even exist at all.

Read your system's manual pages for ssh, sshd, and sshd_config!

Disabling root Logins - PermitRootLogin

In /etc/ssh/sshd_config, the PermitRootLogin directive should ideally be set to “no”:

PermitRootLogin no

Logging in directly as root makes auditing more difficult and is generally a bad idea; you should login as a user and elevate using sudo if necessary. If root access is necessary, use prohibit-password to enforce key-based authentication only:

PermitRootLogin prohibit-password

Key-based Authentication

Any serious system should have password-based authentication over SSH disabled and favor keys or certificate-based authentication.

⚠ Warning:
Prior to disabling password-based authentication, you should ensure that keys or certificates are in place and functioning. Failure to do so may result in being locked out of your system!

Creating Keys

If you only have a handful of systems, ssh keys are a solid alternative to passwords. If you are in an enterprise environment with lots of ssh servers, you should be using certificates. Setting up and maintaining a certificate authority is beyond the scope of this document, but you can read more about the process here:

Each user should have their own ssh key, and ideally per machine. Meaning if you have three laptops, each laptop should have its own key pairs for each user who utilizes ssh clients.

The problem with this is that it quickly becomes a nightmare to manage dozens of ssh users and dozens of machines. There is no easy way to revoke access to the keys; it must be done manually. However, you are the only one using a system or if you have say two systems and three users, this is not too terrible to manage with keys.

Create an ssh key pair:

ssh-keygen -t ed25519 -C "your@email.com"

Note: for legacy systems that do not support ed25519, you may need to create an RSA key:

ssh-keygen -t rsa -b 4096 -C "your@email.com"

Copying Keys to a Host

This can be done using the ssh-copy-id command:

From the client system:

ssh-copy-id user@remote_host

Alternatively, copy the contents of the .pub file of the key you wish to use, and place it in /home/user/.ssh/authorized_keys of the remote user.

The public key should look something like this:

ssh-rsa AAAAABBBBakdsjfaksd9832twe...snip... your@email.com
⚠ Warning:
Manually editing _authorized_keys_ is not the preferred method to manage keys. A lot can go wrong, causing inadvertent lockouts to your systems. A common example is that the permissions on the user's ~/.ssh directory and ~/.ssh/authorized_keys need to be 700 and 600 respectively, or the key may not function, causing frustration and possibly lockout issues.

Importing Keys From Launchpad or GitHub

If you use SSH key authentication on Launchpad or GitHub (you should!), your public keys can be copied to a host using the ssh-import-id command.

Install the ssh-import-id package on the target server:

apt install ssh-import-id

Next, login as the user you wish to install your keys and import them:

ssh-import-id gh:YOUR_GITHUB_USERNAME

or

ssh-import-id lp:YOUR_LAUNCHPAD_USERNAME

Disabling password-based authentication

After keys are verified to be working, password authentication should be disabled altogether. Disabling passwords prevents brute force attacks, default credential attacks, and lessens the impact of users setting terrible passwords.

To disable password authentication, set the PasswordAuthentication directive in sshd_config to no:

PasswordAuthentication no

Removing Debian Banner

On Debian and Ubuntu-based systems, the ssh daemon may advertise information about the underlying distribution. This can be omitted by adding the DebianBanner directive to sshd_config:

DebianBanner no

Before:

% nc localhost 22
SSH-2.0-OpenSSH_9.2p1 Debian-2+deb12u4

After:

% nc localhost 22
SSH-2.0-OpenSSH_9.2p1

Automatic Updates

Keeping software up to date automatically tends to be a good idea. This ensures that you don’t have to manually login to your systems if a new vulnerability affecting ssh is released; automatic updates has you covered.

Debian and Ubuntu variants

Debian/Ubuntu variants use unattended-upgrades:

apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
systemctl enable --now unattended-upgrades

See debconf(7) for explanations of the priority setting.

RedHat Derivatives

RedHat derivatives use dnf-automatic:

dnf install dnf-automatic

Often, /etc/dnf/automatic.conf will also need to be altered. Otherwise, dnf-automatic installs updates but does not apply them:

In /etc/dnf/automatic.conf:

apply_updates = yes

Finally, enable _dnf-automatic:

systemctl enable --now dnf-automatic.timer

Older systems may need yum-cron instead.

File Integrity Monitoring

Many malware families target SSH-related binaries by replacing them with trojanized or otherwise malicious copies, modifying the underlying configurations, or by manipulating the PAM system. See this article for a more detailed writeup.

Ensure that whatever solution you decide to use (AIDE, Tripwire, …) is configured to monitor and alert on changes to /usr/bin/ssh, /usr/sbin/sshd, /etc/ssh/*, /etc/pam.conf, /etc/pam.d/*, /usr/lib/x86_64-linux-gnu/security/*, or wherever the relevant binaries, libraries, and configurations reside.

In a pinch, you can use the methods outlined in Finding Bad with Linux Package Managers.

Change Default Port

This suggestion is often touted as silly, ineffective, or sEcuRiTy bY obScUrIty, but the fact of the matter is that most scanners and worms target only port 22. Moving sshd to a different port causes a significant drop in automated traffic to the service.

⚠ Warning:
Ensure that these settings work with your firewall settings. Changing the port and restarting the service may lock you out of the system!

To test this, run a separate ssh daemon while your original copy is running on port 22:

/usr/sbin/sshd -f /path/to/your/temporary/sshd_config

After verifying it is working correctly on the new port, change /etc/ssh/sshd_config to reflect your new port settings, kill the test sshd process, then restart the ssh service.

Mucking around with your firewall settings is beyond the scope of this document. Good luck!

To set the port, use the Port directive in sshd_config:

# Set 123456 to your desired port
Port 123456

To specify a port to use with the ssh client:

ssh -p PORT_NUMBER_HERE user@host

Configuring the SSH Client to Remember Alternate Ports

It may be cumbersome to remember several different port numbers or type in additional CLI flags every time you want to login to a server. Luckily, ssh’s config file can set defaults on a per-host basis:

Edit _~/.ssh/config~:

Host rattlesnake
	HostName rattlesnake.thunderdome.local
	Port 22222

Now, ssh daniel@rattlesnake will now default connecting to port 22222 on host rattlesnake.

The manual page for ssh_config contains more directives that may be helpful

Restricting SSH Access to Specific Users

In many cases, only one or two users need to actually login with SSH on a system. If this is the case, setting the AllowUsers directive in sshd_config may be useful:

AllowUsers daniel trevor mrpickles stevedave

Alternatively, the AllowGroups directive can be used to specify groups allowed to login:

AllowGroups wheel sshusers

Disabling unneeded features

Unless you have a compelling reason to use agent forwarding, TCP forwarding, or X11 forwarding, they can be disabled in sshd_config to reduce attack surface:

AllowAgentForwarding no
AllowTcpForwarding no
X11Forwarding no

Fail2Ban

Fail2Ban is a tool that bans IP addresses that make too many failed login attempts to a host.

Installation is straightforward:

apt install fail2ban

Fail2Ban is typically disabled by default until it is configured. It is recommended to create a jail.local file within /etc/fail2ban/, as /etc/fail2ban/jail,conf may be overwritten if the fail2ban package is upgraded:

cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

From here, edit the file to your tastes. Fail2Ban is robust and powerful software and may offer some features that interest you such as email alerts, allow lists, integration with other software besides ssh, and extra reporting. I highly encourage reading the jail.conf file in its entirety and the relevant documentation. Setting up allow lists, email lists, etc varies a lot depending on circumstances and taste. As such, this is beyond the scope of this document.

When you are done editing your configuration, enable and start the fail2ban service:

systemctl enable fail2ban
systemctl start fail2ban
⚠ Warning:
Setting oppressive lockout times often sounds like a great idea, but may work against you. Say you work in an environment with one public IP address and several sysadmins behing NAT. If one user forgets their password and triggers a Fail2Ban lockout, it will lock out YOUR ENTIRE TEAM!

Even setting the lockout time to something as low as a minute or two makes the time required for brute forcing SSH increase exponentially.

/etc/ssh/sshd_config.d/ Directory

Some distributions may add an Include directive similar to this to sshd_config:

Include /etc/ssh/sshd_config.d/*.conf

If this is the case, you can add your custom configurations to any file ending in .conf within /etc/ssh/sshd_config.d/. This is very useful for using configuration managers or scripts; you won’t have to do any inline parsing or editing of the existing sshd_config file, and settings can be added in several files, making it as granular as you want.

For example, you can add a debianbanner.conf file to /etc/ssh/sshd_config.d/ and add or delete this file as it is needed using a configuration manager such as Puppet, Ansible, or Chef.

Google Authenticator

SSH can be configured to use Google Authenticator for multifactor authentication. This is not something I tend to do, but it is possible and some people like it.

This guide seems to do a good job explaining the process:

https://goteleport.com/blog/ssh-2fa-tutorial/

Conclusion

Hopefully this gives you some ideas of ways to secure your exposed SSH services. To summarize, a simple checklist that can be modified to your tastes is as follows:

  • Disable root access in favor of logging in as a normal user and using su or sudo to elevate to root as needed.

  • Restrict access to SSH to only those who need it. Reduce attack surface by disabling unnecessary SSH server functionality such as X11 and agent forwarding.

  • Configure and enforce either key-based or certificate-based authentication in favor of password authentication.

  • Keep software up to date with automatic updates and ensure ssh-related files are not altered using a file integrity monitoring solution.

  • Proactively blocking attackers using a solution such as Fail2Ban.

While these measures improve security, SSH hardening is not a one-time process. It is imperitive to stay updated on new OpenSSH features, CVEs, and best practices. For additional reading, check out:


No notes link to this note