To restrict an SFTP user to a specific directory using a chroot jail, configure the
sshd_config file with the
ChrootDirectory directive and set up a dedicated group for these users. This approach isolates the user's filesystem view, preventing access to sensitive system paths while maintaining secure file transfer capabilities through SSH.
Understanding the Chroot Environment
A chroot jail alters the apparent root directory for a running process and its children. For SFTP, this means the user sees the specified path as their root
/, but they cannot traverse upwards to access the actual system root or other directories. This is a critical security measure for shared hosting environments or when providing file access to untrusted users. The primary goal is to ensure that even if the user gains shell access (which we will disable), they remain confined to their designated space.
The implementation relies on OpenSSH's built-in subsystem support. You do not need third-party tools like
scponly for basic chrooting; standard SSH configuration suffices. However, strict permissions on the chroot directory are required by the SSH daemon to prevent privilege escalation attacks. If the chroot directory is writable by the user or any group other than root, SSH will refuse to start the session for security reasons.
Configuring sshd_config
The core configuration happens in
/etc/ssh/sshd_config. You need to define a match block or global settings that target your specific users. It is best practice to create a dedicated group, such as
sftponly, to manage these restrictions centrally.
First, ensure the SFTP subsystem is correctly defined. Look for the line:
Subsystem sftp internal-sftp
This directive tells SSH to use the internal SFTP server rather than an external binary. Next, add a Match block to apply the chroot settings specifically to users in your new group. Add the following lines to the end of
sshd_config:
Match Group sftponly
ChrootDirectory /home/%u
ForceCommand internal-sftp
AllowTcpForwarding no
X11Forwarding no
Here,
ChrootDirectory /home/%u sets the root for the user to their home directory. The
%u is a token that expands to the username.
ForceCommand internal-sftp ensures that even if the user has a valid shell account, they are forced into SFTP mode only. Disabling TCP and X11 forwarding reduces the attack surface significantly.
After editing the file, restart the SSH service to apply changes:
sudo systemctl restart sshd
For more details on SSH configuration nuances, see
Understanding KEX Algorithms in sshd_config.
Setting Up User Accounts and Permissions
Creating the user involves two main steps: adding the user to the correct group and ensuring the directory structure meets SSH's strict permission requirements.
Create the group if it does not exist:
sudo groupadd sftponly
Add the user to this group and set their shell to
/usr/sbin/nologin to prevent interactive login:
sudo useradd -m -g sftponly -s /usr/sbin/nologin username
sudo passwd username
The directory ownership is crucial. The
ChrootDirectory and all parent directories up to
/ must be owned by
root and not writable by any other user or group. Since
/home/username is the chroot root, it must be owned by root. However, the user needs write access inside this directory.
To achieve this, create a subdirectory for the user to write to:
sudo mkdir /home/username/files
sudo chown username:sftponly /home/username/files
sudo chmod 750 /home/username/files
Note that
/home/username itself remains owned by
root:root with permissions
755. This satisfies the SSH requirement that the chroot root is immutable by the user, while allowing them to operate freely within the
files subdirectory.
Testing and Troubleshooting
Verify the configuration by attempting an SFTP connection from another machine:
sftp username@your_server_ip
If successful, you should land directly in
/, which corresponds to
/home/username. Attempting to run
ls -la / will show the contents of that directory, but commands like
cd .. will fail or keep you in the same place.
Common issues include permission denied errors. Check the ownership of the chroot directory tree. Every component from the chroot point up to the system root must be owned by root. Additionally, check
/var/log/auth.log or
/var/log/secure for detailed error messages if the connection fails silently.
For related issues, such as handling wildcards during uploads, refer to
SFTP Rename (mv) with Wildcard.
Frequently Asked Questions
Can I allow the user to upload files if the chroot directory is owned by root?
Yes, because the user does not need write access to the chroot root itself (
/home/username). They only need write access to a subdirectory within it, such as
/home/username/uploads. You can create this subdirectory, change its ownership to the user, and grant appropriate permissions.
What happens if I forget to set the shell to nologin?
If the shell is not set to
/usr/sbin/nologin or similar, the user might attempt an SSH login. However, the
ForceCommand internal-sftp directive overrides this, forcing them into SFTP mode regardless of their configured shell. It is still recommended to disable shell access for security hygiene.
How do I resize the disk quota for these users?
You can use standard Linux quota tools (
quota) on the partition where
/home resides. Since the chroot jail is just a directory restriction, standard filesystem quotas apply normally to the user's home directory and its subdirectories.
Does this setup work with public key authentication?
Absolutely. The chroot jail applies to the authenticated session regardless of the authentication method. As long as the user's public key is in
~/.ssh/authorized_keys and they belong to the
sftponly group, they will be jailed upon successful login.
Why does SFTP fail with "Connection closed" after entering credentials?
This is often caused by incorrect permissions on the chroot directory. Ensure that the
ChrootDirectory path and all its parents are owned by
root and have no group or world write permissions. Check the system logs for specific rejection reasons from the SSH daemon.