Restricting and Locking Down SSH Users

Sunday 13th January 2019

This article outlines a number of techniques for restricting and locking down SSH users on Linux systems, and how you can use multiple different protections at once to create the most secure setup.

Skip to Section:

Restricting and Locking Down SSH Users
┣━━ SSH Security Strategy
┣━━ Restricted Shells
┣━━ Chrooting
┣━━ authorized_keys File Options
┣━━ ForceCommand Configuration
┣━━ IP/Host Whitelisting
┗━━ Creating a Secure SFTP Configuration

SSH Security Strategy

My personal strategy when it comes to locking down and restricting SSH users is to ensure that there are always multiple protections in place, so that if one were to fail, it fails securely, rather than failing open.

Why would an SSH security configuration fail?

There are many ways that an SSH security configuration could fail:

The key message is that relying on more than one security control is great defence-in-depth and hardening, and really has the potential to save you should something go wrong.

In this article, I have explained each control individually, however in most cases they can be easily combined without any problems. At the end of this page, I have documented a secure SFTP configuration, which combines multiple security controls in order to result in a secure, restricted directory that can be accessed via SFTP.

Restricted Shells

User accounts can be restricted by setting their shell to be either restricted, or completely disabled.


rssh is a restricted shell which allows you to impose restrictions on user accounts accessing an SSH server. You can view the manual page here:

For Debian-based systems, you can install rssh using apt install rssh. In order for rssh to effective, you'll have to set the shell of the user you want to restrict to be rssh. The path of the rssh binary is probably /usr/bin/rssh, however you can double-check with which rssh.

In order to change the shell of an existing user to rssh, you can use usermod --shell /usr/bin/rssh username, and in order to create a new user with rssh as the default shell, you can use adduser --shell /usr/bin/rssh username.

Now when you try to log in as the restricted user, you'll see the following message:

This account is restricted by rssh.
This user is locked out.

If you believe this is in error, please contact your system administrator.

In order to remove specific restrictions to give access to the system features you need, edit the file /etc/rssh.conf:

# This is the default rssh config file

# set the log facility.  "LOG_USER" and "user" are equivalent.
logfacility = LOG_USER 

# Leave these all commented out to make the default action for rssh to lock
# users out completely...


To remove the restrictions for a particular service (e.g. scp), simply remove the hash (#) from the start of the line, then save the file. Log out and back in, and the user will have the newly adjusted restrictions.


An alternative restricted shell is nologin, which is a shell that completed disables the ability for the user to log in. On Debian-based systems the path is usually /usr/sbin/nologin, however you can double-check using which nologin.

In order to change the shell of an existing user to nologin, you can use usermod --shell /usr/sbin/nologin username, and in order to create a new user with nologin as the default shell, you can use adduser --shell /usr/sbin/nologin username.

If you attempt to log in to a user which uses the nologin shell, you'll see the following message:

This account is currently not available.

The nologin shell is best used along with other protections in this article in order to provide failover should one of your configuration files be overwritten or a user account changed accidentally.


Chrooting simply refers to changing the perceived root directory of a system, but the term 'chroot jail' is often used to describe the use of chroot to provide security. Chrooting can provide security by limiting the resources and files that a particular user or application can access, helping to prevent a further system compromise or privilege escalation should the chrooted user or application turn rogue.

There are several different ways to chroot on Linux, but a particularly useful method for chrooting SSH users is the ChrootDirectory option in sshd_config. It's most useful when used inside a Match block, as shown below. Match blocks should always be used right at the bottom of your sshd_config file, as all configuration after them (until the end of the file or another Match block) will only apply to situations that match the criteria:

Match User jamie
  ChrootDirectory /home/jamie/

This configuration will restrict the jamie user to /home/jamie/. Running ls / as this chrooted user will show the contents of /home/jamie, but this will be transparent to the user in the chroot jail.

Setting this configuration and connecting via SSH will probably result in an error like below:

packet_write_wait: Connection to port 22: Broken pipe

This is because the chroot directory must be fully owned by root in order for chrooting to be possible. You can change this using chown root:root /path/to/chroot/dir.

Then, upon connecting, you'll most likely see a further error:

/bin/bash: No such file or directory

This is because the user is trying to start their shell, which in this case is /bin/bash. However, /bin/bash doesn't exist at the path /home/jamie/bin/bash, so the connection fails.

In order to resolve this, all of the required files and directories for whatever you want to be able to do within the chroot jail need to be available. To run a basic bash shell, the required files/directories are usually just the following:

However, in some cases you'll also need /usr.

You can copy all of these into your chroot jail by executing the following commands whilst in your desired chroot directory:

$ mkdir bin
$ cp /bin/bash bin
$ cp /lib /lib64 .

The total size of these files depends on the exact system type, but for a Xubuntu 16.04 machine they are around 1 GB. It would be very inefficient to store multiple copies of these files if you had more than 1 chroot jail, however you can use bind mounts to share the files between your host and the chroot directory. This can have serious security implications if not done properly, as it would potentially allow the chrooted user or application to modify files outside of their jail, which could lead to a full chroot escape and system compromise.

In addition to the basic files required to get a bash shell working, you'll also need to copy in anything else that is needed to do whatever you want your chrooted SSH user to be able to do. If you need to copy in more executable files, the ldd (list dynamic dependencies) command will help you to identify which libraries a particular program requires, so that you can copy those in too. For example:

ldd /bin/cat =>  (0x00007ffcc838e000) => /lib/x86_64-linux-gnu/ (0x00007fd58773e000)
        /lib64/ (0x00007fd587b08000)

When you log in to the chrooted user via SSH, you'll be fully restricted to the chroot directory, and will only be able to use the commands/programs that are available to you. For example, with a basic chroot containing only only bash and the required libraries, the resulting chroot environment will just have a raw bash prompt, and the only commands available will be bash builtins such as pwd, cd and printf.

authorized_keys File Options

It's possible to impose restrictions on users on a per-key basis using options in the authorized_keys file.

There are two main options that are particularly useful for security: restrict and command. You can view a full list of options on the manual page here:


The restrict option will enable all of the following restrictions for users authenticating against the key:

You can then re-enable specific functionality using the corresponding options without the no- prefix.

If you wish to have an insecure-by-default setup and just lock down individual options, you can just use the restrictions with the no- prefix on their own (i.e. without setting restrict), however it is definitely more secure to use restrict and then override specific restrictions where needed.


The command option allows you to force execution of a specific command using the user's shell upon initial connection. The output of the command is sent back to the connecting client, and then it is disconnected (unless another feature such as TCP forwarding is in-use).

The command is best used along with the restrictions describe above in order to ensure a high level of security.

Please note that the command option will be overridden by ForceCommand if it is set in sshd_config.

Implementing the Options in authorized_keys

The options for a specific key should be set in the authorized_keys file in a comma-separated format directly before the key. A space should be used between the options and the key. In the examples below I have used ssh-rsa AAAB... as a placeholder for the key, but in reality this is where your SSH public key goes.

Example configuration:

restrict,X11-forwarding,command="ls -la" ssh-rsa AAAB...

This particular setup will restrict any users that connect using this key, but still allow X11 forwarding to take place. Upon connection, the command ls -la will be executed using the user's shell.

If you just want to restrict the user fully, then you'll need to use:

restrict ssh-rsa AAAB...

...and if you want to enforce no specific restrictions, but enforce the execution of a specific command, use:

command="echo \"Hello\"" ssh-rsa AAAB...

Notice that double quotes (") must be escaped in the command configuration value.

ForceCommand Configuration

The ForceCommand option is very similar to the command option described above, however it is set in the server configuration in sshd_config rather than on a per-key basis in authorized_keys.

ForceCommand is most useful in Match blocks, as shown below:

Match User jamie
  ForceCommand echo "Hello"

This configuration will force the user jamie to execute the command echo "Hello" upon connection, and then disconnect (unless something else such as X11 forwarding keeps the connection open).

You can also use ForceCommand internal-sftp to force the creation of an in-process SFTP server which doesn't require any support files when used in a chroot jail. This option is best combined with the ChrootDirectory option.

IP/Host Whitelisting

SSH users can be restricted to connecting from specific hosts using the AllowUsers option in sshd_config.

The syntax for AllowUsers is localuser[@remotehost]. Ensure that you don't accidentally use remoteuser[@remotehost], as this is incorrect.

For example, in order to restrict the jamie user to connecting from

AllowUsers jamie@

You can specify multiple users in the same line:

AllowUsers jamie@ fred@

You can also choose multiple different hosts for the same user, for example an IPv4 and IPv6 address, or the addresses of completely separate machines entirely:

AllowUsers jamie@ jamie@2a01:db8::1

Wildcards are supported too, both on user names and IPv4 addresses:

AllowUsers jamie@192.168.* fred@10.*.5.* *@

Creating a Secure SFTP Configuration

My Raspberry Pi cluster uploads statistics from each of the nodes to the main JamieWeb server every 10 minutes using a secure SFTP link. This is locked down thoroughly in order to ensure that the impact would be as low as possible were the Raspberry Pi's to be compromised and taken over by an attacker.

The result is that the Raspberry Pi's only have SFTP access to a specific directory, and they can only read and write the exact files that they need to be able to, and nothing more.

The configuration is as follows:

This configuration provides a high level of defence-in-depth and hardening, and means that even if multiple of these controls were to fail, it is unlikely that an attacker would be able to get to a shell on my server to cause more serious damage.

This article is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.