How to Set Up a Restricted SFTP User for One Folder on a Linux VPS (Chroot Jail Guide)

Restricted SFTP access to a single folder on a Linux VPS using an OpenSSH chroot jail
Linux VPS OpenSSH SFTP Chroot Jail
Restricted SFTP access to a single folder on a Linux VPS using an OpenSSH chroot jail

You were handed a VPS login, and now someone else needs to upload files to one folder on it. Sharing your own credentials is a bad idea. Here is the complete guide to creating an SFTP-only user who can add and edit files in a single folder, cannot open a terminal, and cannot see anything else on the server.

FTP vs FTPS vs SFTP: Which One to Use?

These three names look alike but are different technologies. For a live server, the choice is easy.

Feature FTP FTPS SFTP
Full name File Transfer Protocol FTP over SSL/TLS SSH File Transfer Protocol
Encrypted No (plain-text passwords) Yes Yes
Built on Its own protocol FTP + TLS certificates SSH
Default port 21 + random data ports 21 / 990 + data ports 22 (single port)
Extra server software Yes (vsftpd, proftpd) Yes, plus certificates None, ships with OpenSSH
Firewall friendly Poor Poor Good
SSH key login No Limited Yes

SFTP wins for this job: it runs through the SSH service already on your server, needs no new software or ports, and encrypts everything, including the password. OpenSSH can also lock a user into one folder and allow file transfer only.

📌 SFTP Is Not "FTP over SSH"
SFTP is a separate protocol that happens to run inside an SSH connection. Plain FTP clients cannot talk to it. Use a client with SFTP support such as FileZilla, Cyberduck, WinSCP, or the sftp command.

How This Setup Works

SSH's ChrootDirectory option locks a user inside a folder so that folder looks like the server's root (/). It comes with one strict rule: the jail folder and every parent folder above it must be owned by root and must not be writable by anyone else.

A folder like <target_folder> sits inside a home directory owned by a normal user, so it cannot be a jail directly. The workaround is a root-owned jail folder with your real folder bind-mounted into it. A bind mount makes the same directory appear in two places at once.

Real location on the server
<target_folder>
docs images data exports
↔
What the SFTP user sees (jail)
/<folder_name>
docs images data exports

Files uploaded over SFTP land directly in the real folder, and anything your own apps write there is visible to the SFTP user.

Prerequisites

  • SSH access to the VPS with sudo rights.
  • OpenSSH server installed (it normally is). The commands below are for Ubuntu / Debian.
  • The target folder already exists, and the owning user's group exists.
Terminal — Pre-checks
ssh -V
getent group <owner_group>
ls <target_folder>
⚠️ Keep Your Current Terminal Open
Leave your existing SSH session open for the whole process and do all testing from a second terminal. If a config mistake breaks SSH, your open session still works and you can undo the change.

Placeholders Used in This Guide

Every value that is specific to your server is written in angle brackets. Replace each one with your own value before running a command, and remove the angle brackets when you do.

Placeholder What it means Example
<username>The restricted SFTP account you are creatingsftpuser
<target_folder>Full path of the existing folder you want to share/home/appuser/shared
<folder_name>Name of that folder (what the user sees inside the jail)shared
<owner_group>Group that owns the target folder (usually your app user's group)appuser
<server_ip>Your VPS IP address or hostname203.0.113.10
<their_ip>The SFTP user's static IP (optional hardening step)198.51.100.25
<second_username>A second restricted user (maintenance section)sftpuser2
📌 Don't Paste the Brackets
A command like sudo passwd <username> will fail if you paste it as-is, because the shell treats < and > as redirection. Swap in your real value first.

Step-by-Step Setup Walkthrough

Step 1: Create a Dedicated SFTP User

Create a group for restricted users, then a user with no shell and no home directory of its own.

Terminal — Create user
sudo groupadd sftpusers
sudo useradd -g sftpusers -G <owner_group> -s /usr/sbin/nologin -d /<folder_name> -M <username>
sudo passwd <username>
Part What it does
groupadd sftpusersCreates a group. SSH applies the restrictions to everyone in it, so adding more restricted users later is easy.
-g sftpusersSets the user's primary group.
-G <owner_group>Adds the shared group (<owner_group>) so the user can write into the target folder.
-s /usr/sbin/nologinGives the user no shell, so they can never get a terminal.
-d /<folder_name>Home directory as seen inside the jail.
-MDo not create a home directory (the jail provides it).
passwdSets the password. Use a long, random one.
Step 2: Build the Jail and Bind-Mount the Folder
Terminal — Jail + bind mount
sudo mkdir -p /srv/sftp/<username>/<folder_name>
sudo chown root:root /srv/sftp /srv/sftp/<username>
sudo chmod 755 /srv/sftp /srv/sftp/<username>
sudo mount --bind <target_folder> /srv/sftp/<username>/<folder_name>
  • mkdir -p creates the jail folder with an empty <folder_name> folder inside it to act as the mount point.
  • chown and chmod make root the owner and block group/other writes, which SSH requires or it refuses the login.
  • mount --bind makes your real folder appear inside the jail.

A plain mount --bind disappears on reboot, so make it permanent through /etc/fstab:

Terminal — Persist the mount
echo '<target_folder> /srv/sftp/<username>/<folder_name> none bind 0 0' | sudo tee -a /etc/fstab
sudo mount -a
ls /srv/sftp/<username>/<folder_name>

mount -a re-reads fstab and reports any error immediately. The last command should list the same files and folders as your real target folder.

Step 3: Give the User Write Access to the Folder
Terminal — Permissions
sudo chmod -R g+rwX <target_folder>
sudo find <target_folder> -type d -exec chmod g+s {} \;
  • g+rwX gives the <owner_group> group read/write on files and folders. The capital X adds execute to folders only.
  • g+s (setgid) on folders makes new files and subfolders inherit the <owner_group> group, so your own apps can still read and modify what the SFTP user adds.
Step 4: Configure SSH

Back up the config, then open it for editing:

Terminal — Backup and edit
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config

4a. Switch to the built-in SFTP server

Find the line that starts with Subsystem sftp and change it to:

/etc/ssh/sshd_config
Subsystem sftp internal-sftp

The default entry points to an external program that would not exist inside the jail. internal-sftp is built into the SSH daemon, so it works anywhere.

4b. Add the restriction block at the very end of the file

Match blocks must come last, because everything after a Match line applies only to that match.

/etc/ssh/sshd_config (end of file)
Match Group sftpusers
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp -u 0002
    PasswordAuthentication yes
    AllowTcpForwarding no
    X11Forwarding no
    AllowAgentForwarding no
    PermitTunnel no
Line What it does
Match Group sftpusersApplies everything below only to members of sftpusers.
ChrootDirectory /srv/sftp/%uLocks the user into /srv/sftp/<username>.
ForceCommand internal-sftp -u 0002Allows only SFTP, even if they try to run a command. -u 0002 makes uploaded files group-writable.
PasswordAuthentication yesAllows password login for this group even if it is disabled globally.
The four No linesBlock port forwarding, X11, agent forwarding, and tunnels.

4c. Validate and restart

Terminal — Validate and restart
sudo sshd -t
sudo systemctl restart ssh

No output from sshd -t means the config is valid. On RHEL / CentOS / Fedora the service is called sshd instead of ssh.

Step 5: Test Everything from a Second Terminal
Terminal — SFTP test
sftp <username>@<server_ip>

sftp> pwd              # prints: Remote working directory: /
sftp> ls               # shows only: <folder_name>
sftp> cd <folder_name>
sftp> ls               # shows your existing files and folders
sftp> mkdir test-folder
sftp> cd /
sftp> cd /etc          # must FAIL
sftp> exit

Then confirm that a normal SSH login is refused:

Terminal — Shell must be blocked
ssh <username>@<server_ip>

Expected result: "This service allows sftp connections only." Finally, check that test-folder exists in the real target folder, then remove it with rmdir <target_folder>/test-folder.

Step 6: Hand Over the Details
Field Value
ProtocolSFTP (not FTP)
HostYour VPS IP
Port22 (or your custom SSH port)
Username<username>
PasswordThe one set in Step 1
ClientFileZilla, Cyberduck, or WinSCP
⚠️ Never Share Your Own Login
Send the password through a different channel than the username and IP. Never give out the main server account credentials.

Optional Hardening

Use SSH Keys Instead of a Password

Keys are far harder to guess or leak. The person generates a key pair on their computer and sends you only the public key:

Their computer — Generate key
ssh-keygen -t ed25519 -C "<username>"

The jailed user has no real home folder, so store the key in a root-owned location on the VPS:

Terminal — Install public key
sudo mkdir -p /etc/ssh/authorized_keys
sudo nano /etc/ssh/authorized_keys/<username>
sudo chown root:root /etc/ssh/authorized_keys /etc/ssh/authorized_keys/<username>
sudo chmod 755 /etc/ssh/authorized_keys
sudo chmod 644 /etc/ssh/authorized_keys/<username>

Paste the public key (one line) into the file, then update the Match block:

/etc/ssh/sshd_config (Match block)
Match Group sftpusers
    ChrootDirectory /srv/sftp/%u
    ForceCommand internal-sftp -u 0002
    AuthorizedKeysFile /etc/ssh/authorized_keys/%u
    PasswordAuthentication no
    AllowTcpForwarding no
    X11Forwarding no
    AllowAgentForwarding no
    PermitTunnel no
Terminal — Apply and test
sudo sshd -t && sudo systemctl restart ssh
sftp -i ~/.ssh/id_ed25519 <username>@<server_ip>

Always test key login before relying on PasswordAuthentication no.

Restrict by IP (If They Have a Static IP)

Terminal — UFW rule
sudo ufw allow from <their_ip> to any port 22 proto tcp

Make sure your own IP is allowed on port 22 first, so you do not lock yourself out.

Block Brute-Force Attempts

Terminal — Install fail2ban
sudo apt install fail2ban

The default configuration already protects SSH by temporarily banning IPs after repeated failed logins.

Back Up the Folder

⚠️ The SFTP User Can Overwrite and Delete Files
SFTP permissions cannot cleanly express "add only". The user can modify or remove anything inside the folder, so keep regular backups.
Terminal — Quick backup
tar -czf ~/backup-$(date +%F).tar.gz <target_folder>

Day-to-Day Maintenance

Change the Password or Disable the User

Terminal — Password and lock
sudo passwd <username>
sudo usermod -L <username>     # lock the account
sudo usermod -U <username>     # unlock it again

If you use key authentication, locking the password may not block key login. Remove or rename the key file in /etc/ssh/authorized_keys/ to be sure.

Add Another Restricted User

Repeat Steps 1 to 3 with a new username. No change to sshd_config is needed because the Match Group sftpusers block already covers every member of the group.

Terminal — Second user (example: <second_username>)
sudo useradd -g sftpusers -G <owner_group> -s /usr/sbin/nologin -d /<folder_name> -M <second_username>
sudo passwd <second_username>
sudo mkdir -p /srv/sftp/<second_username>/<folder_name>
sudo chown root:root /srv/sftp/<second_username>
sudo chmod 755 /srv/sftp/<second_username>
echo '<target_folder> /srv/sftp/<second_username>/<folder_name> none bind 0 0' | sudo tee -a /etc/fstab
sudo mount -a

Check Logs and the Bind Mount

Terminal — Logs and mount status
sudo tail -f /var/log/auth.log
findmnt /srv/sftp/<username>/<folder_name>

Removing the Setup Completely

🛑 Unmount Before You Delete Anything
Always unmount before removing anything under /srv/sftp. Deleting files through a live bind mount deletes the real files in your target folder.
Terminal — Safe removal order
# 1. Unmount FIRST
sudo umount /srv/sftp/<username>/<folder_name>

# 2. Delete the <username> line from fstab
sudo nano /etc/fstab

# 3. Delete the user
sudo userdel <username>

# 4. Remove the now-empty jail folders (rmdir refuses non-empty folders)
sudo rmdir /srv/sftp/<username>/<folder_name>
sudo rmdir /srv/sftp/<username>

# 5. If you used key auth, remove the key file
sudo rm -f /etc/ssh/authorized_keys/<username>

# 6. Restore the original SSH config
sudo cp /etc/ssh/sshd_config.bak /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart ssh

Troubleshooting Common Errors

Symptom Likely cause Fix
bad ownership or modes for chroot directory Jail folder is not root-owned or is group/world-writable Re-run the chown / chmod from Step 2
Login works but the shared folder looks empty Bind mount missing, or the folder was deleted and recreated sudo umount /srv/sftp/<username>/<folder_name>; sudo mount -a
Permission denied when uploading Group write permission missing Re-run Step 3 and check id <username> lists <owner_group>
Connection closes right after login Subsystem line or chroot path is wrong Re-check Step 4a and the ChrootDirectory line
Permission denied (publickey) Key file permissions wrong, or passwords disabled too early Fix ownership/modes, or re-enable passwords while debugging
sshd -t prints errors Typo in sshd_config Fix the named line or restore sshd_config.bak
Your apps can't edit uploaded files Wrong group or umask Confirm setgid on folders and -u 0002 in ForceCommand

For anything else, watch the log live while attempting a login: sudo tail -f /var/log/auth.log.

Quick Command Summary

The whole setup in one block, for reference. Edit /etc/ssh/sshd_config as shown in Step 4 where indicated.

Terminal — Full setup
# Step 1: user
sudo groupadd sftpusers
sudo useradd -g sftpusers -G <owner_group> -s /usr/sbin/nologin -d /<folder_name> -M <username>
sudo passwd <username>

# Step 2: jail + bind mount
sudo mkdir -p /srv/sftp/<username>/<folder_name>
sudo chown root:root /srv/sftp /srv/sftp/<username>
sudo chmod 755 /srv/sftp /srv/sftp/<username>
sudo mount --bind <target_folder> /srv/sftp/<username>/<folder_name>
echo '<target_folder> /srv/sftp/<username>/<folder_name> none bind 0 0' | sudo tee -a /etc/fstab
sudo mount -a

# Step 3: permissions
sudo chmod -R g+rwX <target_folder>
sudo find <target_folder> -type d -exec chmod g+s {} \;

# Step 4: SSH config (edit sshd_config as described, then validate)
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak
sudo nano /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl restart ssh

# Step 5: test
sftp <username>@<server_ip>
✅ You're Done
The new user can log in over SFTP, see only the shared folder, add or edit files inside it, and nothing else. No shell, no other folders, and no access to your main account. 🔒

0 Comments

⚠️

Ad Blocker Detected

We noticed you are using an ad blocker. We rely on ads to keep this website free for everyone. Please disable your ad blocker or whitelist our site to continue.