A terminal prompt on a Linux laptop's screen.
Fatmawati Achmad Zaenuri/Shutterstock.com

The chroot command can send you to jail, keep your development or test environments isolated, or just improve your system’s security. We show you the easiest way to use it.

What’s a chroot?

If you try to measure the usefulness of a command, you must take into account the functionality it provides and its ease of use. If it is too complicated for people to use or too long-winded to make them want to try to use it, the functionality might as well be zero. If no one uses it, it doesn’t provide any functionality.

In discussions with Linux users—in person and on forums—it seems that the chroot command is one that is pegged as being difficult to use, or too persnickety and tedious to setup. It seems this terrific utility isn’t used as much as it might be.

With chroot you can set up and run programs or interactive shells such as Bash in an encapsulated filesystem that is prevented from interacting with your regular filesystem. Everything within the chroot environment is penned in and contained. Nothing in the chroot environment can see out past its own, special, root directory without escalating to root privileges. That has earned this type of environment the nickname of a chroot jail. The term “jail” shouldn’t be confused with FreeBSD’s jail command, which creates a chroot environment that is more secure than the usual chroot environment.

But actually, there’s a very straightforward way to use chroot, which we’re going to step through. We’re using regular Linux commands which will work on all distributions. Some Linux distributions have dedicated tools to set up chroot environments, such as debootstrap for Ubuntu, but we’re being distro-agnostic here.

When Should You Use a chroot?

A chroot environment provides functionality similar to that of a virtual machine, but it is a lighter solution. The captive system doesn’t need a hypervisor to be installed and configured, such as VirtualBox or Virtual Machine Manager. Nor does it need to have a kernel installed in the captive system. The captive system shares your existing kernel.

In some senses, chroot environments are closer to containers such as LXC than to virtual machines. They’re lightweight, quick to deploy, and creating and firing one up can be automated. Like containers, one convenient way to configure them is to install just enough of the operating system for you to accomplish what is required. The “what is required” question is answered by looking at how you’re going to use your chroot environment.

Some common uses are:

Software Development and Product Verification. Developers write software and the product verification team (PV) tests it.  Sometimes issues are found by PV that can’t be replicated on the developer’s computer. The developer has all sorts of tools and libraries installed on their development computer that the average user—and PV—won’t have. Often, new software that works for the developer but not for others turns out to be using a resource on the developer’s PC that hasn’t been included in the test release of the software. chroot allows the developers to have a plain vanilla captive environment on their computer that they can sheep-dip the software in before giving it to PV. The captive environment can be configured with the bare minimum dependencies that the software requires.

Reducing Development Risk. The developer can create a dedicated development environment so that nothing that happens in it can mess up his actual PC.

Running Deprecated Software. Sometimes you just have to have an old version of something running.  If the old software has requirements that would clash or be incompatible with your version of Linux you can chroot an environment for the problem software.

Recovery and Filesystem Upgrades: If a Linux installation becomes inoperable, you can use chroot to mount the damaged filesystem to a mount point on a Live CD. This allows you to work in the damaged system and attempt to fix it as though it were mounted normally at root /. This means the expected file paths within the damaged system will be correctly referenced from the root directory, and not from the mount point of the Live CD. A similar technique was used in the article describing how to migrate the Linux filesystem from ext2 or ext3 to ext4.

Ringfencing Applications. Running an FTP server or other internet-connected appliance inside a chroot environment limits the damage an external attacker can do. This can be a valuable step in hardening the security of your system.

RELATED: How to Migrate Ext2 or Ext3 File Systems to Ext4 on Linux

Creating a chroot Environment

We need a directory to act as the root directory of the chroot environment. So that we have a shorthand way of referring to that directory we’ll create a variable and store the name of the directory in it. Here we’re setting up a variable to store a path to the “testroot” directory. It doesn’t matter if this directory doesn’t exist yet, we’re going to create it soon. If the directory does exist, it should be empty.

chr=/home/dave/testroot

chr=/home/dave/testroot in a terminal window

If the directory doesn’t exist, we need to create it. We can do that with this command. The -p (parents) option ensures any missing parent directories are created at the same time:

mkdir -p $chr

mkdir -p $chr in a terminal window

We need to create directories to hold the portions of the operating system our chroot environment will require. We’re going to set up a minimalist Linux environment that uses Bash as the interactive shell. We’ll also include the touch, rm, and ls commands. That will allow us to use all Bash’s built-in commands and touch, rm, and ls. We’ll be able to create, list and remove files, and use Bash. And—in this simple example—that’s all.

List the directories you need to create within the {} brace expansion.

mkdir -p $chr/{bin,lib,lib64}

mkdir -p $chr/{bin,lib,lib64} in a terminal window

Now we’ll change directory into our new root directory.

cd $chr

cd $chr in a terminal window

Let’s copy the binaries that we need in our minimalist Linux environment from your regular “/bin” directory into our chroot “/bin” directory. The -v (verbose) option makes cp tell us what it is doing as it performs each copy action.

cp -v /bin/{bash,touch,ls,rm} $chr

cp -v /bin/{bash,touch,ls,rm} $chr ina terminal window

The files are copied in for us:

output from cp as files are copied in a terminal window

These binaries will have dependencies. We need to discover what they are and copy those files into our environment as well, otherwise bash, touch, rm, and ls will not be able to function. We need to do this in turn for each of our chosen commands. We’ll do Bash first. The ldd command will list the dependencies for us.

ldd /bin/bash

ldd /bin/bash in a terminal window

The dependencies are identified and listed in the terminal window:

Bash dependencies listed in a terminal window

We need to copy those files into our new environment. Picking the details out of that listing and copying them one at a time is going to be time-consuming and error-prone.

Thankfully, we can semi-automate it. We’ll list the dependencies again, and this time we’ll form a list. Then we’ll loop through the list copying the files.

Here we’re using ldd to list the dependencies and feed the results through a pipe into egrep. Using egrep is the same as using grep with the -E (extended regular expressions) option. The -o (only matching) option restricts the output to the matching parts of lines. We’re looking for matching library files that end in a number [0-9].

list="$(ldd /bin/bash | egrep -o '/lib.*\.[0-9]')"

list="$(ldd /bin/bash | egrep -o '/lib.*\.[0-9]')" in a terminal window

We can check the contents of the list using echo:

echo $list

echo $list in a terminal window

Now that we have the list, we can step through it with the following loop, copying the files one at a time. We’re using the variable i to step through the list. For each member of the list, we copy the file to our chroot root directory which is the value held in $chr.

The -v (verbose) option causes cp to announce each copy as it performs it.  The --parents option ensures any missing parent directories are created in the chroot environment.

for i in $list; do cp -v --parents "$i" "${chr}"; done

for i in $list; do cp -v --parents "$i" "${chr}"; done in a terminal window

And this is the output:

output from the cp loop in a terminal window

We’ll use that technique to capture the dependencies of each of the other commands. And we’ll use the loop technique to perform the actual copying. The good news is we only need to make a tiny edit to the command that gathers the dependencies.

We can retrieve the command from our command history by hitting the Up Arrow key a few times and then make the edit. The looping copy command doesn’t need to change at all.

Here we’ve used the Up Arrow key to find the command, and we’ve edited it to say touch instead of bash.

list="$(ldd /bin/touch | egrep -o '/lib.*\.[0-9]')"

list="$(ldd /bin/touch | egrep -o '/lib.*\.[0-9]')" in a terminal window

We can now repeat the exact same loop command as before:

for i in $list; do cp -v --parents "$i" "${chr}"; done

for i in $list; do cp -v --parents "$i" "${chr}"; done in a terminal window

And our files are copied for us:

foutput of the cp loop copying the touch dependencies in a terminal window

We can now edit the list command line for ls:

list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"

list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')" in a terminal window

Again, we’ll use the same loop command. It doesn’t care what files are in the list. It blindly works through the list copying the files for us.

for i in $list; do cp -v --parents "$i" "${chr}"; done

for i in $list; do cp -v --parents "$i" "${chr}"; done in a terminal window

And the dependencies for ls are copied over for us:

Output from the cp loop copying the ls dependencies in a terminal window

We edit the list command line for the last time, making it work for rm:

list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')"

list="$(ldd /bin/ls | egrep -o '/lib.*\.[0-9]')" in a terminal window

We use the looping copy command one last time:

for i in $list; do cp -v --parents "$i" "${chr}"; done

The last of our dependencies are copied into our chroot environment. We’re finally ready to use the chroot command. This command sets the root of the chroot environment, and specifies which application to run as the shell.

sudo chroot $chr /bin/bash

sudo chroot $chr /bin/bash  in a terminal window

Our chroot environment is now active. The terminal window prompt has changed, and the interactive shell is the being handled by the bash shell in our environment.

Active chroot environment in a terminal window

We can try out the commands that we have brought into the environment.

ls
ls /home/dave/Documents

Active chroot environment in a terminal window

The ls command works as we’d expect when we use it within the environment. When we try to access a directory outside of the environment, the command fails.

We can use touch to create a file, ls to list it, and rm to remove it.

touch sample_file.txt
ls
rm sample_file.txt
ls

touch sample_file.txt in a terminal window

Of course, we can also use the built-in commands that the Bash shell provides. If you type help at the command line, Bash will list them for you.

help

Output of the help command in a terminal window

Use exit to leave the chroot environment:

exit

using exit to leave teh chroot environment in a terminal window

If you want to remove the chroot environment, you can simply delete it:

rm -r testroot/

rm -r testroot/ in a terminal window

This will recursively delete the files and directories in the chroot environment.

Automate for Convenience

If you’re thinking that chroot environments might be useful to you, but they’re a bit fiddly to set up, remember that you can always take the strain and the risk out of repetitive tasks by using aliases, functions, and scripts.

RELATED: How to Create Aliases and Shell Functions on Linux

Dave McKay Dave McKay
Dave McKay first used computers when punched paper tape was in vogue, and he has been programming ever since. He is now a Data Protection Officer and has worked as a freelance programmer, manager of an international software development team, and an IT services project manager. Dave is a Linux evangelist and open source advocate.
Read Full Bio »