Creating a Chroot Jail for SSH Access

I wanted to setup a way to allow SSH access to my machine but limit their abilities heavily. To do that I figured a chroot jail was the best way. In this example I’m using ArchLinux and OpenSSH 5.1p1. It should be a very similar process on any *nix operating system.

Setup your test user

The way I’m setting this up, is that all my chrooted users will be added to the sshusers group. So we must setup the group, then add the user.

$ groupadd sshusers
$ adduser -g sshusers user

Setup the jail directories

The next step is to setup all the directories needed. This needs to emulate the / directory to a bare minimum. That is we need a dev, etc, lib, usr, and bin directory as well as usr/bin/. The base directory has to be owned by root.

$ mkdir -p /var/jail/{dev,etc,lib,usr,bin}
$ mkdir -p /var/jail/usr/bin
$ chown root.root /var/jail

You also need the /dev/null file:

$ mknod -m 666 /var/jail/dev/null c 1 3

You need to fill up the etc directory with a few minimum files:

$ cd /var/jail/etc
$ cp /etc/ .
$ cp /etc/ .
$ cp /etc/nsswitch.conf .
$ cp /etc/hosts .

Once this is done you need to figure out what commands you want accessible by your limited users. In this example I only want the users to be able to get into bash and use the ls command. So you must copy the binaries to the jail.

$ cd /var/jail/usr/bin
$ cp /usr/bin/ls .
$ cp /usr/bin/bash .

Now that you’ve got all the binaries in place, you need to add the proper shared libraries. To find out what libraries are need you can run ldd /path/to/bin. The output looks similar to this:

$ ldd /bin/ls =>    (0xb7f2b000) => /lib/ (0xb7f1d000) => /lib/ (0xb7f16000) => /lib/ (0xb7dcf000) => /lib/ (0xb7db7000)
         /lib/ (0xb7f2c000) => /lib/ (0xb7db2000)

Then you have to manually copy each file to the lib directory in your jail. That is a pain. Especially if there is a lot of shared libraries for a binary you want. I came across a useful script called l2chroot which automatically finds the libraries and copies them to your chroot jail.

cd /sbin
wget -O l2chroot
chmod +x l2chroot

Edit the l2chroot file and change BASE=”/webroot” to BASE=”/var/jail”. This tells l2chroot where your jail is located so it copies everything to the right place. Now go ahead and run the command on the binaries you want.

l2chroot ls
l2chroot bash

Configure SSHd to Chroot your users

All that is left is to set a few things in your sshd configuration file. You need to make sure you have at least OpenSSH 4.8p1, because before that they didn’t have this nice ChrootDirectory() function. Previously there was a few extra steps you had to take to get it working, but really you should have a newer version anyway. To configure ChrootDirectory add the following to /etc/ssh/sshd_config:

Match group sshusers
          ChrootDirectory /var/jail/
          X11Forwarding no
          AllowTcpForwarding no

Note that this also disables X11Forwarding and does not allow port forwarding. If you want to setup a box to allow secure tunneling for your friends, you may want to change this.

Optional Steps

When you login to your test user, you’ll notice a prompt as such:


That is not a very useful bash prompt. So if you want something a little better I recommend simply copying the contents of /etc/skel to /var/jail/home/user. This gives you a .bashrc file which sets the PS1 variable to a much nicer looking prompt. Here’s what mine looks like:

phrygian:~> echo $PS1


por geekslack


This article describes how to build a chroot environment for Gentoo distribution.
NOTE: Debian GNU/Linux will be our host system, but these steps should also work for most other Linux based distributions (e.g: Ubuntu).


First we are going to download a minimal Gentoo system, called STAGE3.

We choose our architecture, in my case x86, and i686 specifically.
$ wget*.tar.bz2

There is a list of mirrors here:

We could download directly from gentoo page too:
$ wget*.tar.bz2


We create a directory where we will place Gentoo files:
$ mkdir gentoo_chroot
and uncompress there the stage3 archive.
# tar xvjf stage3-i686-20100216.tar.bz2 -C gentoo_chroot/

Copy resolv.conf file to resolve names:
# cp -L /etc/resolv.conf gentoo_chroot/etc/resolv.conf
We will be able to resolve addresses.

Copy /etc/host file:
# cp /etc/hosts gentoo_chroot/etc/
or we could create a new one:
e.g: # echo " mybox localhost" > gentoo_chroot/etc/hosts

I like using same hostname as the host one because if we change it in chroot, it also changes in the host.

When we will enter in the chroot jail we will be able to exec:
# hostname -f # shows
# ping


We use bind option to duplicate some host directories in the chroot filesystem:

# mount --bind /dev gentoo_chroot/dev
# mount --bind /proc gentoo_chroot/proc
# mount --bind /sys gentoo_chroot/sys
# mount --bind /dev/pts gentoo_chroot/dev/pts # Needed for agetty login and screen command.
# mount --bind /tmp gentoo_chroot/tmp # If we want share X windows between host and guest.


# chroot gentoo_chroot /bin/bash
We are in the gentoo chroot envirionment!!
Unless told, every command from now on is executed within the chroot env.

To update and configure some environment variables:
# env-update
# source /etc/profile # To configure our current shell environment.


We can also customize our prompt to show we are in the chroot jail.
# export PS1="(chroot) $PS1"

If we want to customize every login prompt we have to change /etc/profile.
# echo "export PS1=\"(chroot) \$PS1\"" >> /etc/profile
For non login shells you have to edit .bashrc file.

CREATE /etc/mtab FILE

# cp /proc/mounts gentoo_chroot/etc/mtab

Some mtab file lines are useless so we edit them:
# nano -w /etc/mtab

We leave something like that:

udev /dev tmpfs rw,relatime,size=10240k,mode=755 0 0
none /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0
none /proc proc rw,nosuid,nodev,noexec,relatime 0 0
devpts /dev/pts devpts rw,relatime,mode=600,ptmxmode=000 0 0

Now we are able to exec mount and df commands.


# ls /usr/share/zoneinfo # Shows all available timezones.
# cp /usr/share/zoneinfo/Europe/Madrid /etc/localtime # I use Madrid timezone.


$ cd /etc
# nano -w locale.gen
I add: es_ES.UTF-8 UTF-8 # Spanish specific locales, choose the ones which suit you.
# locale-gen # we generate locales.
We change locale:
# export LANG=es_ES.UTF-8
# locale


# passwd root

edit /etc/securetty file
# nano -w /etc/securetty
Add there all tty where you want to login as root from. e.g: add pts/5 if you want to login from /dev/pts/5 tty.
Exec $ echo $(tty) in your host to show which tty you are in.


We are going to create an user called new_user:
# useradd -m new_user # -m option to create home directory.
# passwd new_user # change the user password.
# su new_user # become that user.


Entering the chroot environment using the already told way mixes host and guest env variables.

A better way is using agetty command, execute in the host system:
# chroot gentoo_chroot/ /sbin/agetty $(tty) 38400

We can login as root or as the new user we have just created.

NOTE: The standard user wont be able to gain root privileges using su command unless he pertains to wheel group, so:

# usermod -a -G wheel new_user
# su new_user
$ su root
If not we will get “su: permission denied error”.

Now user new_user can exec su command and become root.


Portage is the Gentoo package management system.

We are going to install portage subsystem because we want to install more gentoo available packages.

# cd /usr # Portage is placed in /usr directory.
# wget # (35 MBytes)
or we could choose download it from a mirror:

Uncompress it:
# tar xvjf portage-latest.tar.bz2


Emerge is the command line tool which allow us to search, install, update, remove, etc gentoo packages.

Install less pager:
$ emerge --search less
We see: sys-apps/less
# emerge less # we install less pager.
less tool is compiled and installed.

Install screen terminal manager:
$ emerge --color y --search screen | less
# emerge -v screen

Now we are going to install a text web client:
Searching into categories. e.g: www-client
$ emerge --search @www-client --color y | less
# emerge -v links
$ links

My favourite text editor:
$ emerge --color y --search emacs | less
$ emerge --pretend emacs # To see what is going to happen without installing anything.
# emerge -v emacs
# tail -f /var/log/emerge-fetch.log # to see package downloading status.


# emerge -v xclock # we will use xclock tool to test the X system.

We need to obtain permissions and store it in .Xauthority file.
In the host machine exec:
$ xauth list
we obtain something like:

mybox/unix:0  MIT-MAGIC-COOKIE-1  5e20455a59909d2f911d73b8d7d8cba5

As I am using unix socket X display :0, I copy that line.

In the chroot env.
# emerge xauth
# xauth add mybox/unix:0 MIT-MAGIC-COOKIE-1 5e20455a59909d2f911d73b8d7d8cba5
# DISPLAY=:0.0 xclock # we can exec X apps as root.
$ xauth add mybox/unix:0 MIT-MAGIC-COOKIE-1 5e20455a59909d2f911d73b8d7d8cba5
$ DISPLAY=:0.0 xclock # we can exec X apps as a standard user.


# emerge -v mplayer

After installation it runs correctly as root, but not as a standard user. We need to give him audio privileges.

As I use alsa sound drivers:
$ ls -l /dev/snd
I see in files owned by group 29 (legacy from host machine)
We edit /etc/group file and change audio group gid into 29.
# nano -w /etc/group

Add the standard user to audio group:
# usermod -a -G audio new_user
# su new_user
$ mplayer -vo xv -framedrop foo.avi


Simply type:
$ exit


After exit we unmount binded directories:
# umount gentoo_chroot/dev/pts
# umount gentoo_chroot/dev
# umount gentoo_chroot/proc
# umount gentoo_chroot/sys
# umount gentoo_chroot/tmp

NOTE: when unmounting if it complains about a bind mount busy. You can use mount -M to move it to another place instead.
It is an ugly workaround, I know.😀

por geekslack