The author selected the Electronic Frontier Foundation Inc to receive a donation as part of the Write for DOnations program.
Linux servers are often administered remotely using SSH by connecting to an OpenSSH server, which is the default SSH server software used within Ubuntu, Debian, CentOS, FreeBSD, and most other Linux/BSD-based systems. Significant effort is put into securing the server-side aspect of SSH, as SSH acts as the entry into your server.
However, it is also important to consider security on the client-side, such as OpenSSH client.
OpenSSH client is the “client” side of SSH, also known as the ssh
command. You can learn more about the SSH client-server model in SSH Essentials: Working with SSH Servers, Clients, and Keys.
When hardening SSH at the server side, the primary objective is to make it harder for malicious actors to access your server. However, hardening at the client side is very different, as instead you are working to defend and protect your SSH connection and client from various different threats, including:
In this tutorial, you will harden your OpenSSH client in order to help ensure that outgoing SSH connections are as secure as possible.
To complete this tutorial, you will need:
A device that you use as an SSH client, for example:
An SSH server that you want to connect to, for example:
Once you have these ready, log in to your SSH client device as a non-root user to begin.
In this first step, you will implement some initial hardening configurations in order to improve the overall security of your SSH client.
The exact hardening configuration that is most suitable for your client depends heavily on your own threat model and risk threshold. However, the configuration described in this step is a general, all-round secure configuration that should suit the majority of users.
Many of the hardening configurations for OpenSSH client are implemented using the global OpenSSH client configuration file, which is located at /etc/ssh/ssh_config
. In addition to this, some configurations may also be set using the local SSH configuration file for your user, located at ~/.ssh/config
.
Before continuing with this tutorial, it is recommended to take a backup of both of these files, so that you can restore them in the unlikely event that something goes wrong.
Take a backup of the files using the following commands:
- sudo cp /etc/ssh/ssh_config /etc/ssh/ssh_config.bak
- cp ~/.ssh/config ~/.ssh/config.bak
This will save a backup copy of the files in their default location, but with the .bak
extension added.
Note that your local SSH configuration file (~/.ssh/config
) may not exist if you haven’t used it in the past. If this is the case, it can be safely ignored for now.
You can now open the global configuration file using your favorite text editor to begin implementing the initial hardening measures:
- sudo nano /etc/ssh/ssh_config
Note: The OpenSSH client configuration file includes many default options and configurations. Depending on your existing client setup, some of the recommended hardening options may already have been set.
When editing your configuration file, some options may be commented out by default using a single hash character (#
) at the start of the line. To edit these options, or have the commented option be recognized, you’ll need to uncomment them by removing the hash.
Firstly, you should disable X11 display forwarding over SSH by setting the following options:
ForwardX11 no
ForwardX11Trusted no
X11 forwarding allows for the display of remote graphical applications over an SSH connection, however this is rarely used in practice. By disabling it, you can prevent potentially malicious or compromised servers from attempting to forward an X11 session to your client, which in some cases can allow for filesystem permissions to be bypassed, or for local keystrokes to be monitored.
Next, you can consider disabling SSH tunneling. SSH tunneling is quite widely used, so you may need to keep it enabled. However, if it isn’t required for your particular setup, you can safely disable it as a further hardening measure:
Tunnel no
You should also consider disabling SSH agent forwarding if it isn’t required, in order to prevent servers from requesting to use your local SSH agent to authenticate onward SSH connections:
ForwardAgent no
In the majority of cases, your SSH client will be configured to use password authentication or public-key authentication when connecting to servers. However, OpenSSH client also supports other authentication methods, some of which are enabled by default. If these are not required, they can be disabled to further reduce the potential attack surface of your client:
GSSAPIAuthentication no
HostbasedAuthentication no
If you’d like to know more about some of the additional authentication methods available within SSH, you may wish to review these resources:
OpenSSH client allows you to automatically pass custom environment variables when connecting to servers, for example, to set a language preference or configure terminal settings. However, if this isn’t required in your setup, you can prevent any variables being sent by ensuring that the SendEnv
option is commented out or completely removed:
# SendEnv
Finally, you should ensure that strict host key checking is enabled, to ensure that you are appropriately warned when the host key/fingerprint of a remote server changes, or when connecting to a new server for the first time:
StrictHostKeyChecking ask
This will prevent you from connecting to a server when the known host key has changed, which could mean that the server has been rebuilt or upgraded, or could be indicative of an ongoing person-in-the-middle attack.
When connecting to a new server for the first time, your SSH client will ask you whether you want to accept the host key and save it in your ~/.ssh/known_hosts
file. It’s important that you verify the host key before accepting it, which usually involves asking the server administrator or browsing the documentation for the service (in the case of GitHub/GitLab and other similar services).
Save and exit the file.
Now that you’ve completed your initial configuration file hardening, you should validate the syntax of your new configuration by running SSH in test mode:
- ssh -G .
You can substitute the .
with any hostname to test/simulate any settings contained within Match
or Host
blocks.
If your configuration file has a valid syntax, the options that will apply to that specific connection will be printed out. In the event of a syntax error, there will be an output describing the issue.
You do not need to restart any system services for your new configuration to take effect, although existing SSH sessions will need to be re-established if you want them to inherit the new settings.
In this step, you completed some general hardening of your OpenSSH client configuration file. Next, you’ll restrict the ciphers that are available for use in SSH connections.
Next, you will configure the cipher suites available within your SSH client to disable support for those that are deprecated/legacy.
Begin by opening your global configuration file in your text editor:
- sudo nano /etc/ssh/ssh_config
Next, ensure that the existing Ciphers
configuration line is commented out by prefixing it with a single hash (#
).
Then, add the following to the top of the file:
Ciphers -arcfour*,-*cbc
This will disable the legacy Arcfour ciphers, as well as all ciphers using Cipher Block Chaining (CBC), which is no longer recommended for use.
If there is a requirement to connect to systems that only support these legacy ciphers, you can explicitly re-enable the required ciphers for specific hosts by using a Match
block. For example, to enable the 3des-cbc
cipher for a specific legacy host, the following configuration could be used:
Match host legacy-server.your-domain
Ciphers +3des-cbc
Save and exit the file.
Finally, as you did in Step 1, you may wish to test your SSH client configuration again to check for any potential errors:
- ssh -G .
If you have added a Match
block to enable legacy ciphers for a specific host, you can also specifically target that configuration during the test by specifying the associated host address:
- ssh -G legacy-server.your-domain
You’ve secured the ciphers available to your SSH client. Next, you will review the access permissions for files used by your SSH client.
In this step, you’ll lock down the permissions for your SSH client configuration files and private keys to help prevent accidental or malicious changes, or private key disclosure. This is especially useful when using a shared client device between multiple users.
By default on a fresh installation of Ubuntu, the OpenSSH client configuration file(s) are configured so that each user can only edit their own local configuration file (~/.ssh/config
), and sudo/administrative access is required to edit the system-wide configuration (/etc/ssh/ssh_config
).
However, in some cases, especially on systems that have been in existence for a long time, these configuration file permissions may have been accidentally modified or adjusted, so it’s best to reset them to make sure that the configuration is secure.
You can begin by checking the current permissions value for the system-wide OpenSSH client configuration file using the stat
command, which you can use to show the status or files and/or filesystem objects:
- stat -c "%a %A %U:%G" /etc/ssh/ssh_config
You use the -c
argument to specify a custom output format.
Note: On some operating systems, such as macOS, you will need to use the -f
option to specify a custom format rather than -c
.
In this case, the %A %a %U:%G
option will print the permissions for the file in octal and human-readable format, as well as the user/group that owns the file.
This will output something similar to the following:
Output644 -rw-r--r-- root:root
In this case, the permissions are correct, root owns the file entirely, and only root has permission to write to/modify it.
Note: If you’d like to refresh your knowledge on Linux permissions before continuing, you may wish to review An Introduction to Linux Permissions.
However, if your own output is different, you should reset the permissions back to the default using the following commands:
- sudo chown root:root /etc/ssh/ssh_config
- sudo chmod 644 /etc/ssh/ssh_config
If you repeat the stat
command from earlier in this step, you will now receive the correct values for your system-wide configuration file.
Next, you can carry out the same checks for your own local SSH client configuration file, if you have one:
- stat -c "%a %A %U:%G" ~/.ssh/config
This will output something similar to the following:
Output644 -rw--r--r-- user:user
If the permissions for your own client configuration file permissions are any different, you should reset them using the following commands, similarly to earlier in the step:
- chown user:user ~/.ssh/config
- chmod 644 ~/.ssh/config
Next, you can check the permissions for each of the SSH private keys that you have within your ~/.ssh
directory, as these files should only be accessible by yourself, and not any other users on the system.
Begin by printing the current permission and ownership values for each private key:
- stat -c "%a %A %U:%G" ~/.ssh/id_rsa
This will output something similar to the following:
Output600 -rw------- user:user
It is extremely important that you properly lock down the permissions for your private key files, as failing to do so could allow other users of your device to steal them and access the associated servers or remote user accounts.
If the permissions aren’t properly configured, use the following commands on each private key file to reset them to the secure defaults:
- chown user:user ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
In this step, you assessed and locked down the file permissions for your SSH client configuration files and private keys. Next, you will implement an outbound allowlist to limit which servers your client is able to connect to.
In this final step, you will implement an outgoing allowlist in order to restrict the hosts that your SSH client is able to connect to. This is especially useful for shared/multi-user systems, as well as SSH jump hosts or bastion hosts.
This security control is specifically designed to help protect against human error/mistakes, such as mistyped server addresses or hostnames. It can be easily bypassed by the user by editing their local configuration file, and so isn’t designed to act as a defense against malicious users/actors.
If you want to restrict outbound connections at the network level, the correct way to do this is using firewall rules. This is beyond the scope of this tutorial, but you can check out UFW Essentials: Common Firewall Rules and Commands.
However, if you want to add some additional fail-safes, then this security control may be of benefit to you.
It works by using a wildcard rule within your SSH client configuration file to null route all outbound connections, apart from those to specific addresses or hostnames. This means that if you were ever to accidentally mistype a server address, or attempt to connect to a server that you’re not supposed to, the request would be stopped immediately, giving you the opportunity to realize your mistake and take corrective action.
You can apply this at either the system-level (/etc/ssh/ssh_config
) or using your local user configuration file (~/.ssh/config
). In this example, we will use the local user configuration file.
Begin by opening the file, creating it if it doesn’t already exist:
- nano ~/.ssh/config
At the bottom of the file, add the following content, substituting in your own list of allowed IP addresses and hostnames:
Match host !203.0.113.1,!192.0.2.1,!server1.your-domain,!github.com,*
Hostname localhost
You must prefix IP addresses or hostnames with an exclamation point (!
), and use commas to separate each item in the list. The final list item should be a single asterisk (*
) without a prefixed exclamation point.
If you’re running an SSH server on your machine too, you may wish to use a hostname value other than localhost
, as this will cause the null routed connections to be sent to your own local SSH server, which could be counterproductive or confusing. Any nullrouted hostname is acceptable, such as null
, do-not-use
, or disallowed-server
.
Save and close the file once you’ve made your changes.
You can now test that the configuration is working by attempting to connect to a disallowed destination using your SSH client. For example:
- ssh disallowed.your-domain
If the configuration is working properly, you will immediately receive an error similar to the following:
OutputCannot connect to localhost: connection refused
However, when you attempt to connect to an allowed destination, the connection will succeed as normal.
In this final step, you implemented some additional fail-safes to help protect against human error and mistakes when using your SSH client.
In this article you reviewed your OpenSSH client configuration and implemented various hardening measures.
This will have improved the security of your outgoing SSH connections, as well as helping to ensure that your local configuration files cannot be accidentally or maliciously modified by other users.
You may wish to review the manual pages for OpenSSH client and its associated configuration file to identify any potential further tweaks that you want to make:
Finally, if you want to harden OpenSSH at the server side too, check out How To Harden OpenSSH on Ubuntu 18.04.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!