Improving the Security of Your SSH Configuration

Most developers make use of SSH servers on a regular basis and it’s quite common to be a bit lazy when it comes to the admin of some of them. However, this can create significant problems because SSH is usually served over a port that’s remotely accessible. I always spend time securing my own SSH servers according to some best practices, and you should review the steps in this article yourself.  This blog post will expand upon these best practices by offering some improvements.

Setup SSH Server Configuration

The first step is to make the SSH service accessible via only the local network and Tor. Tor brings a few benefits for an SSH server:

  • Nobody knows where users are connecting to the SSH server from.
  • Remote scans need to know the hidden service address Tor uses, which reduces the risk of automated scan attacks on known login/password and bugs in the ssh server.
  • It’s always accessible remotely, even if the user’s IP address changes; there’s no need to register IP addresses or track changes.

To do so, you’ll need to run a Tor leaf node (if you don’t know how, the Arch Linux wiki has a good article on the subject). Then, add the following lines to the end of the torrc configuration file

HiddenServiceDir /var/lib/tor/hidden_service/ssh
HiddenServicePort 22

Restart Tor, then edit the sshd_config file according to the best practices linked to above. It should include something similar to the following configurations


Protocol 2

HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ed25519_key

AuthorizedKeysFile .ssh/authorized_keys

IgnoreRhosts yes
AuthenticationMethods publickey,keyboard-interactive
PasswordAuthentication no
ChallengeResponseAuthentication yes
UsePAM yes
AllowGroups ssh-users

UsePrivilegeSeparation sandbox

Subsystem sftp /usr/lib/ssh/sftp-server


You will notice a few changes compared to the direction given in the first link. The first change is the addition of UsePrivilegeSeparation sandbox. This further restricts and isolates the process that parses the incoming connection. You might have a different implementation depending on the distro you use, but it’s an improvement anyways.

The second set of changes include adding keyboard-interactive to the AuthenticationMethods and setting ChallengeResponseAuthentication to yes. This adds the use of a One Time Password (OTP) in addition to a public key. One time passwords are a nice addition because they make it so that additional knowledge that changes over time is also needed to login. Many websites do this as an extended mean to protect login information, including GitHub, facebook, Gmail, and Amazon. If you haven’t turned on OTP or 2FA on these sites, you really should do so. Now we’ll configure the SSH server to use this.

In an OTP setup, each user has their own secret key and this key is different for every service. The following setup will be done with pam_oath from the oath-toolkit package. Please install it before continuing. For each user you want to enable OTP for do, the following

$ head -10 /dev/urandom | sha512sum | cut -b 1-30

Add the following line in /etc/users.oath for each user

HOTP/T30/6 user - a0423afcb323d675ba9bbc36d18253

The hexadecimal key should be unique and you shouldn’t copy the one in this example! Once you have added all users, make sure nobody can access this file:

 # chmod 600 /etc/users.oath
 # chown root /etc/users.oath

Now, let’s enforce all login to require the OTP code when connecting to SSH by adding  the following line at the beginning of /etc/pam.d/sshd

 auth	  sufficient usersfile=/etc/users.oath window=30 digits=6

With all that is done, you now need to add all the users to the ssh-users group; this needs to be created first with sudo groupadd ssh-users. Then, run the following command

sudo usermod -a -G ssh-users user

Now, you can restart your SSH server and it will require you to input your OTP code. You can get that code by typing the following command :

 oathtool -v -d6 a0423afcb323d675ba9bbc36d18253

This will output the following

Hex secret: a0423afcb323d675ba9bbc36d18253
Digits: 6
Window size: 0
Start counter: 0x0 (0)


The last line is the password you’ll need, and it will change over time. Obviously, this isn’t very nice to do every time you login to your server. Today, most people have a smart phone with a decent level of security on it. Most manufacturers provide full disk encryption and, sometime, even containers like Samsung Knox. So let’s use them for that purpose. I use FreeOTP on Android inside Samsung Knox; it should provide ample security for the task. Now, the easiest method to upload a key to the phone is to use a QR code. Install qrencode and proceed with the following line

qrencode -o user.png 'otpauth://totp/user@machine?secret=UBBDV7FTEPLHLOU3XQ3NDAST'

This should generate a png file of a QR code you can use to quickly and easily create a key for each user. Obviously, this png should be handled carefully as it contains the secret key for your OTP configuration!

Improving the Security of Your SSH Configuration - user

Setup SSH Client Configuration

Now it’s time to configure the SSH client you will use to connect to the server; if you also followed the instructions of this article, you should have also started on a secure configuration for your SSH client. If so, you should have something like the following configuration either at the system level in /etc/ssh/ssh_config, or in your user config ~/.ssh/config.


Host *
    PubkeyAuthentication yes
    ServerAliveInterval 300
    ServerAliveCountMax 2
    TCPKeepAlive yes
    Compression yes
    CompressionLevel 9
    UseRoaming no

Host *.onion
    ProxyCommand socat - SOCKS4A:localhost:%h:%p,socksport=9050

If you have configured your server to only answer on Tor as described in my previous blog post, you probably want to easily reach it from the various devices you use. Once Tor has been installed on them it’s possible to contact the server at the address given in /var/lib/tor/hidden_service/ssh/hostname. Remembering the random sequence of numbers and letters is not trivial; this is where one last trick in the user config will help. Append the following lines to the client configuration file

Host nice.onion
    Hostname random4name.onion

Where nice is the name you will remember to join the SSH server and random4name.onion is the real name of the hidden service. If you have not generated public key for your client, it is time to do so with the following comand :

ssh-keygen -t ed25519 -o -a 100
ssh-keygen -t rsa -b 4096 -o -a 100

These commands generate two keys, and you only need to execute the first line; ed25519 key generation is more efficient than a 4,096 bit RSA key while being just as secure. If you have any keys that weren’t generated by one of these commands, you should regenerate them now and update the server authorization keys as soon as possible. Don’t forget to protect them with a long pass-phrase and use ssh-copy-id to deploy them to the server.

You should now be able to fully trust connecting the server to the public Internet. One final note: never use ssh -A to connect to a server because it provides direct access to your private key to any admin on the server without password protection. If you need to use an intermediate host, you should instead use ProxyCommand in your client configuration.