chroot()
Learn how PHP's chroot() function changes a process's root directory to create an isolated filesystem environment, with syntax, examples, and gotchas.
PHP chroot() Function
The chroot() function changes the root directory of the currently running process to the directory you give it, and then sets the current working directory to /. After the call, the process can no longer see or reach any file above that new root — it is "jailed" inside the directory tree. This technique is commonly called a chroot jail.
This page explains what chroot() does, when you would (and would not) use it, its syntax, and the limitations you need to know before relying on it.
Syntax
chroot(string $directory): bool| Parameter | Description |
|---|---|
$directory | The path to the directory that becomes the new root (/) of the process. |
Return value: true on success, false on failure.
Requirements
chroot() is not available everywhere. Before using it, keep these constraints in mind:
- It works only on CLI and CGI SAPIs — it is not available under most module SAPIs such as
mod_phpor PHP-FPM running a typical web request. - It is not implemented on Windows.
- The calling process must have root (superuser) privileges. A normal user cannot change the root directory.
Because of these requirements, chroot() is mostly used in long-running PHP CLI daemons and worker scripts, not in code that handles ordinary HTTP requests.
Basic Example
This script jails the process inside /var/www/jail, then reads a path relative to the new root:
<?php
// Must be run as root, on CLI.
if (chroot('/var/www/jail')) {
echo "Root directory changed.\n";
// Paths are now relative to /var/www/jail.
// What was /var/www/jail/data/config.txt is now /data/config.txt
$contents = file_get_contents('/data/config.txt');
echo $contents;
} else {
echo "Failed to change root directory.\n";
}After the chroot() call, the path /data/config.txt actually refers to /var/www/jail/data/config.txt on the real filesystem. The process simply cannot express a path that escapes the jail.
Confirming the Working Directory
Because chroot() also moves the working directory to /, you can confirm the change with getcwd():
<?php
chroot('/var/www/jail');
echo getcwd(); // "/" (which is /var/www/jail on the real filesystem)If you need a different working directory inside the jail, set it explicitly with chdir() after the chroot() call.
Why Use chroot()
The goal of chroot() is isolation. Once a process is jailed:
- It cannot open, read, or write files outside the new root, even with absolute paths.
- A bug or exploit that tries directory traversal (
../../etc/passwd) has nothing to traverse to — there is no path above/. - You can ship a minimal directory tree (only the files the worker actually needs), shrinking the attack surface.
A common pattern is to start a daemon as root, call chroot() to lock it into a sandbox, and then drop privileges with posix_setuid() / posix_setgid() so the jailed process no longer runs as root.
chroot() vs open_basedir
These are often confused. They solve a similar problem at very different levels:
chroot() | open_basedir | |
|---|---|---|
| Level | Operating-system process root | PHP engine path check |
| Where set | In code at runtime | php.ini, .htaccess, FPM pool |
| Works under web SAPIs | No (CLI/CGI only) | Yes |
| Needs root privileges | Yes | No |
| Strength | OS-enforced jail | Advisory, can be weakened by symlinks |
If you only need to keep a normal web request inside one directory, open_basedir is the practical tool. Use chroot() when you control a CLI process and want a real OS-level boundary.
Limitations and Gotchas
- Not a perfect security boundary. A process still running as root inside a chroot can often break out. Always drop privileges after jailing.
- Missing dependencies. The jail has no
/lib,/etc,/usrunless you put them there. Functions that rely on system files (DNS lookups, locale data, time zones, dynamic libraries) may fail inside the jail. - One-way for the process. There is no
unchroot(); the change lasts for the life of the process. - Environment-dependent. Because availability depends on the SAPI and OS, guard calls and check the return value rather than assuming success.
Conclusion
chroot() confines a PHP process to a single directory tree by changing its root to that directory and resetting the working directory to /. It is a powerful OS-level isolation tool for privileged CLI daemons, but it requires root, is limited to CLI/CGI, and is not available on Windows. For per-request restrictions in a normal web stack, reach for open_basedir instead, and treat chroot() as one layer of a defense-in-depth strategy. To learn more about working with paths and the filesystem, see chdir(), getcwd(), and the PHP Filesystem chapter.
Diagram
Here's how chroot() reshapes what a process can reach:
graph TD;
A[PHP Process] --> B{chroot('/var/www/jail')};
B --> C[New root = /var/www/jail];
C --> D[Working dir set to /];
D -->|Path /data/config.txt| E[Allowed: inside jail];
D -->|Path ../../etc/passwd| F[Blocked: nothing above /];Note: The boundary is enforced by the operating system, so it applies to every file operation the process makes, not just PHP function calls.