Skip to main content

SSH Server Hardening Guide v2

This is an updated version from last year. Thank you for the great feedback!


This article covers mainly the configuration of the SSH service and only references ways to protect the service on the host machine or via policies.

I'll use Linux with an SSH server as a reference (OpenBSD Secure Shell server according to systemd).


Important: Please test the configuration changes in a test environment or a single user or group to limit the lockout risk!

Additionally, DO NOT copy any configuration mindlessly! - Some configuration changes are just recommendations and work in most cases, but make sure those work for your system, too.

SSH Server Configuration #

The following configurations can be changed in the /etc/ssh/sshd_config file or in a separate configuration file that can be created in a subdirectory /etc/ssh/sshd_config.d/*.conf.

Side note: It is recommended to create a separate configuration file as the default file is at risk of getting overwritten with a future software update. Just make sure that the default configuration file references the subdirectory with Include /etc/ssh/sshd_config.d/*.conf.

That said, you can check the configuration file with sudo sshd -t; no output means that it is okay, and errors will be displayed if someone is not working out, like in the following example:

$ sudo sshd -t
/etc/ssh/sshd_config: line 49: Bad configuration option: DebianBanner
/etc/ssh/sshd_config: terminating, 1 bad configuration options

Use sudo sshd -T for a more verbose output, which additionally displays all the options that are used.

Almost every config file change requires a restart of the SSH server service.

Public key authentication #

You can find a guide on how to use public key authentication in this linked article. I highly recommend securing your server with public key authentication instead of password authentication.

After enabling it, make sure to turn off password authentification:

PasswordAuthentication no

It requires some configuration on the server and client, but it is worth it as it is one of the best ways to protect your server.

Changing the ssh port #

Port 2222

Change the default SSH port 22 of your host to something else. Some people think it is a must; some think it is useless. There is no perfect answer, but I hope the following list of pros and cons will help you to decide:

Pros:

  • Reduce exposure to automated attacks and bots
  • Reduce noises in the logs
  • Attackers need to port scan to find the correct port, which makes it easier to detect targeted attacks with IPS and firewalls. Great for internal servers as port scans are uncommon; for internet-facing servers, rather useless as port scans are inevitable and common

Cons:

  • It does not protect against targeted attacks, as a simple port scan can detect the correct port
  • Compatibility issues, since some clients or applications might not work with a non-default port
  • Adds complexity, as clients and scripts must be configured differently, must be documented, users must be informed, etc

Side note: choosing a port below 1024 (system or well-known port) is recommended to make it more difficult for an unprivileged user to highjack the service, as by default, non-root processes can only open ports above 1023. Just make sure to avoid conflicts with already used ports.

Disable root login #

PermitRootLogin no

Prohibits connecting as root as it is recommended to work with a separate user with optional sudo permissions.

I've got some feedback that it is unnecessary to disable this since users with sudo permissions could do the same damage, but I disagree. Most - if not all - systems have a root user, and this is known, which makes it easy to run brute-force or dictionary attacks against the system. Most attackers don't know the available users on a system, which makes the username a kind of password.

Disable login attempts with empty passwords #

PermitEmptyPasswords no

It is fairly self-explanatory, but to make sure, allowing any account without a password to log into the system is a big no-no and should be turned off immediately.

Disable SSHv1 and use SSHv2 #

Protocol 2

SSHv2 is usually the default, but it is worth ensuring SSHv1 is disabled.

There are multiple ways to check if SSHv1 is still enabled, and I show you some. The following commands can be done remotely or on the server with localhost as destination:

ssh -1 remoteuser@remoteserver
SSH protocol v.1 is no longer supported

or with a verbose output like this:

ssh -v remoteuser@remoteserver
[...]
debug1: Remote protocol version 2.0, remote software version OpenSSH_8.9p1 Ubuntu-3ubuntu0.3 
[...]

or netcat:

echo ~ | nc remoteserver 22
SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.3

Important: If you see SSH-1.99 as version, it means that SSHv1 is enabled and it should be disabled!

Restrict access to specific users or/and groups #

AllowUsers a_this a_that

AllowGroups ssh_login

This option is pretty straightforward and limits the users or groups that can access the server via SSH.

Restrict access to specific IP or network #

AllowUsers *@10.10.10.10 # affects all users

AllowUsers provider-a@111.111.111.111 provider-b@222.222.222.222

AllowUsers internal-user@10.10.0.0/16

You can further limit the access to specific IPs or networks.

Restrict access to specific interfaces #

ListenAddress 10.10.10.10

Most servers have multiple interfaces. If the server has one interface for the internal network and one for the internet, and you don't need to reach the server over the internet, it is recommended to make the SSH server listen only to the internal IP. The default is 0.0.0.0, which allows the service to listen to all interfaces.

Set an authentication timer #

LoginGraceTime 20

The authentication must happen in 20 seconds before the connection gets closed. The default is 2 minutes. It helps to prevent specific denial-of-service attacks where authentication sessions are kept open for a period of time and prevent valid authentications.

Side note: make sure that this limit works for you. This limit won't be a problem for Public Key Authentication, but if you have to wait for mail to arrive with the MFA token, 20 seconds might be too short.

Limit maximum number of attempted authentications #

MaxAuthTries 3

The default is 6, and lowering it makes it a little bit more difficult to brute-force a password since the server drops the unauthenticated connection after 3 failed attempts.

Side note: Every SSH key loaded into the ssh-agent counts as one attempt each. Keep this in mind if you have a bunch of keys loaded! Additionally, if the Kerberos/GSSAPI authentication method is enabled, the look-up of whether the client is authenticated counts as one attempt.

Limit the number of concurrent unauthenticated connections #

MaxStartups 10:30:100

This is the default and good enough, but I thought explaining this option makes sense.

Explanation MaxStartups start:rate:full:
10 - number of allowed concurrent unauthenticated connections
30 - percentage chance of randomly dropping connections attempts after reaching start (10) and the chance increases linearly until the full (100) connections are reached
100 - maximum number of unauthenticated connections after every new attempt is getting dropped

The randomized connection dropping makes it more difficult to DOS the service with unauthenticated connections.

Side note: Please remember that this option can cause problems with automation and configuration tools or transferring tools that might authenticate slowly and require separate connections.

This option only affects pre-authentication connection and does not limit anything else. Additionally, it has nothing to do with the following option.

Restrict Multiplexing #

MaxSessions 10

This is the default, limits the 'sessions' for one SSH session to 10, and is fine for most cases. Nevertheless, I thought I'd write about some options.

This option simply limits the 'sessions' - as in shell, login, or subsystems like sftp - of a single network connection (TCP)/ SSH authentication. If this limit is reached, a user could simply open another connection.


You can disable multiplexing by setting this option to 1. Every shell, sftp connection, and so on, will then each require a TCP connection, which adds overhead, but can be helpful to limit the damage a highjacked session can do, increases visibility of the connections as they are separate, and therefore troubleshoot certain issues.

But please keep in mind that disabling multiplexing can cause problems and limits some functions! Automation and configuration tools like Chef, parallel transfers via scp, and other tools that need multiplexing could stop working.


Setting MaxSessions to 0 disables all shell, login, and subsystem sessions but still allows tunneling, port forwarding, or SOCKS proxying.

This option can be used to limit the permissions of a bastion/jump host user or group to a single task.

Set up a session timeout #

ClientAliveCountMax 3

ClientAliveInterval 120

The configuration above means that the session is terminated after 6 minutes of client inactivity. After 120 seconds without receiving any data from the client, the server will ask if the client is still there. If the client does not respond, the server will try it again in 120 seconds. If the client fails to answer 3 times, the session is getting terminated.

Hide Linux Version in identification string #

DebianBanner no

The Linux version is being added as a comment on the identification string. Debian and Debian derivates add it by default, RHEL distros do not (from my experience, CentOS+RockyOS).

It changes the identification string pre-authentication from SSH-2.0-OpenSSH_8.9p1 Ubuntu-3ubuntu0.3 to SSH-2.0-OpenSSH_8.9p1.

Please note that the rest of the identification string must remain unchanged according to RFC 4253:

4.2.  Protocol Version Exchange

   When the connection has been established, both sides MUST send an
   identification string.  This identification string MUST be

      SSH-protoversion-software version SP comments CR LF

Disable tunneling and port forwarding #

AllowAgentForwarding no

AllowTcpForwarding no

PermitTunnel no

Disabling those functions makes it more difficult to use the server as a jump host to gain access to the connected networks, malicious or not. Most servers do not need those functions enabled, but to learn more, feel free to check my article about SSH tunneling and port forwarding.

Disable unused authentification methods #

KerberosAuthentication no

GSSAPIAuthentication no

ChallengeResponseAuthentication

It highly depends on your needs, but if an authentification method is unused, it should be disabled as it increases the attack surface to exploits and vulnerabilities.

Side note: Please ensure you don't disable the only method you can log in to prevent a lockout.

Disable X11 Forwarding #

X11Forwarding no

The security concern here is that X11 forwarding opens a channel from the server to the client. In an X11 session, the server can send specific X11 commands to the client, which can be dangerous if the server is compromised. Source

Disable SFTP subsystem #

If you do not need SFTP, disable it. It decreases the attack surfaces and makes the system less vulnerable to security flaws.

Just comment out the Subsystem sftp [...] out of the config by placing a # at the beginning of the lines.

Disable insecure ciphers and MACs #

Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

KexAlgorithms curve25519-sha256@libssh.org

MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256

There are even some more restrictive options, but I have not tested them myself.

Side note: Please note that some clients could encounter problems connecting to the server if they don't support the same ciphers. Either update the software on the client or add ciphers that are supported by the client to the server.

Auditing tools like ssh-audit can tell you what is secure and what is not.

Host Server configurations #

I won't go into detail in this section as it is not in the scope. I just reference methods that I have already covered and name others that can help you secure your server even further.


Methods:

  • use Fail2Ban or similar software to monitor the access logs to ban IPs that failed too many login attempts. This helps to prevent brute forcing and DOS attacks > Getting started with Fail2Ban on Linux
  • use Port Knocking to hide the service and decrease the attack surface > Port Knocking with knockd and Linux - Server Hardening
  • get notified as soon someone logs into the machine - practical for critical infrastructure > SSH - run script or command at login
  • Enable MFA with TOTP-modules like libpam-google-authenticator for your SSH access
  • use host and/or network firewalls to limit the hosts or networks that can access the server. Feel free to add this in additionally to the whitelisting of the SSH server configuration
  • use host and/or network firewalls to add a rate limit of network connection to help prevent brute-forcing and DOS attacks.

General policies:

  • keep your OS and software up-to-date
  • use a remote logging instance to keep the logs safe in case of an incident
  • audit those logs with a threat/anomaly detection of your choice
  • don't expose your services to the internet. Use VPNs instead
  • use hardened bastion/jump hosts in front of hosts that can't be properly secured
  • do scheduled and regular audits of your hardening procedure. Use tools like ssh-audit and make sure that your systems are still secure
  • check regularly if services, user accounts, permissions, etc are required and remove them, if not
  • if you use password authentication, force secure password or passphrases

Special thanks to ruffy for recommending disabling X11 forwarding and the SFTP subsystem.


E-Mail hellofoo@ittafoovern.comcom


More reading: