Setting up accounts for SFTP only access within a chroot

So, you want to setup an SFTP server that will not allow your users to do any of the other things that ssh would normally allow (like a shell) and you don't want them to be able to look all around your server and read any world readable file. You found the ChrootDirectory setting in man sshd_config but it didn't just work because you can't chroot a user to their home directory because the chroot directory has to be owned by root and only writable by root. Well duh, it is called chroot not chhomedir. The user's home directory is supposed to be inside of the chroot not the root of it. The good news is that this is completely possible to accomplish with a few tricks.

Note: This is written specifically for OpenSSH on Linux. It is not distribution specific but other operating systems will probably need some syntax changes.

Also, I am assuming you know how to add a user to your OS/distro.

Also, This will NOT allow scp. Only sftp.

Also, This will NOT allow rsync either. If you want to use rsync you can skip all of this and use rrsync to restrict the user.

Also, If you are doing this to allow users to upload a web site to a web server you are probably wasting your time. If your web server allows any kind of scripting (PHP, perl, python, etc) then it will be trivial for the user to upload a script that will do whatever they want without the chroot restrictions. Unless of course the web server is also chrooted which is a much more complicated setup.

First, let's setup the /etc/ssh/sshd_config file. We are going to use a group called sftp-only to denote which users these settings will apply to. You can list the users if you wish instead but using a group makes it pretty easy as you can tell adduser/useradd to set the account up in that group initially. You need to add a Match block to apply different settings to users in the sftp-only group:

Match Group sftp-only
  ChrootDirectory /chroots/%u
  X11Forwarding no
  AllowTcpForwarding no
  ForceCommand internal-sftp

Note: you do not need to change the Subsystem sftp line to internal-sftp. Doing so can actually have security implications for non-matched users (internal-sftp works even if the user does not have a valid $SHELL while the external one runs under the user's $SHELL).

I know, you are screaming "But my users are in /home/%u not /chroots/%u!". Well, this is where things get interesting but require a bit of per-user work. For each $UserName that will use this setup perform the following steps:

mkdir -p /chroots/$UserName/home/$UserName
chown root:sftp-only /chroots /chroots/$UserName /chroots/$UserName/home /chroots/$UserName/home/$UserName
chmod 0750 /chroots /chroots/$UserName /chroots/$UserName/home /chroots/$UserName/home/$UserName
echo "/home/$UserName /chroots/$UserName/home/$UserName bind defaults,bind 0 0" >> /etc/fstab
mount -v /chroots/$UserName/home/$UserName

Now, when the user logs in via SFTP the authentication phase is run against their normal /home/$UserName allowing them to have a ~/.ssh/authorized_keys file. As soon as they authenticate they are chrooted to /chroots/$UserName. The internal-sftp service is then launched placing them in their home directory /home/$UserName within the chroot. Their home directory will look the same to them with or without the chroot. The only difference is that if they cd out of their home directory they will see a filesystem that contains nothing else.

You can also optionally setup /etc/passwd and /etc/group files within the /chroots/$UserName directory if you want to make ls -l look right in the sftp client. It doesn't need real data it just needs to map the appropriate UIDs and GIDs to the names you want displayed in the ls -l output.


Here is an example setup (the commands marked optional are purely cosmetic):

desktop# tail /etc/ssh/sshd_config 
Match Group sftp-only
  ChrootDirectory /chroots/%u
  X11Forwarding no
  AllowTcpForwarding no
  ForceCommand internal-sftp

desktop# groupadd sftp-only
desktop# useradd --comment "chroot demo account" --home-dir /home/sftpuser --group sftp-only --create-home --shell /sbin/nologin sftpuser
desktop# getent passwd sftpuser
sftpuser:x:12320:12321:chroot demo account:/home/sftpuser:/sbin/nologin
desktop# mkdir -p /chroots/sftpuser/home/sftpuser
# setup an authorized_keys file now if you want.
desktop# chown -R root:sftp-only /chroots
desktop# chmod 710 /chroots
desktop# chmod -R 750 /chroots/sftpuser
desktop-optional# mkdir /chroots/sftpuser/etc
desktop-optional# chgrp sftp-only /chroots/sftpuser/etc
desktop-optional# chmod 710 /chroots/sftpuser/etc
desktop-optional# getent passwd sftpuser > /chroots/sftpuser/etc/passwd
desktop-optional# echo "root:x:0:0:not really root:::" >> /chroots/sftpuser/etc/passwd
desktop-optional# chmod 644 /chroots/sftpuser/etc/passwd
desktop-optional# getent group sftp-only > /chroots/sftpuser/etc/group
desktop-optional# getent group sftpuser >> /chroots/sftpuser/etc/group
desktop-optional# chmod 644 /chroots/sftpuser/etc/group
desktop# echo "/home/sftpuser /chroots/sftpuser/home/sftpuser bind defaults,bind 0 0" >> /etc/fstab
desktop# mount -v /chroots/sftpuser/home/sftpuser
/home/sftpuser on /chroots/sftpuser/home/sftpuser type none (rw,bind)
desktop# passwd sftpuser
New password: 
Retype new password: 
passwd: password updated successfully
--------------------------------------------------------------------------------------------------------------------
How it looks to the user if you set it up this way...
--------------------------------------------------------------------------------------------------------------------
kmk@desktop[1%]> sftp sftpuser@localhost
Connected to localhost.
sftp> pwd
Remote working directory: /home/sftpuser
sftp> ls -la
drwxr-xr-x    3 sftpuser sftpuser     1024 Oct  3 00:47 .
drwxr-x---    3 root     sftp-only    1024 Oct  3 00:47 ..
-rw-r--r--    1 sftpuser sftpuser      127 Aug 19 20:10 .bash_logout
-rw-r--r--    1 sftpuser sftpuser      193 Aug 19 20:10 .bash_profile
-rw-r--r--    1 sftpuser sftpuser      551 Aug 19 20:10 .bashrc
drwx------    2 sftpuser sftpuser     1024 Sep 16  2007 .ssh
-rw-r--r--    1 sftpuser sftpuser     1466 Sep 20  2003 .tcsh.config
sftp> cd ..
sftp> ls -la
drwxr-x---    3 root     sftp-only     1024 Oct  3 00:47 .
drwxr-x---    4 root     sftp-only     1024 Oct  3 00:48 ..
drwxr-xr-x    3 sftpuser sftpuser      1024 Oct  3 00:47 sftpuser
sftp> cd ..
sftp> ls -la
drwxr-x---    4 root     sftp-only     1024 Oct  3 00:48 .
drwxr-x---    4 root     sftp-only     1024 Oct  3 00:48 ..
drwx--x---    2 root     sftp-only     1024 Oct  3 00:49 etc
drwxr-x---    3 root     sftp-only     1024 Oct  3 00:47 home
sftp> cd ..
sftp> ls -la
drwxr-x---    4 root     sftp-only     1024 Oct  3 00:48 .
drwxr-x---    4 root     sftp-only     1024 Oct  3 00:48 ..
drwx--x---    2 root     sftp-only     1024 Oct  3 00:49 etc
drwxr-x---    3 root     sftp-only     1024 Oct  3 00:47 home
sftp> cd etc
sftp> ls -la
remote readdir("/etc"): Permission denied
sftp>