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
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.
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.
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: https://linux.die.net/man/1/rssh
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
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
# 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... #allowscp #allowsftp #allowcvs #allowrdist #allowrsync #allowsvnserve ...
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
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
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 192.168.1.8 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 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
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 linux-vdso.so.1 => (0x00007ffcc838e000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd58773e000) /lib64/ld-linux-x86-64.so.2 (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
It's possible to impose restrictions on users on a per-key basis using options in the
There are two main options that are particularly useful for security:
command. You can view a full list of options on the manual page here: https://linux.die.net/man/5/sshd_config
restrict option will enable all of the following restrictions for users authenticating against the key:
no-agent-forwarding- Disable SSH agent forwarding.
no-port-forwarding- Deny all port forwarding requests.
no-pty- Deny requests to allocate a tty.
no-user-rc- Disable execution of
no-X11-forwarding- Deny requests to forward an X11 session
You can then re-enable specific functionality using the corresponding options without the
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.
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).
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
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.
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 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
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
SSH users can be restricted to connecting from specific hosts using the
AllowUsers option in
The syntax for
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 192.168.1.2:
You can specify multiple users in the same line:
AllowUsers firstname.lastname@example.org email@example.com
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 firstname.lastname@example.org jamie@2a01:db8::1
Wildcards are supported too, both on user names and IPv4 addresses:
AllowUsers email@example.com.* fred@10.*.5.* *@172.16.0.1
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:
/etc/ssh/sshd_confighas the following configuration:
Match User service_pi ForceCommand internal-sftp ChrootDirectory /home/service_pi/sftp/
/home/service_pi/.ssh/authorized_keyshas the following options:
restrict,command="false" ssh-rsa AAAB...
/home/service_pi/sftp/) is fully owned by root, and only the exact files that
service_pineed to be able to write to have write permissions.
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.