FreeBSD.software
Home/Blog/How to Set Up FreeBSD Jails: Complete Guide 2026
tutorial2026-03-29

How to Set Up FreeBSD Jails: Complete Guide 2026

Complete guide to FreeBSD jails in 2026. Covers manual jail creation, jail.conf, VNET networking, ZFS integration, Bastille manager, resource limits, and practical service isolation.

# How to Set Up FreeBSD Jails: Complete Guide 2026

FreeBSD jails are the original operating-system-level virtualization. They shipped in FreeBSD 4.0 in March 2000 -- a full thirteen years before Docker existed. While the container world on Linux reinvents isolation primitives every few years, jails have been stable, production-tested, and incrementally improved for over two decades.

A jail gives you an isolated userland with its own root filesystem, process tree, network stack, and user accounts, running on the host kernel with near-zero overhead. No hypervisor. No emulation layer. No container runtime daemon that can crash and take all your workloads down with it.

This guide covers everything from creating your first jail by hand to managing fleets with Bastille. Every command is real. Every output block reflects what you will see on a FreeBSD 14.x system.

Table of Contents

1. [What Are Jails and Why They Matter](#what-are-jails-and-why-they-matter)

2. [Manual Jail Creation Step by Step](#manual-jail-creation-step-by-step)

3. [jail.conf Walkthrough](#jailconf-walkthrough)

4. [Starting, Stopping, and Managing Jails](#starting-stopping-and-managing-jails)

5. [Networking: Inherited IP vs VNET](#networking-inherited-ip-vs-vnet)

6. [ZFS and Jails](#zfs-and-jails)

7. [Resource Limits with rctl](#resource-limits-with-rctl)

8. [Bastille Jail Manager](#bastille-jail-manager)

9. [Practical Example: Running NGINX in a Jail](#practical-example-running-nginx-in-a-jail)

10. [Updating and Maintaining Jails](#updating-and-maintaining-jails)

11. [Security Considerations](#security-considerations)

12. [FAQ](#faq)

---

What Are Jails and Why They Matter

A FreeBSD jail is an isolated environment that shares the host kernel but has its own:

- **Root filesystem.** Each jail has a complete directory tree. Processes inside the jail cannot see or access files outside it.

- **Process space.** A ps aux inside a jail only shows that jail's processes. The host and other jails are invisible.

- **Network identity.** A jail can have its own IP address or, with VNET, its own full network stack including routing tables and firewall rules.

- **Users and groups.** Root inside a jail is not root on the host. Privilege escalation within a jail does not grant host access.

Jails vs Docker

Docker containers on Linux depend on a layered stack of kernel features -- namespaces, cgroups, seccomp, AppArmor or SELinux -- bolted together by a userland daemon. The isolation boundary is complex and the attack surface is wide. Docker requires a daemon running as root. If that daemon crashes, every container goes down.

Jails are a single, audited kernel subsystem. There is no daemon. The jail boundary is enforced directly by the FreeBSD kernel, has been reviewed by security researchers for over two decades, and has a track record of very few privilege-escalation vulnerabilities.

For a detailed comparison, see our [FreeBSD jails vs Docker](/blog/freebsd-jails-vs-docker/) analysis.

A Brief History

- **2000** -- FreeBSD 4.0 introduces jails (Poul-Henning Kamp's design).

- **2008** -- Hierarchical jails land in FreeBSD 8.0, allowing jails inside jails.

- **2012** -- VNET (virtual network stack) becomes stable, giving each jail its own full networking.

- **2014** -- jail.conf replaces the old rc.conf-based configuration.

- **2020+** -- Integration with ZFS, RCTL resource limits, and modern jail managers like Bastille matures.

---

Manual Jail Creation Step by Step

We will build a jail from scratch on a FreeBSD 14.2-RELEASE host. This teaches you what every jail manager does under the hood.

Step 1: Create the Jail Directory

Pick a location for your jails. /usr/local/jails is a common convention:

sh

mkdir -p /usr/local/jails/containers/webserver

Step 2: Fetch the FreeBSD Base System

Download and extract the base system into the jail directory:

sh

fetch https://download.FreeBSD.org/releases/amd64/14.2-RELEASE/base.txz

tar -xf base.txz -C /usr/local/jails/containers/webserver

This gives you a minimal FreeBSD userland -- around 350 MB -- with all the standard tools, libraries, and configuration files.

Step 3: Configure DNS Resolution

Copy the host's DNS configuration into the jail:

sh

cp /etc/resolv.conf /usr/local/jails/containers/webserver/etc/resolv.conf

Step 4: Set the Jail's Root Password

sh

chroot /usr/local/jails/containers/webserver passwd root

Step 5: Create the jail.conf Entry

Edit /etc/jail.conf:

conf

webserver {

host.hostname = "webserver.jail";

ip4.addr = "192.168.1.50";

interface = "em0";

path = "/usr/local/jails/containers/webserver";

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown";

mount.devfs;

allow.raw_sockets;

}

Step 6: Enable and Start

Add to /etc/rc.conf:

sh

jail_enable="YES"

jail_list="webserver"

Start the jail:

sh

service jail start webserver

You now have a running, isolated FreeBSD environment.

---

jail.conf Walkthrough

The /etc/jail.conf file is where all jail configuration lives. Understanding its parameters is essential. Here is a comprehensive example with every commonly used option:

conf

# Global defaults applied to all jails

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown";

exec.clean;

mount.devfs;

allow.raw_sockets;

exec.consolelog = "/var/log/jail_${name}_console.log";

webserver {

# Identity

host.hostname = "webserver.jail";

path = "/usr/local/jails/containers/webserver";

osrelease = "FreeBSD 14.2-RELEASE";

# Networking (inherited IP mode)

ip4.addr = "em0|192.168.1.50/24";

ip6 = "disable";

# Permissions

allow.set_hostname = 0;

allow.sysvipc = 0;

allow.raw_sockets = 1;

allow.chflags = 0;

allow.mount = 0;

allow.mount.devfs = 0;

allow.mount.nullfs = 0;

allow.mount.procfs = 0;

allow.mount.zfs = 0;

allow.quotas = 0;

allow.socket_af = 0;

# Execution hooks

exec.prestart = "";

exec.poststart = "";

exec.prestop = "";

exec.poststop = "";

# Mount points

mount.devfs;

mount.fstab = "/etc/fstab.webserver";

# Boot behavior

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown jail";

# Security

securelevel = 2;

devfs_ruleset = 4;

enforce_statfs = 2;

children.max = 0;

persist;

}

Key Parameters Explained

**path** -- The root filesystem of the jail. Every file access inside the jail is relative to this directory.

**host.hostname** -- The hostname visible inside the jail. Does not need to resolve in DNS.

**ip4.addr** -- The IPv4 address assigned to the jail. The format interface|address/mask binds the IP as an alias on the given interface. With VNET jails, this parameter is not used (you configure networking inside the jail instead).

**exec.start / exec.stop** -- Commands run inside the jail when it starts and stops. The defaults invoke /etc/rc to start services defined in the jail's own /etc/rc.conf.

**mount.devfs** -- Mounts a devfs inside the jail so that /dev is populated. Without this, most software will not function.

**devfs_ruleset** -- Controls which devices are visible inside the jail. Ruleset 4 is the default restricted set. Never use ruleset 0 (unrestricted) in production.

**securelevel** -- Sets the kernel securelevel inside the jail. Level 2 prevents modification of system flags and direct disk writes.

**enforce_statfs** -- Controls what mount point information is visible. Value 2 (the default) hides all mount points outside the jail.

**children.max** -- Maximum number of child jails. Set to 0 to prevent nested jails.

**allow.raw_sockets** -- Permits raw socket access, needed for ping and traceroute inside the jail.

**persist** -- Keeps the jail running even if all processes inside it exit.

---

Starting, Stopping, and Managing Jails

Basic Operations

sh

# Start all jails in jail_list

service jail start

# Start a specific jail

service jail start webserver

# Stop a specific jail

service jail stop webserver

# Restart a jail

service jail restart webserver

Listing Running Jails

sh

jls

Output:


JID IP Address Hostname Path

1 192.168.1.50 webserver.jail /usr/local/jails/containers/webserver

2 192.168.1.51 database.jail /usr/local/jails/containers/database

For detailed information:

sh

jls -v

Or query specific fields:

sh

jls jid name host.hostname ip4.addr path

Executing Commands Inside a Jail

sh

# Open a shell inside jail ID 1

jexec 1 /bin/sh

# Or use the jail name

jexec webserver /bin/sh

# Run a single command

jexec webserver pkg info

Viewing Jail Processes from the Host

sh

ps -aux -J 1

This shows all processes belonging to jail ID 1, with the jail ID column visible.

Console Logging

With exec.consolelog set in jail.conf, all console output during jail start and stop is logged:

sh

tail -f /var/log/jail_webserver_console.log

---

Networking: Inherited IP vs VNET

FreeBSD jails support two networking models. Choosing correctly is critical.

Inherited IP (Simple Mode)

In inherited IP mode, the jail borrows an IP address from the host's network stack. The jail shares the host's routing table and firewall. This is the simplest setup and works for most single-service jails.

conf

webserver {

ip4.addr = "em0|192.168.1.50/24";

# ...

}

The host adds 192.168.1.50 as an alias on em0. The jail can only bind to that address. It cannot modify routes, cannot run its own firewall, and cannot see other interfaces.

**Pros:** Simple. No extra configuration. Low overhead.

**Cons:** No per-jail firewall. No per-jail routing. All jails share the host's network stack.

VNET (Full Network Stack)

VNET gives each jail its own complete network stack -- interfaces, routing table, ARP table, and firewall. This is necessary when jails need to run DHCP clients, VPN software, or their own PF rules.

VNET requires a bridge and an epair (virtual ethernet pair).

#### Step 1: Create the Bridge on the Host

sh

# /etc/rc.conf on the host

cloned_interfaces="bridge0"

ifconfig_bridge0="inet 192.168.100.1/24 up"

#### Step 2: Configure jail.conf for VNET

conf

webserver {

host.hostname = "webserver.jail";

path = "/usr/local/jails/containers/webserver";

vnet;

vnet.interface = "epair0b";

exec.prestart = "ifconfig epair0 create up";

exec.prestart += "ifconfig bridge0 addm epair0a";

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown jail";

exec.poststop = "ifconfig bridge0 deletem epair0a";

exec.poststop += "ifconfig epair0a destroy";

mount.devfs;

devfs_ruleset = 4;

}

#### Step 3: Configure Networking Inside the Jail

In the jail's /etc/rc.conf:

sh

ifconfig_epair0b="inet 192.168.100.10/24"

defaultrouter="192.168.100.1"

#### How It Works

The epair device creates a virtual ethernet cable with two ends: epair0a and epair0b. The a end stays on the host and is added to the bridge. The b end is passed into the jail via vnet.interface. The jail sees epair0b as its network interface and configures it normally.

This gives the jail full control over its own networking. It can run PF, set up routes, use ifconfig, and even run a DHCP client.

#### NAT for VNET Jails

If your VNET jails need internet access through the host, enable NAT with PF on the host:

conf

# /etc/pf.conf on the host

ext_if = "em0"

jail_net = "192.168.100.0/24"

nat on $ext_if from $jail_net to any -> ($ext_if)

pass from $jail_net to any

Enable IP forwarding:

sh

sysctl net.inet.ip.forwarding=1

Make it persistent in /etc/sysctl.conf:


net.inet.ip.forwarding=1

---

ZFS and Jails

ZFS and jails are a natural combination. ZFS datasets give each jail its own filesystem with independent snapshots, quotas, and compression settings. For a deep dive into ZFS itself, see our [ZFS guide](/blog/zfs-freebsd-guide/).

One Dataset Per Jail

sh

zfs create zpool/jails

zfs create zpool/jails/webserver

Set the jail's path to the dataset mount point:

conf

webserver {

path = "/zpool/jails/webserver";

# ...

}

Now you can snapshot, clone, and send/receive entire jails:

sh

# Snapshot a jail

zfs snapshot zpool/jails/webserver@2026-03-29

# Roll back to a snapshot

zfs rollback zpool/jails/webserver@2026-03-29

# Send a jail to another machine

zfs send zpool/jails/webserver@2026-03-29 | ssh backup-host zfs receive backup/jails/webserver

Thin Jails with ZFS Clones

Instead of extracting a full base.txz for every jail (350 MB each), create one base dataset and clone it:

sh

# Create and populate the base

zfs create zpool/jails/base

tar -xf base.txz -C /zpool/jails/base

# Snapshot the base

zfs snapshot zpool/jails/base@14.2-RELEASE

# Clone for each jail -- near-instant, near-zero disk usage

zfs clone zpool/jails/base@14.2-RELEASE zpool/jails/webserver

zfs clone zpool/jails/base@14.2-RELEASE zpool/jails/database

zfs clone zpool/jails/base@14.2-RELEASE zpool/jails/mailserver

Each clone starts as a zero-byte copy-on-write reference to the base snapshot. Disk usage only grows as files inside the jail diverge from the base. With 20 jails, you might use 350 MB for the base plus a few MB per jail instead of 7 GB.

Quotas

Limit how much disk space a jail can consume:

sh

zfs set quota=10G zpool/jails/webserver

zfs set reservation=2G zpool/jails/webserver

Compression

Enable compression per jail dataset:

sh

zfs set compression=zstd zpool/jails/webserver

---

Resource Limits with rctl

FreeBSD's rctl framework lets you cap CPU, memory, and other resources per jail. This prevents a runaway process in one jail from starving the host or other jails.

Enable rctl

Add to /boot/loader.conf:


kern.racct.enable=1

Reboot. Then verify:

sh

sysctl kern.racct.enable


kern.racct.enable: 1

Set Resource Limits

sh

# Limit jail to 2 GB of RAM

rctl -a jail:webserver:memoryuse:deny=2G

# Limit to 50% of one CPU core

rctl -a jail:webserver:pcpu:deny=50

# Limit to 200 processes

rctl -a jail:webserver:maxproc:deny=200

# Limit open files

rctl -a jail:webserver:openfiles:deny=4096

View Current Limits

sh

rctl -l jail:webserver


jail:webserver:memoryuse:deny=2147483648

jail:webserver:pcpu:deny=50

jail:webserver:maxproc:deny=200

jail:webserver:openfiles:deny=4096

View Current Usage

sh

rctl -u jail:webserver


cputime=312

datasize=847249408

stacksize=8388608

coredumpsize=0

memoryuse=524288000

memorylocked=0

maxproc=47

openfiles=213

vmemoryuse=1249902592

pseudoterminals=3

swapuse=0

nthr=98

msgqqueued=0

msgqsize=0

nmsgq=0

nsem=0

nsemop=0

nshm=0

shmsize=0

wallclock=28847

pcpu=8

readbps=0

writebps=0

readiops=0

writeiops=0

Make Limits Persistent

Add rules to /etc/rctl.conf so they survive reboots:


jail:webserver:memoryuse:deny=2G

jail:webserver:pcpu:deny=50

jail:webserver:maxproc:deny=200

jail:webserver:openfiles:deny=4096

---

Bastille Jail Manager

While manual jail creation teaches fundamentals, managing dozens of jails by hand is tedious. Bastille is the leading modern jail manager for FreeBSD. It automates creation, networking, templates, and lifecycle management.

Install Bastille

sh

pkg install bastille

Enable it:

sh

sysrc bastille_enable="YES"

Bootstrap a Release

Before creating jails, download a FreeBSD release:

sh

bastille bootstrap 14.2-RELEASE update

This fetches and extracts the base system and applies the latest security patches. Bastille stores releases in /usr/local/bastille/releases/.

Create a Jail

sh

bastille create webserver 14.2-RELEASE 192.168.1.50

That single command does everything we did manually: creates the directory structure, extracts the base, configures the IP address, and generates the jail.conf entry.

For a VNET jail:

sh

bastille create -V webserver 14.2-RELEASE em0

Basic Bastille Operations

sh

# List all jails

bastille list

# Start a jail

bastille start webserver

# Stop a jail

bastille stop webserver

# Open a console

bastille console webserver

# Run a command in a jail

bastille cmd webserver pkg update

# Install a package

bastille pkg webserver install nginx

# View jail logs

bastille logs webserver

# Destroy a jail

bastille destroy webserver

Bastille Templates

Templates are Bastille's automation layer. They define packages, services, and configuration files to apply to a jail. A template is a directory with a Bastillefile:


# Bastillefile for a web server jail

PKG nginx

PKG php84

PKG php84-extensions

SYSRC nginx_enable=YES

SYSRC php_fpm_enable=YES

SERVICE nginx start

SERVICE php-fpm start

CP usr/local/etc/nginx/nginx.conf

TEMPLATE usr/local/etc/nginx/vhosts/default.conf

Apply a template:

sh

bastille template webserver myuser/webserver-template

Bastille templates can be hosted as Git repositories and applied directly from URLs, making jail provisioning reproducible and version-controlled.

Bastille with ZFS

Bastille detects ZFS automatically. If your jail storage sits on a ZFS dataset, Bastille creates a child dataset per jail and uses ZFS clones for fast provisioning:

sh

sysrc -f /usr/local/etc/bastille/bastille.conf bastille_zfs_enable="YES"

sysrc -f /usr/local/etc/bastille/bastille.conf bastille_zfs_zpool="zpool"

---

Practical Example: Running NGINX in a Jail

Let us put everything together and deploy an NGINX web server in a jail with VNET networking and ZFS storage.

Create the Jail Infrastructure

sh

# Create ZFS datasets

zfs create zpool/jails

zfs create zpool/jails/nginx-web

# Fetch and extract the base system

fetch https://download.FreeBSD.org/releases/amd64/14.2-RELEASE/base.txz

tar -xf base.txz -C /zpool/jails/nginx-web

# Copy DNS config

cp /etc/resolv.conf /zpool/jails/nginx-web/etc/resolv.conf

Configure the Jail

/etc/jail.conf:

conf

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown jail";

exec.clean;

mount.devfs;

devfs_ruleset = 4;

nginx-web {

host.hostname = "nginx-web.jail";

path = "/zpool/jails/nginx-web";

vnet;

vnet.interface = "epair1b";

exec.prestart = "ifconfig epair1 create up";

exec.prestart += "ifconfig bridge0 addm epair1a";

exec.poststop = "ifconfig bridge0 deletem epair1a";

exec.poststop += "ifconfig epair1a destroy";

}

Configure Networking Inside the Jail

Write to /zpool/jails/nginx-web/etc/rc.conf:

sh

ifconfig_epair1b="inet 192.168.100.20/24"

defaultrouter="192.168.100.1"

Start the Jail and Install NGINX

sh

# Enable and start

sysrc jail_enable="YES"

sysrc jail_list+="nginx-web"

service jail start nginx-web

# Enter the jail

jexec nginx-web /bin/sh

# Inside the jail:

pkg install -y nginx

sysrc nginx_enable="YES"

service nginx start

Verify

From the host:

sh

curl http://192.168.100.20

You should see the default NGINX welcome page.

From outside the network, set up port forwarding with PF on the host:

conf

# /etc/pf.conf

ext_if = "em0"

rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> 192.168.100.20 port 80

rdr on $ext_if proto tcp from any to ($ext_if) port 443 -> 192.168.100.20 port 443

For a production NGINX configuration inside the jail, see our [NGINX setup guide](/blog/nginx-freebsd-production-setup/).

Set Resource Limits

sh

rctl -a jail:nginx-web:memoryuse:deny=1G

rctl -a jail:nginx-web:maxproc:deny=100

rctl -a jail:nginx-web:openfiles:deny=8192

Snapshot the Working State

sh

zfs snapshot zpool/jails/nginx-web@working-nginx

You now have a fully isolated, resource-limited, snapshotable web server.

---

Updating and Maintaining Jails

Update the Base System in a Jail

Use freebsd-update inside the jail:

sh

jexec webserver freebsd-update fetch install

Or from the host, targeting the jail's filesystem:

sh

freebsd-update -b /usr/local/jails/containers/webserver fetch install

Update Packages in a Jail

sh

jexec webserver pkg update

jexec webserver pkg upgrade -y

Bulk Updates with Bastille

sh

# Update all jails at once

bastille cmd ALL pkg update

bastille cmd ALL pkg upgrade -y

# Apply freebsd-update to all jails

bastille cmd ALL freebsd-update fetch install

Upgrading Jails to a New FreeBSD Release

When the host is upgraded (e.g., from 14.1 to 14.2), the jails need to follow:

sh

freebsd-update -b /usr/local/jails/containers/webserver -r 14.2-RELEASE upgrade

freebsd-update -b /usr/local/jails/containers/webserver install

Always snapshot before upgrading:

sh

zfs snapshot zpool/jails/webserver@pre-upgrade-14.2

If anything breaks, roll back in seconds:

sh

service jail stop webserver

zfs rollback zpool/jails/webserver@pre-upgrade-14.2

service jail start webserver

---

Security Considerations

Jails are a strong isolation boundary, but they are not magic. Follow these practices to maximize security.

Minimize the Jail's Contents

Do not copy the full base system if you only need a few binaries. Strip down the jail to the minimum required files. Fewer files means fewer potential vulnerabilities.

Use devfs Rulesets

Never use devfs_ruleset = 0 (unrestricted). The default ruleset 4 hides most devices. Create custom rulesets for jails that need specific devices:

sh

# /etc/devfs.rules

[devfsrules_jail_custom=10]

add include $devfsrules_hide_all

add include $devfsrules_unhide_basic

add include $devfsrules_unhide_login

add path 'bpf*' unhide

Then reference it in jail.conf:

conf

webserver {

devfs_ruleset = 10;

}

Restrict Permissions Aggressively

Start with the most restrictive settings and open only what is needed:

conf

allow.set_hostname = 0;

allow.sysvipc = 0;

allow.raw_sockets = 0;

allow.chflags = 0;

allow.mount = 0;

allow.quotas = 0;

allow.socket_af = 0;

children.max = 0;

enforce_statfs = 2;

Only enable allow.raw_sockets if the jail actually needs ping. Only enable allow.mount if the jail needs to mount filesystems.

Set a Securelevel

conf

securelevel = 2;

Securelevel 2 prevents the jail from modifying system flags on files and from writing directly to mounted disk devices.

Use rctl to Prevent Resource Abuse

Without resource limits, a compromised jail can fork-bomb the host or exhaust all available memory. Always set maxproc, memoryuse, and openfiles limits.

Run Firewalls on the Host

Even with VNET jails that can run their own PF rules, maintain a host-level firewall as the outer defense layer. Defense in depth. See our [FreeBSD hardening guide](/blog/hardening-freebsd-server/) for a complete PF configuration.

Keep Jails Updated

An unpatched jail is an unpatched attack surface. Automate freebsd-update and pkg upgrade for all jails. Use cron jobs or Bastille's bulk command feature.

Audit Jail Access

Log all jexec sessions. Monitor jail console logs. Consider enabling FreeBSD's audit framework (auditd) on the host to track process creation and file access across jail boundaries.

---

FAQ

How many jails can I run on one server?

There is no hard limit built into FreeBSD. The practical limit depends on your hardware. Each jail consumes memory for its running processes and disk space for its filesystem. Lightweight jails (DNS resolver, cron runner) might use 30-50 MB of RAM. A jail running PostgreSQL might use several gigabytes. Servers with 64 GB of RAM routinely run 50-100 jails. With ZFS thin provisioning, disk overhead per jail is minimal.

Can I run Docker inside a FreeBSD jail?

No. Docker requires the Linux kernel. FreeBSD jails run on the FreeBSD kernel. However, you can run Linux containers inside a FreeBSD bhyve virtual machine, or use the Linux compatibility layer (linuxulator) for individual Linux binaries. For containerization on FreeBSD, jails are the native and superior solution.

How do jails compare to bhyve virtual machines?

Jails share the host kernel and have near-zero overhead. They start in under a second. Bhyve is a full hypervisor that runs separate kernels -- it can run Linux, Windows, or other FreeBSD versions, but each VM consumes dedicated CPU and memory and takes longer to boot. Use jails when you are running FreeBSD workloads that need isolation. Use bhyve when you need a different operating system or full kernel-level isolation.

Can jails communicate with each other?

Yes. If jails are on the same network (via shared bridge in VNET mode or shared host interface in inherited IP mode), they can communicate over TCP/IP like any networked hosts. You can also use nullfs mounts to share specific directories between jails and the host. For database-backed web applications, it is common to run the web server and database in separate jails that communicate over a private bridge network.

How do I back up a jail?

The best approach depends on your storage setup. With ZFS, use zfs snapshot and zfs send:

sh

zfs snapshot zpool/jails/webserver@backup-2026-03-29

zfs send zpool/jails/webserver@backup-2026-03-29 | gzip > /backups/webserver-2026-03-29.zfs.gz

Without ZFS, use tar:

sh

service jail stop webserver

tar -czf /backups/webserver-2026-03-29.tar.gz -C /usr/local/jails/containers/webserver .

service jail start webserver

ZFS snapshots are atomic and can be taken while the jail is running.

Is it safe to allow allow.raw_sockets in a jail?

Enabling raw sockets allows tools like ping, traceroute, and packet capture inside the jail. The security risk is that a compromised jail could craft arbitrary packets. For most production jails, this risk is acceptable -- the jail is still confined to its assigned IP address. If the jail does not need network diagnostic tools, leave it disabled.

How do I give a jail access to a specific host directory?

Use a nullfs mount. Add an entry to the jail's fstab file:

/etc/fstab.webserver:


/data/shared /usr/local/jails/containers/webserver/mnt/shared nullfs ro 0 0

And in jail.conf:

conf

webserver {

mount.fstab = "/etc/fstab.webserver";

# ...

}

Use ro (read-only) unless the jail genuinely needs write access. This follows the principle of least privilege.

---

Summary

FreeBSD jails give you production-grade, kernel-enforced process isolation with none of the complexity of Linux container runtimes. Manual creation takes five minutes and teaches you exactly what happens. ZFS integration gives you instant clones, snapshots, and send/receive for migration. VNET gives jails full network independence. Bastille automates the lifecycle.

Start with one jail isolating a service you are already running on bare metal. Move NGINX, PostgreSQL, or a DNS resolver into its own jail. Once you see how clean the separation is and how little overhead it adds, you will never run services directly on the host again.

For the next steps in securing your FreeBSD infrastructure, read our [server hardening checklist](/blog/hardening-freebsd-server/). For storage architecture, see the [ZFS guide](/blog/zfs-freebsd-guide/).