Home
» LINUX
»
How to Restrict SSH Users to Specific Directories in Ubuntu 24.04
How to Restrict SSH Users to Specific Directories in Ubuntu 24.04
The safest way to restrict SSH users to a specific directory on Ubuntu 24.04 is to use OpenSSH's ChrootDirectory with ForceCommand internal-sftp. This is ideal when the account only needs file transfer. It gives the user an SFTP view whose filesystem root is the directory you choose, while blocking an interactive shell and SSH forwarding.
If the user genuinely needs a normal shell, the decision is different. A chrooted interactive shell needs its own shell binary, libraries, device nodes, and supporting files inside the jail. OpenSSH documents this explicitly. For most upload, backup, agency, vendor, and shared-hosting accounts, SFTP-only chroot is far simpler and less error-prone. For untrusted users who need a full command environment, a container or dedicated VM is usually easier to maintain than hand-building a shell chroot.
This guide uses Ubuntu 24.04 LTS and an example user named alice. The user will be restricted to /srv/sftp/alice and will be allowed to write only inside /files. Replace the names and paths to suit your server.
Before you start: understand the ownership rule
The most common chroot failure is caused by permissions. OpenSSH requires every component of the ChrootDirectory path to be owned by root and not writable by group or other users. This requirement is checked unconditionally. That means you should not make /srv/sftp/alice writable by alice.
Instead, make the jail root root-owned and create a writable child directory:
This design follows the behavior documented in the Ubuntu 24.04 sshd_config(5) manual. The same manual notes that internal-sftp needs no extra runtime files inside the chroot, which is why it is the best fit for a restricted file-transfer account.
Step 1: Verify that OpenSSH Server is installed
Verify that OpenSSH Server is installed and that ssh.service is running before changing access rules.
Ubuntu's official OpenSSH documentation uses openssh-server for the daemon package and ssh.service for service management. See the Ubuntu Server OpenSSH guide.
If this is a remote machine and SSH is your only administrative path, keep your existing administrator session open while making changes. A bad SSH configuration can lock you out.
Step 2: Create a group and add the restricted user
Create a dedicated group so the restriction can be applied consistently to one or many SFTP-only accounts.
Create a group for restricted users. If alice already exists, only add her to the group:
sudo groupadd sftpusers
sudo adduser alice
sudo usermod -aG sftpusers alice
id alice
If you already use SSH public keys, keep doing so. The chroot configuration controls what happens after authentication; it does not require you to switch from key authentication to passwords.
A group-based rule is usually better than a separate Match User block for every account. It keeps policy in one place. A per-user rule is still useful when different users must be jailed to unrelated layouts.
Step 3: Build a root-owned jail and a writable subdirectory
The chroot root remains owned by root; only the child files directory is writable by the restricted user.
ls -ld /srv /srv/sftp /srv/sftp/alice /srv/sftp/alice/files
namei -l /srv/sftp/alice
If /srv or /srv/sftp is group-writable or user-writable, fix that too. OpenSSH checks all path components, not just the final directory.
Step 4: Add a Match rule in sshd_config.d
A Match Group block applies the chroot and SFTP-only policy only to members of sftpusers.
Ubuntu's current server documentation recommends keeping custom SSH settings in /etc/ssh/sshd_config.d/ rather than mixing them into the packaged main file. Create a snippet:
Match Group sftpusers
ChrootDirectory /srv/sftp/%u
ForceCommand internal-sftp -d /files
DisableForwarding yes
PermitTTY no
Match all
%u expands to the authenticated username, so alice is placed in /srv/sftp/alice. The -d /files argument tells the internal SFTP server to start inside the writable child directory. DisableForwarding yes disables X11, agent, TCP, and StreamLocal forwarding in one directive; it is specifically documented for simplifying restricted configurations.
The final Match all is important in a snippet because it ends the conditional context before later configuration is parsed.
Step 5: Decide between group-based and per-user restrictions
Use a dedicated account when a vendor, backup job, or external user should have no normal shell access.
For multiple users with the same layout, keep the group rule shown above. For a single exceptional account, use a user-specific block instead:
Match User vendor1
ChrootDirectory /srv/vendor/vendor1
ForceCommand internal-sftp -d /incoming
DisableForwarding yes
PermitTTY no
Match all
Choose based on administration cost:
Scenario
Best rule
Why
Many upload-only users with the same structure
Match Group
One policy applies to every member.
One vendor needs a unique directory
Match User
The path and restrictions can differ without affecting others.
User needs a normal interactive shell
Do not use this SFTP-only recipe
An interactive chroot needs a complete runtime environment.
Untrusted user needs commands and strong isolation
Consider a container or VM
Easier to define and update a complete execution environment.
Step 6: Validate the SSH configuration before restarting
Before applying a chroot rule, validate both the syntax and the effective per-user settings.
Never restart SSH immediately after editing the file. First run the syntax test recommended by Ubuntu:
sudo sshd -t
No output means the syntax check succeeded. You can also inspect the effective configuration for a particular user:
You should see the expected chroot path and forced SFTP command. This is especially useful on Ubuntu because settings can come from both /etc/ssh/sshd_config and files under /etc/ssh/sshd_config.d/. Ubuntu notes that OpenSSH generally uses the first value obtained for most directives, so checking the effective configuration is safer than assuming which file wins.
Step 7: Restart SSH and test with SFTP
After a clean configuration test, restart SSH and verify that the account enters only the jailed SFTP filesystem.
Apply the change:
sudo systemctl restart ssh.service
sudo systemctl status ssh.service
Then connect from another terminal:
sftp alice@server.example.com
Inside SFTP, test:
pwd
ls
put test.txt
ls -l
With the configuration above, the initial SFTP directory should be /files. The slash shown by SFTP is the root of the chroot, not the real server root.
A normal shell attempt should not produce an unrestricted shell:
ssh alice@server.example.com
Because ForceCommand internal-sftp replaces the requested session with the in-process SFTP server, this account is intentionally for file transfer rather than command execution.
Step 8: Confirm the user cannot escape the jail
Test the boundary explicitly: paths such as the host's /etc and /root must not become visible outside the chroot.
Try to move above the apparent root and inspect sensitive host paths:
cd ..
ls /
ls /etc
ls /root
Inside the chroot, / represents /srv/sftp/alice on the host. If you created no etc or root directory inside that jail, the user cannot reach the host's real /etc or /root.
Also test an upload into /files. A configuration that blocks escape but accidentally makes the entire jail read-only is secure but not useful for an upload account.
Troubleshooting: “bad ownership or modes for chroot directory”
If login fails immediately, inspect the SSH service log:
sudo journalctl -fu ssh.service
Ubuntu's OpenSSH guide recommends the SSH service journal for troubleshooting. If you see an ownership or mode error, inspect every parent directory:
namei -l /srv/sftp/alice
The chroot path must be root-owned and not writable by group or others. A common mistake is this:
sudo chown alice:alice /srv/sftp/alice
Do not solve the problem by weakening OpenSSH checks. Put the user's writable area below the chroot root instead:
What if the user needs SSH shell access, not just SFTP?
ChrootDirectory can also be used with an interactive shell, but the setup is substantially more complex. OpenSSH states that an interactive chroot needs at least a shell and basic /dev nodes, plus any binaries, libraries, name-resolution files, and other dependencies required by the commands you allow.
For a tightly controlled appliance account, building such a jail can be justified. For general Linux command access, it often creates a maintenance burden: security updates for tools and libraries must also be reflected inside the jail. If your actual requirement is “let this person run commands but isolate them from the host,” a container, VM, or purpose-built restricted service is usually easier to audit.
Security improvements worth adding
Directory restriction is only one layer. For internet-facing servers, also consider:
Use SSH public-key authentication for human and automation accounts where practical.
Do not give restricted file-transfer users sudo access.
Keep DisableForwarding yes for SFTP-only accounts so the SSH connection cannot be repurposed for tunnels or agent forwarding.
Limit writable directories to the smallest area the workflow needs.
Review journalctl -u ssh.service and file ownership after adding users.
Keep an administrator session open while testing remote SSH configuration changes.
If the goal is “this SSH account may upload and download files only inside one directory,” use a root-owned ChrootDirectory, place writable content in a user-owned child directory, and force internal-sftp. Validate with sshd -t before restarting, then test both the allowed upload path and attempted access outside the jail. That gives you a small, understandable security boundary without having to construct a complete Linux environment inside the chroot.