I setup a web-server. I need to grant a user ssh access but I do not trust users. How can I limit user session to a specific directory such as /home/httpd/$USERNAME? How do I set up a ssh chroort jail on a Linux operating systems?

You can interactive shell with special root directory on a Linux or Unix-like systems. You can set the pathname (such as /home/httpd/foo) of a directory to chroot to after authentication. All components of the pathname must be root owned directories that are not writable by any other user or group. After the chroot, sshd changes the working directory to the user’s home directory. [donotprint]
Tutorial details
Difficulty level Advanced
Root privileges Yes
Requirements OpenSSH
Est. reading time 20m

Say hello to ChrootDirectory directive

From the sshd_config man page:

The ChrootDirectory must contain the necessary files and directo ries to support the user’s session. For an interactive session this requires at least a shell, typically sh(1), and basic /dev nodes such as null(4), zero(4), stdin(4), stdout(4), stderr(4), arandom(4) and tty(4) devices. For file transfer sessions using “sftp”, no additional configuration of the environment is necessary if the in-process sftp server is used, though sessions which use logging do require /dev/log inside the chroot directory.

You may grant a user ssh access, whom you do not completely trust. You can limit what that user can see or run only ls, date, and internal bash commands by setting up a SSH chroot jail.ο»Ώ Let us see how to create the chrooted jail for OpenSSH server on a Debain or Ubuntu Linux server. The following tutorial is tested on a Debian Linux server v8.1:
# lsb_release -a
Sample outputs:

Fig.01: Finding Linux distro version and name command

Fig.01: Finding Linux distro version and name command

1. Login as the root user

Type any one of the following command:
$ su -
$ sudo -s

2. Create the chroot jail

I’m going to set /home/jails/ directory to restrict an ssh user session to this directory:
# D=/home/jails
# mkdir -p $D

As per the sshd man page you need following files too:
# ls -l /dev/{null,zero,stdin,stdout,stderr,random,tty}
Sample outputs:

crw-rw-rw- 1 root root 1, 3 Jun 11 03:11 /dev/null
crw-rw-rw- 1 root root 1, 8 Jun 11 03:11 /dev/random
lrwxrwxrwx 1 root root   15 Jun 11 03:11 /dev/stderr -> /proc/self/fd/2
lrwxrwxrwx 1 root root   15 Jun 11 03:11 /dev/stdin -> /proc/self/fd/0
lrwxrwxrwx 1 root root   15 Jun 11 03:11 /dev/stdout -> /proc/self/fd/1
crw-rw-rw- 1 root tty  5, 0 Jun 11 04:43 /dev/tty
crw-rw-rw- 1 root root 1, 5 Jun 11 03:11 /dev/zero

To create required /dev nodes entries use the following mknod command:
# mkdir -p $D/dev/
# mknod -m 666 $D/dev/null c 1 3
# mknod -m 666 $D/dev/tty c 5 0
# mknod -m 666 $D/dev/zero c 1 5
# mknod -m 666 $D/dev/random c 1 8

3. Set permissions

Type the following command so that the chroot $D directory, and all its components, must be owned by root user and not writable by any non-root user or group:
# chown root:root $D
# chmod 0755 $D

Verify it:
# ls -ld $D
Sample outputs:

drwxr-xr-x 2 root root 4096 Jun 11 03:14 /home/jails

4. Install bash shell in $D

Type the following command to create bin directory in $D path:
# mkdir -p $D/bin
Copy /bin/bash to $D/bin/ directory:
# cp -v /bin/bash $D/bin
Sample outputs:

β€˜/bin/bash’ -> β€˜/home/jails/bin/bash’

Copy required shared libs to $D directory. The syntax is as follows to find out what bash needed:
# ldd /bin/bash
Sample outputs:

	linux-vdso.so.1 (0x00007ffdbb1bc000)
	libncurses.so.5 => /lib/x86_64-linux-gnu/libncurses.so.5 (0x00007f1349bc6000)
	libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f134999c000)
	libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f1349797000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f13493ee000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f1349e0d000)

Copy highlighted files one-by-one as follows using the cp command:
# mkdir -p $D/lib/
# mkdir -p $D/lib64/
# mkdir -p $D/lib/x86_64-linux-gnu/
# cp -v /lib/x86_64-linux-gnu/{libncurses.so.5,libtinfo.so.5,libdl.so.2,libc.so.6} $D/lib/

Sample outputs:

β€˜/lib/x86_64-linux-gnu/libncurses.so.5’ -> β€˜/home/jails/lib/libncurses.so.5’
β€˜/lib/x86_64-linux-gnu/libtinfo.so.5’ -> β€˜/home/jails/lib/libtinfo.so.5’
β€˜/lib/x86_64-linux-gnu/libdl.so.2’ -> β€˜/home/jails/lib/libdl.so.2’
β€˜/lib/x86_64-linux-gnu/libc.so.6’ -> β€˜/home/jails/lib/libc.so.6’

Next, copy /lib64/ld-linux-x86-64.so.2 to /lib64/ directory:
# cp -v /lib64/ld-linux-x86-64.so.2 $D/lib64/
Sample outputs:

β€˜/lib64/ld-linux-x86-64.so.2’ -> β€˜/home/jails/lib64/ld-linux-x86-64.so.2’

Finally, copy /lib/x86_64-linux-gnu/libnss_files*, enter:
# cp -va /lib/x86_64-linux-gnu/libnss_files* $D/lib/x86_64-linux-gnu/

5. Add user to the the system

You also need to copy /etc/passwd, and /etc/group files to $D/etc/ directory:
# mkdir -p $D/etc/
Add a user called tom and jerry:
# adduser tom
# adduser jerry

Sample outputs:

Fig.02: Add a user on a Debian Linux 8 server

Fig.02: Add a user on a Debian Linux 8 server

Finally, copy updated /etc/{passwd,group} files to $D/etc/ directory:
# cp -vf /etc/{passwd,group} $D/etc/
Sample outputs:

β€˜/etc/passwd’ -> β€˜/home/jails/etc/passwd’
β€˜/etc/group’ -> β€˜/home/jails/etc/group’

Warning: if you add or delete or made any changes to the user or password in /etc/passwd file, recopy /etc/{passwd,group} files again by running the following two commands:
cp -vf /etc/{passwd,group} $D/etc/

6. Configure sshd

Edit /etc/ssh/sshd_config file, enter:
# vi /etc/ssh/sshd_config
Append the following two directives:

##  Apply the chrooted jail to the user called tom and jerry ##
Match User tom,jerry
ChrootDirectory /home/jails
## Allow sftp to chrooted jail ##
ForceCommand internal-sftp

7. Restart sshd service

For Debian Linux version 8.x, enter:
# systemctl restart ssh.service
For Debian version 7.x and older, enter:
# /etc/init.d/ssh restart

8. Test it

The syntax is:

ssh user@sever
ssh user@sever-ip-here
ssh tom@localhost

Sample outputs:

tom@localhost's password: 

Last login: Thu Jun 11 04:32:32 2015 from localhost
Could not chdir to home directory /home/tom: No such file or directory
-bash-4.3$ ls
-bash: ls: command not found
-bash-4.3$ date
-bash: date: command not found
-bash-4.3$ pwd

9. Install additional commands

The tom user now able to log into the server but can not run other commands such as ls, date, and so on. The user is restricted to /bin/bash only. If you need ls or any other commands, you need to install them in /home/jails/ directory as I did for /bin/bash. The easiest way is as follows:
# cd /root/
wget http://www.cyberciti.biz/files/lighttpd/l2chroot.txt
# mv l2chroot.txt l2chroot
# chmod +x l2chroot
# vi l2chroot

Find BASE line and change it as follows:


Save and close the file. Install /bin/ls in $D/bin/ directory:
# cp -v /bin/ls $D/bin/
# cp -v /bin/date $D/bin/
# /root/l2chroot /bin/ls
# /root/l2chroot /bin/date

Create $D/home/tom and $D/home/jerry directories:
# mkdir -p $D/home/{tom,jerry}
# chown -R tom:tom $D/home/tom/
# chown -R jerry:jerry $D/home/jerry/
# chmod -R 0700 $D/home/tom/
# chmod -R 0700 $D/home/jerry/

10. Verify and test it again

The syntax is as follows for sftp command:
sftp user@server
sftp user@server-ip-here
sftp tom@server1.cyberciti.biz

Sample outputs:

tom@server1.cyberciti.biz's password: 
Connected to server1.cyberciti.biz.

sftp> pwd
Remote working directory: /home/tom
sftp> ls
sftp> cd /home
sftp> ls
jerry  tom    
sftp> pwd
Remote working directory: /home
sftp> ls -l
drwx------    2 jerry    jerry        4096 Jun 11 08:55 jerry
drwx------    2 tom      tom          4096 Jun 11 08:53 tom
sftp> cd jerry
sftp> pwd
Remote working directory: /home/jerry
sftp> ls
remote readdir("/home/jerry"): Permission denied
sftp> ls
remote readdir("/home/jerry"): Permission denied
sftp> put /etc/resolv.conf .
Uploading /etc/resolv.conf to /home/jerry/.
remote open("/home/jerry/."): Permission denied
sftp> cd /home/tom
sftp> put /etc/resolv.conf .
Uploading /etc/resolv.conf to /home/tom/./resolv.conf
/etc/resolv.conf                                                 100%   70     0.1KB/s   00:00    
sftp> ls -l
-rw-r--r--    1 tom      tom            70 Jun 11 09:01 resolv.conf
sftp>  quit

How do I map users web-server (DocumentRoot) to /home/jails/ directory?

Say, /home/httpd/tom_web is DocumentRoot for tom user, then:
# mkdir $D/home/tom/web
# mount --bind /home/httpd/tom_web $D/home/tom/web
## update fstab file so that it can mount after server reboot ##
# echo "/home/httpd/tom_web/ $D/home/tom/web none bind" >> /etc/fstab

🐧 Get the latest tutorials on Linux, Open Source & DevOps via RSS feed or Weekly email newsletter.

🐧 16 comments so far... add one

CategoryList of Unix and Linux commands
Disk space analyzersncdu pydf
File Managementcat
FirewallAlpine Awall CentOS 8 OpenSUSE RHEL 8 Ubuntu 16.04 Ubuntu 18.04 Ubuntu 20.04
Network UtilitiesNetHogs dig host ip nmap
OpenVPNCentOS 7 CentOS 8 Debian 10 Debian 8/9 Ubuntu 18.04 Ubuntu 20.04
Package Managerapk apt
Processes Managementbg chroot cron disown fg jobs killall kill pidof pstree pwdx time
Searchinggrep whereis which
User Informationgroups id lastcomm last lid/libuser-lid logname members users whoami who w
WireGuard VPNAlpine CentOS 8 Debian 10 Firewall Ubuntu 20.04
16 comments… add one
  • vishal Jun 13, 2015 @ 7:01


  • Ricky Jun 15, 2015 @ 14:37

    Why so difficult solution? Try rbash.

    • echo083 Jun 30, 2015 @ 1:12

      I agree too complex solution described in this article for chrooted jail.

    • J Jul 15, 2015 @ 13:41

      rbash does not accomplish the compartmentalization that the chroot jail accomplishes. It also doesn’t restrict someone from running an unrestricted shell from the restricted shell which potentially exposes information that you do not want a user (or daemon) to have the potential to access. chroot jail ensures that files outside of the jail are not accessible from the user/daemon session.

      Good article in my opinion. Time to get the old skool back into play here when sysadmins were not so darn lazy..j

  • Faiz Qureshi Jun 17, 2015 @ 18:48

    I am looking for a refresher course on Linux/ Unix , I already have a basic understanding from a windows environment where in I created directories , libraries and files. How to do the same on a Unix/Linux console.

  • Serge Jul 21, 2015 @ 19:50

    Try the Linux Foundation MOOC on edX, Faiz. Seems like a good start.

    Great tutorial, Vivek, thanks for this. One thing I’d like to add, please: everything is explained in great detail, except for the part 6, –> vi /etc/ssh/sshd_config <– where the author assumes pre-existing vi knowledge.

    I could have used some indications there as well since I'm a complete newb and I will have to google for vi commands.

    Again, thanks for this. Really helpful.

  • Edwin Enrique Flores Bautista Dec 26, 2015 @ 21:48

    I like this publication, thanks you, i’m Enrique from Lima Peru.

  • ISV Dec 28, 2015 @ 9:37

    ssh user@sever-ip-here
    This service allows sftp connections only.
    sftp user@server-ip-here
    usage: sftp [-1246aCfpqrv] [-B buffer_size] [-b batchfile] [-c cipher]
    [-D sftp_server_path] [-F ssh_config] [-i identity_file] [-l limit]
    [-o ssh_option] [-P port] [-R num_requests] [-S program]
    [-s subsystem | sftp_server] host
    sftp [user@]host[:file …]
    sftp [user@]host[:dir[/]]
    sftp -b batchfile [user@]host

    It doesn’t work.

    • Felix Dec 1, 2017 @ 11:00

      probably you’ll have to change this in /etc/sshd_config:
      #Subsystem sftp /usr/lib/openssh/sftp-server
      Subsystem sftp internal-sftp

      and remove
      ForceCommand internal-sftp

      to allow both, ssh AND sftp

  • Mathieu Jan 3, 2016 @ 1:14

    What doesn’t work?
    Are you really using “user@server-ip-here”?
    If so, maybe you need to stick to watching TV.
    Nice article, thx

  • Andreee Jan 11, 2016 @ 13:04


    I had same problem. I think this is only restrition to use sftp, so afer removing this argument from config it works like should.
    This service allows sftp connections only.

    ## Allow sftp to chrooted jail ##
    ForceCommand internal-sftp

  • Mubbashar Jun 3, 2016 @ 11:27

    thanks for the great tutorial.
    I wanted so user can not go back to their own directory
    like for foo we have a directory /home/jails/home/foo
    so what i wanted is user should not able to go back to /home/jails/home directory

  • rocaflqu Aug 25, 2016 @ 18:58

    I have problem when use clear command in ssh connection, example
    i create test user and jail in /home/test, and use the next command:

    – cp -v /usr/bin/clear /home/test/bin
    – l2chroot /home/test/bin/clear

    ssh test@server.com
    test# clear
    ‘xterm-256color’: unknown terminal type.

    But use the same way for ls, grep, find, and work fine.

    Note: in server command clear work fine.

    Thanks for the tutorial

    • rocaflqu Aug 25, 2016 @ 18:59

      I try with in ssh console.
      # TERM=”xterm”
      # export TERM=xterm

      But not work.

  • Stan Feb 19, 2021 @ 5:35

    It works, thanks, just I do not know how to enable the users to change their passwords.

    • 🐧 Vivek Gite Feb 19, 2021 @ 6:51

      That is more complicated as you need to copy /etc/passwd, /etc/shadow and other files including passwd command in that jail.

Leave a Reply

Your email address will not be published.

Use HTML <pre>...</pre> for code samples. Still have questions? Post it on our forum