Introduction
To restrict a user to SFTP-only access on Ubuntu 24.04, create a dedicated group, add the user with /usr/sbin/nologin as their shell, set up a chroot directory owned by root, and add a Match Group block to /etc/ssh/sshd_config. The whole process takes about 20 minutes on a fresh Raff Technologies VM and requires no additional software — OpenSSH's built-in internal-sftp subsystem handles everything.
SFTP (SSH File Transfer Protocol) is a secure file transfer protocol built on top of SSH. Unlike FTP, which transmits credentials and data in plaintext, SFTP encrypts everything in transit over a single port (22). When you restrict a user to SFTP-only access with a chroot jail, they can upload and download files within their designated directory but cannot open a shell, run commands, or traverse the rest of the filesystem — a critical isolation layer for shared hosting, client portals, and CI/CD pipelines.
In this tutorial, you will: create an sftponly group, add a restricted user to it, configure the chroot directory structure with correct ownership, update /etc/ssh/sshd_config with the Match Group block, test the setup using the sftp command-line client, and verify that SSH shell access is blocked. By the end, you will have a production-ready SFTP jail that you can replicate for additional users by repeating steps 2–4.
Note
This tutorial configures SFTP chroot using OpenSSH's built-in internal-sftp subsystem. No third-party software or packages are required.
Step 1 — Verify OpenSSH is Installed and Running
Before making any changes, confirm that OpenSSH server is installed and active on your Raff VM.
bashsudo systemctl status ssh
You should see Active: active (running). If the service is not found or inactive, install and start it:
bashsudo apt update
sudo apt install -y openssh-server
sudo systemctl enable --now ssh
Next, confirm which version of OpenSSH is installed. SFTP chroot with internal-sftp requires OpenSSH 4.9 or later — Ubuntu 24.04 ships with OpenSSH 9.x, so you're well covered.
bashssh -V
Expected output:
OpenSSH_9.6p1 Ubuntu-3ubuntu13.5, OpenSSL 3.0.13 30 Jan 2024
Finally, back up the current SSH configuration before touching it. If you make a mistake in sshd_config, this backup is your recovery path.
bashsudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
Warning
On Ubuntu 24.04, OpenSSH uses systemd socket activation by default. Always restart the ssh socket — not the service — after config changes: sudo systemctl restart ssh. Restarting sshd directly may not apply changes.
Step 2 — Create the SFTP Group and User
Using a group to enforce SFTP restrictions scales far better than per-user configuration blocks. You configure the chroot rules once on the group, then add as many users to it as needed.
Create the group:
bashsudo groupadd sftponly
Now create the SFTP user. Replace sftpclient with your actual username. The -s /usr/sbin/nologin flag sets a non-login shell, which blocks SSH shell access at the OS level before sshd even evaluates the ForceCommand.
bashsudo useradd -m -s /usr/sbin/nologin -G sftponly sftpclient
Set a password for the user:
bashsudo passwd sftpclient
Verify the account was created correctly:
bashid sftpclient
getent passwd sftpclient
Expected output for getent:
sftpclient:x:1001:1001::/home/sftpclient:/usr/sbin/nologin
The /usr/sbin/nologin shell at the end confirms the user cannot start an interactive shell session.
Tip
If you already have an existing user you want to restrict, modify their shell and add them to the group instead of creating a new account: sudo usermod -s /usr/sbin/nologin -aG sftponly existinguser
Step 3 — Configure the Chroot Directory Structure
This is the step most tutorials get wrong, and the root cause of the vast majority of bad ownership or modes errors. OpenSSH has strict, non-negotiable rules for the chroot directory:
- The chroot root must be owned by root
- The chroot root must not be writable by anyone other than root (permissions 755 or 750 — never 777)
- Subdirectories inside the jail that the user needs to write to can be owned by the user
The user's home directory is /home/sftpclient. This directory will be the chroot root — it must be owned by root.
Set ownership of the home directory to root:
bashsudo chown root:root /home/sftpclient
sudo chmod 755 /home/sftpclient
Now create a writable uploads subdirectory inside the jail. The user will land here after connecting and will have full read/write access:
bashsudo mkdir /home/sftpclient/uploads
sudo chown sftpclient:sftpclient /home/sftpclient/uploads
sudo chmod 755 /home/sftpclient/uploads
Verify the ownership structure:
bashls -la /home/sftpclient/
Expected output:
total 12
drwxr-xr-x 3 root root 4096 Apr 11 09:14 .
drwxr-xr-x 4 root root 4096 Apr 11 09:12 ..
drwxr-xr-x 2 sftpclient sftpclient 4096 Apr 11 09:14 uploads
The parent directory (/home/sftpclient) is owned by root — that satisfies OpenSSH's ownership check. The uploads subdirectory is owned by sftpclient — that gives the user somewhere to actually write files.
A common mistake here is leaving the home directory owned by the user. The connection will succeed but you'll immediately see fatal: bad ownership or modes for chroot directory in /var/log/auth.log and the client will get a generic "Connection closed" error.
Step 4 — Configure OpenSSH for SFTP Chroot
On Ubuntu 24.04, you have two options for adding config: editing /etc/ssh/sshd_config directly, or adding a file to /etc/ssh/sshd_config.d/. The drop-in directory approach is cleaner because it keeps your customizations separate from the distro-managed defaults.
Create a drop-in configuration file:
bashsudo nano /etc/ssh/sshd_config.d/sftp-chroot.conf
Add the following content:
# SFTP-only chroot configuration
# Applied to all members of the sftponly group
Match Group sftponly
ForceCommand internal-sftp
ChrootDirectory %h
AllowTcpForwarding no
X11Forwarding no
PermitTunnel no
Save and close the file (Ctrl+O, Enter, Ctrl+X in nano).
What each directive does:
| Directive | Value | Purpose |
|---|---|---|
ForceCommand internal-sftp | internal-sftp | Forces the in-process SFTP server; ignores any shell command the client sends |
ChrootDirectory %h | %h (home dir) | Jails the user to their home directory after authentication |
AllowTcpForwarding no | no | Blocks SSH tunnelling through this account |
X11Forwarding no | no | Disables X11 forwarding (unnecessary for SFTP users) |
PermitTunnel no | no | Disables VPN tunnelling through this account |
internal-sftp is important here. It runs entirely in-process within sshd — unlike the external /usr/lib/openssh/sftp-server binary, internal-sftp does not need any support files (libraries, device nodes) inside the chroot jail. This is why you don't need to copy /bin/bash, libc.so, or device nodes into the jail the way older tutorials describe.
Now verify your configuration is syntactically correct before restarting:
bashsudo sshd -t
No output means the configuration is valid. If you see any errors, fix them before proceeding — an invalid sshd_config will prevent SSH from restarting, locking you out of the server.
Restart SSH to apply the changes:
bashsudo systemctl restart ssh
Step 5 — Test SFTP Access and Verify Shell Access is Blocked
With configuration in place, test both scenarios: SFTP should work, SSH shell should be blocked.
Test 1: Verify SSH shell login is blocked
Attempt to open an SSH shell as sftpclient:
bashssh sftpclient@<your-server-ip>
You should see either This account is currently not available. (from /usr/sbin/nologin) followed by a closed connection, or a direct disconnect. The user cannot get a shell — confirmed.
Test 2: Verify SFTP access works
Connect via SFTP:
bashsftp sftpclient@<your-server-ip>
Enter the password when prompted. You should land at an sftp> prompt. Verify you are inside the chroot:
bashsftp> pwd
Remote working directory: /
sftp> ls
uploads
The root of the SFTP session shows / — this is the inside of the chroot jail. The user sees their home directory as the filesystem root and cannot navigate higher. The uploads folder is visible and writable.
Test file upload:
bashsftp> cd uploads
sftp> put /etc/hostname test-upload.txt
sftp> ls -la
Expected output:
-rw-r--r-- 1 1001 1001 12 Apr 11 09:31 test-upload.txt
Test directory traversal is blocked:
bashsftp> cd /etc
Couldn't stat remote file: No such file or directory
The chroot jail is working correctly. The user cannot access /etc, /home, or any other system directory.
Tip
To test with FileZilla or another SFTP GUI client, set the protocol to "SFTP - SSH File Transfer Protocol", port 22, and use the sftpclient credentials. Do not use "FTP over TLS" — that is a different protocol.
Step 6 — Add Additional SFTP Users
One of the advantages of the group-based approach is that adding new SFTP users is a two-command operation. The OpenSSH Match Group block you already configured applies to everyone in sftponly automatically.
For each new user, repeat:
bash# Create the user
sudo useradd -m -s /usr/sbin/nologin -G sftponly newuser
sudo passwd newuser
# Fix chroot directory ownership
sudo chown root:root /home/newuser
sudo chmod 755 /home/newuser
# Create the writable uploads directory
sudo mkdir /home/newuser/uploads
sudo chown newuser:newuser /home/newuser/uploads
sudo chmod 755 /home/newuser/uploads
No SSH service restart is required. The new user can log in immediately via SFTP.
Warning
Never skip the chown root:root /home/newuser step. Ubuntu's useradd -m creates the home directory owned by the new user by default. The chroot will fail with a bad ownership error until you fix it.
Step 7 — Verify Logs and Ongoing Monitoring
Check /var/log/auth.log to confirm connections are logging correctly and to diagnose any issues:
bashsudo grep sftp /var/log/auth.log | tail -20
A successful SFTP session produces log entries like:
Apr 11 09:31:04 raff-vm sshd[4821]: Accepted password for sftpclient from 203.0.113.5 port 52301 ssh2
Apr 11 09:31:04 raff-vm sshd[4821]: subsystem request for sftp by user sftpclient
A blocked SSH shell attempt looks like:
Apr 11 09:32:15 raff-vm sshd[4833]: Accepted password for sftpclient from 203.0.113.5 port 52302 ssh2
Apr 11 09:32:15 raff-vm sshd[4833]: Changed root directory to "/home/sftpclient"
Apr 11 09:32:15 raff-vm sshd[4833]: fatal: bad ownership or modes for chroot directory "/home/sftpclient"
Wait — that last entry means your chroot directory ownership is wrong. Run ls -la /home/sftpclient to confirm root owns the directory.
A properly working setup with ForceCommand internal-sftp will never show a shell login for sftponly group members, even if they authenticate successfully.
If you want to tail logs in real time during testing:
bashsudo tail -f /var/log/auth.log
Conclusion
You have set up a fully functional SFTP-only chroot jail on Ubuntu 24.04 using OpenSSH's built-in internal-sftp subsystem. The setup restricts users in the sftponly group to their home directories via a chroot jail, blocks SSH shell access at both the shell level (/usr/sbin/nologin) and the OpenSSH config level (ForceCommand internal-sftp), and disables TCP forwarding and tunnelling to prevent the account from being used as a pivot point.
This was tested on a Raff Tier 3 VM (2 vCPU / 4 GB RAM, Ubuntu 24.04 LTS) with OpenSSH 9.6p1. The internal-sftp subsystem requires no support files inside the jail, so there is no need to copy binaries, libraries, or create device nodes — a common source of confusion in older tutorials.
As next steps, consider:
- Key-based authentication: Add an
AuthorizedKeysFileforsftpclientinside the chroot-aware path (the key file must live outside the chroot at its real filesystem path). See our SSH key setup guide for the full workflow. - Firewall rules: Limit SFTP access to specific IP ranges using UFW or your Raff firewall to reduce exposure.
- Automated backups: Schedule Raff automated backups to protect uploaded files. Block storage snapshots run outside the VM and require no agent configuration.

