In this guide, we are going to set up an SFTP-only server on Debian 11/12. We will also configure a chroot jail so SFTP users are restricted to a specific directory and cannot get a normal SSH shell.
SFTP (SSH File Transfer Protocol) runs over SSH, which provides encryption in transit. This makes it a safer alternative to legacy FTP, which transmits credentials and data in clear text unless wrapped with TLS.
Related Content
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
|
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 citizix@18.236.122.10
citizix@18.236.122.10'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
|
That is it. Users should now have access.
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.
Conclusion
We managed to set up an SFTP-only server on Debian 11/12 in this guide, with chroot isolation and a dedicated upload directory for SFTP users.