How to Set Up an SFTP Server on Debian 11/12 (Step-by-Step with Chroot)

Set up an SFTP-only server on Debian 11 or 12 with OpenSSH, chroot isolation, and internal-sftp. Restrict users to /srv/sftp with no shell access using sshd_config and sftpusers group.

SFTP (SSH File Transfer Protocol) runs over SSH and encrypts both credentials and data in transit, making it a secure alternative to plain FTP. This guide walks you through setting up an SFTP-only server on Debian 11 or 12 with chroot isolation: users in a dedicated group get only internal-sftp (no shell) and are restricted to their own directory under /srv/sftp.

In this guide you’ll:

  • Install and enable OpenSSH server (which provides SFTP)
  • Create SFTP-only users with /usr/sbin/nologin and a chroot layout under /srv/sftp/<username>/upload
  • Configure sshd_config with Match Group sftpusers, ChrootDirectory, and ForceCommand internal-sftp
  • Test access with the sftp client and optionally add extra directories inside the chroot

Related guides:

Prerequisites

To follow along this guide ensure you have the following:

  • A Debian 11 or Debian 12 server
  • Root access to the server or a user with sudo access
  • Internet access from the server

Ensuring that the server is up to date

Before proceeding, ensure your system is up to date. Use this command to refresh the system packages and update them.

1
2
sudo apt update
sudo apt upgrade -y

Ensuring that the SSH service is installed

Install OpenSSH server (this provides both SSH and SFTP):

1
2
3
4
5
6
sudo apt install -y openssh-server
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
openssh-server is already the newest version (1:8.4p1-5).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

Now that it is installed, enable and start the service:

1
sudo systemctl enable --now ssh

Confirm its status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sudo systemctl status ssh
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Fri 2021-12-03 10:18:03 UTC; 2 days ago
       Docs: man:sshd(8)
             man:sshd_config(5)
    Process: 665 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
   Main PID: 682 (sshd)
      Tasks: 1 (limit: 4626)
     Memory: 6.2M
        CPU: 9.132s
     CGroup: /system.slice/ssh.service
             └─682 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

Dec 05 09:20:46 ip-10-2-40-122 sshd[14717]: Received disconnect from 221.181.185.94 port 47597:11:  [preauth]
Dec 05 09:20:46 ip-10-2-40-122 sshd[14717]: Disconnected from authenticating user root 221.181.185.94 port 47597 [preauth]
Dec 05 10:34:30 ip-10-2-40-122 sshd[14797]: Received disconnect from 222.186.30.76 port 23207:11:  [preauth]
Dec 05 10:34:30 ip-10-2-40-122 sshd[14797]: Disconnected from authenticating user root 222.186.30.76 port 23207 [preauth]
Dec 05 10:34:41 ip-10-2-40-122 sshd[14799]: Received disconnect from 221.181.185.151 port 18104:11:  [preauth]
Dec 05 10:34:41 ip-10-2-40-122 sshd[14799]: Disconnected from authenticating user root 221.181.185.151 port 18104 [preauth]
Dec 05 11:11:54 ip-10-2-40-122 sshd[14830]: Received disconnect from 221.131.165.65 port 19729:11:  [preauth]
Dec 05 11:11:54 ip-10-2-40-122 sshd[14830]: Disconnected from authenticating user root 221.131.165.65 port 19729 [preauth]
Dec 05 11:19:03 ip-10-2-40-122 sshd[14840]: Accepted publickey for admin from 105.231.148.146 port 60649 ssh2: RSA SHA256:nDQ1FMciYtGpPYjdOwbUTVg7kQxEFtAjoSdWulRilIA
Dec 05 11:19:03 ip-10-2-40-122 sshd[14840]: pam_unix(sshd:session): session opened for user admin(uid=1000) by (uid=0)

Create SFTP users and directories (chroot layout)

We will store SFTP users under /srv/sftp using this layout:

  • /srv/sftp/<username>: chroot directory (must be owned by root and not writable)
  • /srv/sftp/<username>/upload: writable directory owned by the user (where file uploads go)

Create the base directory:

1
2
3
sudo mkdir -p /srv/sftp
sudo chown root:root /srv/sftp
sudo chmod 755 /srv/sftp

Create an umbrella group for SFTP-only users:

1
sudo groupadd sftpusers

Create an SFTP-only user called citizix (no shell login):

1
sudo useradd -g sftpusers -d /upload -s /usr/sbin/nologin citizix

The above options do the following:

  • -g sftpusers: Set the primary group to sftpusers
  • -d /upload: Set the home directory (inside the chroot we will create)
  • -s /usr/sbin/nologin: Disable shell access (SFTP-only)
  • Finally, username citizix

Now create the chroot directory and the writable upload directory with correct ownership and permissions:

1
2
3
4
5
6
7
8
9
sudo mkdir -p /srv/sftp/citizix/upload

# Chroot directory must be owned by root and not writable by the user
sudo chown root:root /srv/sftp/citizix
sudo chmod 755 /srv/sftp/citizix

# Writable directory for uploads/downloads
sudo chown citizix:sftpusers /srv/sftp/citizix/upload
sudo chmod 755 /srv/sftp/citizix/upload

Then add password to the created user using this command:

1
2
3
4
sudo passwd citizix
New password:
Retype new password:
passwd: password updated successfully

Configure SSH for SFTP-only users (chroot)

Now we’ll configure OpenSSH so users in the sftpusers group:

  • can only use SFTP (no shell)
  • are chrooted to /srv/sftp/%u
  • land in /upload on login

Edit the SSH server config:

1
sudo vim /etc/ssh/sshd_config

Ensure the SFTP subsystem uses internal-sftp (this is often the default, but it’s safe to set explicitly):

1
Subsystem sftp internal-sftp

Add this content at the bottom of the file:

1
2
3
4
5
6
7
Match Group sftpusers
    X11Forwarding no
    AllowTcpForwarding no
    PermitTTY no
    ForceCommand internal-sftp -d /upload
    ChrootDirectory /srv/sftp/%u
    PasswordAuthentication yes

Then restart SSH to reload the config:

1
sudo systemctl restart ssh

Verify that SSH is running as expected:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
sudo systemctl status ssh
● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; vendor preset: enabled)
     Active: active (running) since Sun 2021-12-05 11:22:02 UTC; 12s ago
       Docs: man:sshd(8)
             man:sshd_config(5)
    Process: 15292 ExecStartPre=/usr/sbin/sshd -t (code=exited, status=0/SUCCESS)
   Main PID: 15293 (sshd)
      Tasks: 1 (limit: 4626)
     Memory: 1.0M
        CPU: 159ms
     CGroup: /system.slice/ssh.service
             └─15293 sshd: /usr/sbin/sshd -D [listener] 0 of 10-100 startups

Dec 05 11:22:02 ip-10-2-40-122 systemd[1]: Starting OpenBSD Secure Shell server...
Dec 05 11:22:02 ip-10-2-40-122 sshd[15293]: Server listening on 0.0.0.0 port 22.
Dec 05 11:22:02 ip-10-2-40-122 sshd[15293]: Server listening on :: port 22.
Dec 05 11:22:02 ip-10-2-40-122 systemd[1]: Started OpenBSD Secure Shell server.

Verifying that the set up is working as expected

After successfully creating the user and adding SFTP configurations, let’s test the setup:

1
2
3
4
sftp [email protected]
[email protected]'s password:
Connected to 18.236.122.10.
sftp>

Try a few commands:

1
2
3
4
sftp> pwd
sftp> ls -la
sftp> put local-file.txt
sftp> get remote-file.txt

If everything is configured correctly, the user will be restricted to the chroot and should start in /upload.

Adding shared directories inside the chroot (optional)

If you want users to access additional directories, they must still be inside the chroot (/srv/sftp/<username>/...). Also remember: the chroot directory itself must be root-owned and not writable.

Example: create a shared directory under the chroot and make a subdirectory writable:

Create the directory

1
2
3
4
sudo mkdir -p /srv/sftp/citizix/paymentfiles/upload
sudo chown root:root /srv/sftp/citizix/paymentfiles
sudo chmod 755 /srv/sftp/citizix/paymentfiles
sudo chown citizix:sftpusers /srv/sftp/citizix/paymentfiles/upload

Users in the chroot will then see and use the new directory (e.g. paymentfiles/upload).

Optional hardening tips

  • Disable direct root login over SSH by setting PermitRootLogin no in /etc/ssh/sshd_config.
  • If you only need SFTP for these accounts, keep shell access disabled (/usr/sbin/nologin) as shown above.
  • Consider using key-based authentication for admin users and restricting SSH access with AllowUsers/AllowGroups.

Frequently Asked Questions (FAQ)

What is SFTP?

SFTP (SSH File Transfer Protocol) is a secure file transfer protocol that runs over SSH. Unlike traditional FTP, it encrypts authentication and data in transit. OpenSSH provides the SFTP subsystem (internal-sftp), so no separate FTP server is needed.

What port does SFTP use?

SFTP uses the same port as SSH: port 22. There is no separate SFTP port; the SSH server handles both SSH sessions and SFTP subsystem requests.

What is chroot for SFTP?

Chroot restricts an SFTP user to a specific directory (e.g. /srv/sftp/username) so they cannot see or access the rest of the filesystem. The chroot directory must be owned by root and not writable by the user; you give the user a writable subdirectory (e.g. upload) for their files.

How do I allow only SFTP (no SSH shell)?

Set the user’s shell to /usr/sbin/nologin and use a Match Group block in /etc/ssh/sshd_config with ForceCommand internal-sftp -d /upload and ChrootDirectory /srv/sftp/%u. Users in that group will only get an SFTP session, not a login shell.

Why use internal-sftp?

internal-sftp is the SFTP subsystem built into OpenSSH. It is simpler and more secure than the deprecated sftp-server and works correctly with ChrootDirectory for per-user chroot jails.

Conclusion

You now have an SFTP-only server on Debian 11/12 with chroot isolation: users in the sftpusers group can only use SFTP (no shell), are restricted to /srv/sftp/<username>, and use the upload directory for file transfers. For client usage, see how to work with the SFTP client in Linux or automate transfers with Python or Go.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy