# 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/).