Linux VPS

Create an SFTP-only user limited to one folder

Give someone file access over SFTP without a shell, locked inside one folder. Configure internal-sftp with a Match block, set the chroot ownership right and test with WinSCP.

AdvancedLinux server5 min read

An SFTP-only account lets a designer, a client or a backup tool upload files without getting a shell on your server. With a chroot, the user also cannot see anything outside one folder. OpenSSH can do all of this on its own with a few lines of config. The tricky part is folder ownership, which this guide explains step by step.

Before you start

  • A Linux VPS with Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux or Rocky Linux 8 or 9.
  • Root or sudo access over SSH, and a second SSH session open while you change the SSH config.
  • An SFTP client to test with, such as WinSCP or FileZilla.

Warning: A mistake in sshd_config can stop new SSH logins, including yours. Always run sudo sshd -t before reloading, and keep an existing session open until a fresh login works. The browser console in your VPS provider's control panel is the way back in if you get locked out.

How a chroot works

ChrootDirectory makes a folder appear as / to the user. They cannot go above it. For security, OpenSSH enforces strict rules:

  • The chroot folder and every folder above it must be owned by root.
  • None of them may be writable by any other user or group.

So the user cannot write to the top of their own chroot. You give them a writable subfolder inside it instead.

Create a group and a user

Using a group means one Match block covers every SFTP-only account:

Bash
sudo groupadd sftponly
sudo useradd -g sftponly -s /usr/sbin/nologin -d /srv/sftp/client1 -M client1
sudo passwd client1
  • -s /usr/sbin/nologin blocks shell logins.
  • -d sets the home folder to the future chroot path.
  • -M skips creating the home folder; you will build it by hand with the right ownership.

Build the folder structure

Bash
sudo mkdir -p /srv/sftp/client1/uploads
sudo chown root:root /srv/sftp /srv/sftp/client1
sudo chmod 755 /srv/sftp /srv/sftp/client1
sudo chown client1:sftponly /srv/sftp/client1/uploads
sudo chmod 750 /srv/sftp/client1/uploads

The result:

drwxr-xr-x root    root     /srv/sftp
drwxr-xr-x root    root     /srv/sftp/client1
drwxr-x--- client1 sftponly /srv/sftp/client1/uploads

The user lands in a read-only top folder and works inside uploads.

Configure OpenSSH

Ubuntu, Debian, AlmaLinux 9 and Rocky 9 read extra files from /etc/ssh/sshd_config.d/. Check that the main file contains the include line:

Bash
grep -i '^Include' /etc/ssh/sshd_config

If it prints Include /etc/ssh/sshd_config.d/*.conf, create a drop-in:

Bash
sudo nano /etc/ssh/sshd_config.d/50-sftponly.conf

On a system without the include line, such as AlmaLinux or Rocky 8, add the same block at the very end of /etc/ssh/sshd_config instead.

Match Group sftponly
    ChrootDirectory %h
    ForceCommand internal-sftp
    AllowTcpForwarding no
    X11Forwarding no
    PermitTunnel no
  • Match Group sftponly applies the lines below only to members of that group.
  • ChrootDirectory %h uses each user's home folder as their chroot.
  • ForceCommand internal-sftp runs the built-in SFTP server, which needs no extra files inside the chroot.
  • The other lines block tunnelling tricks.

The main config should also use the built-in SFTP server. Check the Subsystem line:

Bash
grep -i '^Subsystem' /etc/ssh/sshd_config

Subsystem sftp /usr/lib/openssh/sftp-server (Debian, Ubuntu) or /usr/libexec/openssh/sftp-server (AlmaLinux, Rocky) is fine, because ForceCommand internal-sftp overrides it for this group.

Test and reload:

Bash
sudo sshd -t
sudo systemctl reload ssh

On AlmaLinux and Rocky the service is called sshd, so use sudo systemctl reload sshd.

Note: If password logins are disabled with PasswordAuthentication no, the SFTP user needs an SSH key, or you can add PasswordAuthentication yes inside the Match block for this group only.

Set a sensible upload umask (optional)

To control permissions on uploaded files, add a umask to the forced command:

    ForceCommand internal-sftp -u 0027

New files will then be 640 and new folders 750.

Test with WinSCP

  1. Open WinSCP and choose New Site.
  2. Set File protocol to SFTP, Host name to 203.0.113.10, Port to 22 (or your custom SSH port), and enter client1 and its password.
  3. Click Login. You should see / with a single folder, uploads.
  4. Upload a file into uploads. Try to create a folder at the top level; it should fail with a permission error.

Check it works

Confirm that a shell login is refused:

Bash
ssh client1@203.0.113.10
This service allows sftp connections only.
Connection to 203.0.113.10 closed.

Then connect with sftp client1@203.0.113.10 and run pwd. It should print /, and cd.. should keep you there.

Troubleshooting

  • Login fails at once with "broken pipe" or "connection closed": chroot ownership is wrong. Read the log with sudo journalctl -u ssh -n 20 (or -u sshd) and look for bad ownership or modes for chroot directory. Fix it with chown root:root and chmod 755 on every folder in the path.
  • Login works but uploads fail: the user is writing to the top folder. Upload into uploads, or check its ownership.
  • Match block is ignored: another Match block or a later setting overrides it. Place it at the end of the file, and confirm with sudo sshd -T -C user=client1 | grep -i chroot.
  • Permission denied with the right password: password logins are turned off for this user. Allow them in the Match block or use a key. See Fix WinSCP and FileZilla permission denied.

Next steps

  • Point the chroot at a website folder: SFTP-only user for a website.
  • Use keys in your client: WinSCP login with an SSH key.
  • Learn the transfer basics: Transfer files with SCP and SFTP.
Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.