FreeBSD Jails VNET Configuration Guide 2026
๐ฏ What You'll Learn
This comprehensive guide details the setup of FreeBSD 14 VNET jails, focusing on robust network isolation using epair interfaces, bridge networking, and PF firewall integration. We'll explore an IPv6-only architecture, providing you with the knowledge to build secure, scalable, and future-proof containerized environments.
FreeBSD Jails offer a lightweight, powerful virtualization solution, providing process and file system isolation with minimal overhead. While traditional jails share the host's network stack, the introduction of VNET (Virtual Network Stack) revolutionized jail networking by granting each jail its own independent network environment. This guide, updated for FreeBSD 14 in 2026, delves into configuring VNET jails with a strong emphasis on modern networking practices: epair interfaces, bridge-based connectivity, granular firewall control with PF, and an IPv6-only design for optimal performance and security.
Adopting VNET for your jails unlocks unparalleled flexibility. Each jail can have its own IP addresses (both IPv4 and IPv6), routing tables, and even run its own firewall or network services like DHCP or DNS without interfering with the host system or other jails. This level of isolation is crucial for deploying microservices, multi-tenant applications, or simply enhancing the security posture of individual services.
1. Prepare the Host System for VNET Jails
Before creating VNET jails, the FreeBSD host system requires specific kernel modules, sysctl tunings, and basic network configuration to support virtual networking. This section outlines the necessary steps to prepare your host.
1.1. Kernel Modules and `rc.conf` Configuration
First, ensure the required kernel modules are loaded. While some might be compiled into the kernel, it's safer to explicitly load them or configure them for automatic loading.
# Load modules immediately
kldload if_epair
kldload if_bridge
kldload pf
kldload pflog
# Add to /etc/rc.conf for persistent loading
echo 'if_epair_load="YES"' >> /etc/rc.conf
echo 'if_bridge_load="YES"' >> /etc/rc.conf
echo 'pf_enable="YES"' >> /etc/rc.conf
echo 'pflog_enable="YES"' >> /etc/rc.conf
Next, configure the host's rc.conf to set up the bridge interface and enable IPv6 forwarding. We'll create a bridge interface, typically bridge0, which will act as a virtual switch for our jails.
# /etc/rc.conf additions
cloned_interfaces="bridge0"
ifconfig_bridge0="inet6 -ifdisabled up" # IPv6-only bridge, no IPv4 address on bridge itself
ipv6_enable="YES"
ipv6_gateway_enable="YES" # Enable IPv6 forwarding on the host
rtsold_enable="YES" # If host needs to get IPv6 from upstream
rtadvd_enable="YES" # If host acts as a router for jails
The inet6 -ifdisabled up on bridge0 ensures the bridge is brought up for IPv6 traffic but doesn't attempt to configure an IPv6 address on the bridge interface itself by default, relying on Router Advertisements (RA) for jails or static assignments. If the host needs an IPv6 address on the bridge for management or as a default gateway for jails, you would specify it: ifconfig_bridge0="inet6 2001:db8:jails::1/64 up".
1.2. `sysctl` Tunings
Several sysctl parameters need to be adjusted to allow the bridge to pass traffic correctly and to enable IP forwarding for both IPv4 (if used) and IPv6. For an IPv6-only setup, IPv4 forwarding is less critical but often enabled for completeness or future flexibility.
# Enable IP forwarding for IPv4 and IPv6
sysctl net.inet.ip.forwarding=1
sysctl net.inet6.ip6.forwarding=1
# Prevent the host's PF from filtering traffic between bridge members
# This allows PF to filter on the bridge interface itself, but not on individual epair interfaces
sysctl net.link.bridge.pfil_member=0
sysctl net.link.bridge.pfil_bridge=1 # Enable PF filtering on the bridge interface
# Add to /etc/sysctl.conf for persistence
echo 'net.inet.ip.forwarding=1' >> /etc/sysctl.conf
echo 'net.inet6.ip6.forwarding=1' >> /etc/sysctl.conf
echo 'net.link.bridge.pfil_member=0' >> /etc/sysctl.conf
echo 'net.link.bridge.pfil_bridge=1' >> /etc/sysctl.conf
1.3. Basic Host `pf.conf`
A basic PF configuration on the host is essential to allow traffic to flow through the bridge and to the external network. This example assumes your external interface is vtnet0 and your jails will use the IPv6 prefix 2001:db8:jails::/64.
# /etc/pf.conf on the host
ext_if = "vtnet0"
bridge_if = "bridge0"
jail_net = "2001:db8:jails::/64"
set skip on lo0
set skip on $bridge_if # PF on host will not filter traffic *within* the bridge,
# but will filter traffic *to/from* the bridge.
# This is important if jails have their own PF.
scrub in on $ext_if all fragment reassemble
scrub out on $ext_if all fragment reassemble
# Default deny everything
block all
# Allow all outbound traffic from jails
pass out on $ext_if from $jail_net to any keep state
# Allow specific inbound traffic to jails (e.g., SSH, HTTP/S)
# This assumes the host is acting as a router/gateway for the jails.
# You might use rdr-to for specific port forwarding if needed.
# For IPv6, direct routing is more common.
pass in on $ext_if proto tcp from any to $jail_net port { ssh, http, https } keep state
# Allow host to communicate with jails
pass in quick on $bridge_if from $jail_net to any keep state
pass out quick on $bridge_if from any to $jail_net keep state
# Basic host protection
pass in on $ext_if proto icmp6 from any to any icmp6-type { echoreq, routeradvert, routersol } keep state
pass out on $ext_if proto icmp6 from any to any icmp6-type { echoreq, routeradvert, routersol } keep state
pass in on $ext_if proto tcp from any to ($ext_if) port ssh keep state
After configuring, enable PF and load the rules:
pfctl -e -f /etc/pf.conf
2. Set Up Basic VNET Jails with `epair` and `bridge`
With the host prepared, we can now define and create our VNET jails. The core components here are the epair interfaces and the jail.conf structure.
2.1. Understanding `epair` and `bridge` Interaction
An epair interface is a virtual network device that always comes in pairs: epairXa and epairXb. Think of it as a virtual Ethernet cable. When you create an epair, you get two ends. For VNET jails:
- One end (e.g.,
epair0a) is assigned to the jail's network stack. - The other end (e.g.,
epair0b) remains on the host system. - The host's
bridge0interface acts as a virtual switch, connecting all theepairXbends. This allows jails to communicate with each other and with the host.
2.2. `jail.conf` Structure for VNET Jails
The /etc/jail.conf file is used to define jail configurations. For VNET jails, several specific parameters are crucial:
vnet: Enables the virtual network stack for the jail.vnet.interface: Specifies theepair'a' end that will be moved into the jail.exec.prestart: Commands executed on the host *before* the jail starts, typically to create theepairand add the 'b' end to the bridge.exec.start: Commands executed *inside* the jail after it starts, to configure the network interface within the jail.exec.poststop: Commands executed on the host *after* the jail stops, to remove the 'b' end from the bridge and destroy theepair.
Here's an example jail.conf entry for an IPv6-only VNET jail:
# /etc/jail.conf
# Ensure base system is installed at /jails/basejail
# and /jails/vnet-jail-01 is created with 'jail -c vnet-jail-01'
# or 'zfs create -o mountpoint=/jails/vnet-jail-01 zroot/jails/vnet-jail-01'
vnet_base {
path = "/jails/basejail";
mount.devfs;
allow.raw_sockets;
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
}
vnet-jail-01 {
host.hostname = "vnet-jail-01";
path = "/jails/vnet-jail-01";
vnet;
vnet.interface = "epair0a"; # The 'a' end goes into the jail
# Commands executed on the host before jail starts
exec.prestart += "ifconfig epair0 create";
exec.prestart += "ifconfig bridge0 addm epair0b"; # The 'b' end goes to the bridge
# Commands executed inside the jail after it starts
exec.start += "ifconfig epair0a up";
exec.start += "ifconfig epair0a inet6 accept_rtadv"; # Configure IPv6 via Router Advertisements
exec.start += "route add -inet6 default fe80::1%epair0a"; # Default gateway (host's link-local on bridge)
# Commands executed on the host after jail stops
exec.poststop += "ifconfig bridge0 deletem epair0b";
exec.poststop += "ifconfig epair0b destroy";
mount.devfs;
allow.raw_sockets; # Required for ICMP6, etc.
exec.clean;
exec.consolelog = "/var/log/jail_vnet-jail-01_console.log";
exec.start = "/bin/sh /etc/rc";
exec.stop = "/bin/sh /etc/rc.shutdown";
}
Before starting the jail, ensure the jail's root filesystem exists (e.g., /jails/vnet-jail-01) and contains a basic FreeBSD installation. You can use make installworld DESTDIR=/jails/vnet-jail-01 or a tool like ezjail/cbsd to manage jail creation.
To start the jail:
jail -c vnet-jail-01
To enter the jail's console:
jexec vnet-jail-01 /bin/csh
Inside the jail, you should see epair0a configured:
# ifconfig epair0a
epair0a: flags=8863<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
options=8<VLAN_MTU>
ether 02:ff:ff:ff:ff:0a
inet6 fe80::4ff:ff:feff:fa%epair0a prefixlen 64 scopeid 0x1
inet6 2001:db8:jails:xxxx:xxxx:xxxx:xxxx:xxxx prefixlen 64 autoconf
media: Ethernet autoselect
status: active
nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL,NO_DAD>
The inet6 accept_rtadv command in exec.start tells the jail to configure its IPv6 address automatically using Router Advertisements from the host's rtadvd service, which we'll configure next.
Key Principle: Automation
Always automate the creation and destruction of epair interfaces within jail.conf's exec.prestart and exec.poststop hooks. Manual management is error-prone and leads to orphaned interfaces.
3. Implement IPv6-Only Networking for Jails
An IPv6-only architecture simplifies network management, reduces attack surface, and prepares your infrastructure for the future. This section details how to configure the host and jails for this setup.
3.1. Host `rtadvd` Configuration
The host needs to act as a router for the jails, advertising the IPv6 prefix they should use. This is done with the rtadvd service.
# /etc/rc.conf (ensure these are present)
ipv6_enable="YES"
ipv6_gateway_enable="YES"
rtadvd_enable="YES"
# /etc/rtadvd.conf
# This configuration advertises the prefix 2001:db8:jails::/64 on bridge0.
# Jails will automatically configure addresses within this prefix.
bridge0:\
:addrs#1:prefix:2001:db8:jails::/64:
Replace 2001:db8:jails::/64 with your actual IPv6 prefix. If you don't have a globally routable prefix, you can use a Unique Local Address (ULA) prefix like fd00::/64 for internal jail communication, but jails won't have direct internet access without NAT64/DNS64.
Start rtadvd:
service rtadvd start
3.2. Jail `rc.conf` and DNS Configuration
Inside the jail, ensure IPv6 is enabled and the router solicitation daemon (rtsol) is configured to receive advertisements.
# /jails/vnet-jail-01/etc/rc.conf
ipv6_enable="YES"
rtsol_enable="YES"
The exec.start += "ifconfig epair0a inet6 accept_rtadv" in jail.conf, combined with rtsol_enable="YES", will ensure the jail automatically configures its IPv6 address and default route.
For DNS resolution, configure /jails/vnet-jail-01/etc/resolv.conf. You can point it to a public IPv6 DNS server or a local DNS server running on the host or another jail.
# /jails/vnet-jail-01/etc/resolv.conf
nameserver 2001:4860:4860::8888 # Google Public DNS IPv6
nameserver 2001:4860:4860::8844
3.3. Static IPv6 Addressing (Alternative)
For services requiring stable, predictable IPv6 addresses, you can assign them statically instead of relying on SLAAC. This involves removing inet6 accept_rtadv and rtsol_enable, and explicitly setting the address and default route.
# In /etc/jail.conf for vnet-jail-01
# ...
exec.start += "ifconfig epair0a inet6 2001:db8:jails::10/64 up"; # Static IPv6 address
exec.start += "route add -inet6 default 2001:db8:jails::1"; # Host bridge IP as gateway
# ...
In this case, 2001:db8:jails::1 would be the IPv6 address assigned to the host's bridge0 interface, acting as the gateway for the jails.
Advanced IPv6 Routing & NAT64/DNS64
Access detailed configurations for complex IPv6 routing scenarios, including multi-homed jails, BGP peering, and full NAT64/DNS64 setup for seamless IPv4 connectivity from IPv6-only jails.
4. Integrate PF Firewall for Enhanced Security
PF (Packet Filter) is FreeBSD's powerful stateful firewall. With VNET, you can implement PF rules on both the host and independently within each jail, creating a layered security model.
4.1. Host PF for VNET Jails
The host's PF rules primarily control traffic between the external network and the jail bridge, and between the host and the jails. As configured in Section 1.3, the host's PF should:
- Default deny all traffic.
- Allow outbound connections from jails to the internet.
- Allow specific inbound connections to jails (e.g., SSH, HTTP/S).
- Allow the host to communicate with its jails.
Consider using PF's tagging feature to simplify rules for multiple jails or services:
# /etc/pf.conf (host)
# ...
# Define tags for specific jail services
pass in on $ext_if proto tcp from any to $jail_net port ssh tag SSH_JAIL
pass in on $ext_if proto tcp from any to $jail_net port http tag HTTP_JAIL
# Example: allow SSH to specific jail IP
pass in on $ext_if proto tcp from any to 2001:db8:jails::10 port ssh keep state tag SSH_JAIL_01
Remember that set skip on bridge0 means the host's PF won't filter traffic *between* jails on the same bridge, nor traffic *from* the bridge *to* the jails. This allows jails to have their own PF rules without conflict.
4.2. Jail-Specific PF Configuration
Each VNET jail can run its own PF instance, providing microsegmentation and fine-grained control over its network traffic. This is a significant security advantage.
First, enable PF inside the jail by adding to its /etc/rc.conf:
# /jails/vnet-jail-01/etc/rc.conf
pf_enable="YES"
pf_rules="/etc/pf.conf"
Then, create the jail's /etc/pf.conf. This example allows outbound traffic and SSH inbound, blocking everything else.
# /jails/vnet-jail-01/etc/pf.conf
int_if = "epair0a" # The jail's internal interface
set skip on lo0
# Default deny all
block all
# Allow outbound connections
pass out on $int_if all keep state
# Allow inbound SSH
pass in on $int_if proto tcp from any to any port ssh keep state
# Allow ICMP6 for network diagnostics and discovery
pass in on $int_if proto icmp6 from any to any icmp6-type { echoreq, echorep, routersol, routeradvert, neighbrsol, neighbradvert } keep state
To load these rules when the jail starts, ensure your jail.conf includes the PF start command:
# In /etc/jail.conf for vnet-jail-01
# ...
exec.start += "pfctl -e -f /etc/pf.conf";
# ...
This layered approach means the host's PF protects the entire system and controls external access to the jail network, while each jail's PF protects the services running within that specific jail.
Layered Security
Implement PF on both the host and within each VNET jail. The host's PF acts as a perimeter, while the jail's PF provides microsegmentation, limiting the blast radius in case of compromise.
5. Advanced VNET Features and Troubleshooting
Beyond basic setup, VNET offers advanced configurations for complex network topologies. Understanding common issues and debugging techniques is also crucial for maintaining a stable environment.
5.1. Multiple Bridges and VLANs
For more sophisticated network segmentation, you can create multiple bridge interfaces on the host, each connected to a different set of jails or VLANs. This is ideal for separating development, staging, and production environments, or for isolating different service tiers (e.g., web, app, database).
# /etc/rc.conf
cloned_interfaces="bridge0 bridge1"
ifconfig_bridge0="inet6 -ifdisabled up"
ifconfig_bridge1="inet6 -ifdisabled up" # Another bridge for a different network segment
# In /etc/jail.conf for a jail on bridge1
vnet-jail-02 {
host.hostname = "vnet-jail-02";
path = "/jails/vnet-jail-02";
vnet;
vnet.interface = "epair1a"; # Use a new epair pair
exec.prestart += "ifconfig epair1 create";
exec.prestart += "ifconfig bridge1 addm epair1b"; # Add to bridge1
# ... rest of jail config
}
You can also integrate VLANs by adding VLAN-tagged interfaces to your bridges. For example, if your physical interface vtnet0 carries VLANs:
# /etc/rc.conf
cloned_interfaces="bridge0 vlan10 vlan20"
ifconfig_vlan10="vlan 10 vlanif vtnet0"
ifconfig_vlan20="vlan 20 vlanif vtnet0"
ifconfig_bridge0="addm vlan10 addm epair0b up" # Bridge for VLAN 10
ifconfig_bridge1="addm vlan20 addm epair1b up" # Bridge for VLAN 20
5.2. Jail Management Tools
Manually managing jail.conf and creating jail filesystems can become cumbersome with many jails. Tools like cbsd and pot (based on ZFS) simplify the lifecycle management of jails, including VNET configuration, template creation, and resource allocation.
- CBSD: A powerful framework for managing jails, bhyve, and other virtual environments. It provides a command-line interface and a web UI.
- Pot: A lightweight, ZFS-based jail manager focused on immutable infrastructure and easy deployment of services. It integrates well with VNET.
These tools abstract away much of the manual epair and bridge configuration, making VNET jail deployment much faster and less error-prone.
5.3. Monitoring and Troubleshooting
Effective monitoring and debugging are essential for any network setup. Here are some tools and common issues:
ifconfig -a(host and jail): Check interface status, IP addresses, and flags. EnsureepairXais in the jail andepairXbis on the host and added to the bridge.netstat -rn(host and jail): Verify routing tables. Ensure jails have a default route pointing to the host's link-local address on the bridge.pfctl -sr,pfctl -sa(host and jail): Review PF rules and states. Look for blocked packets.tcpdump -i <interface>(host and jail): Capture traffic to diagnose connectivity issues. Use onepairXb,bridge0, and the external interface on the host, and onepairXainside the jail.dmesg,/var/log/messages,/var/log/jail_<jailname>_console.log: Check system logs for errors related to interface creation, jail startup, or network issues.ping6,traceroute6: Test IPv6 connectivity from within the jail to the host, other jails, and external IPv6 addresses.
Common Issues:
- No network connectivity in jail:
- Is
epaircreated and added to the bridge (checkifconfig -aon host)? - Is
epairXaconfigured inside the jail (checkifconfig -ain jail)? - Is
rtadvdrunning on the host and advertising the correct prefix? - Does the jail have a default route (check
netstat -rnin jail)? - Is PF blocking traffic on the host or in the jail?
- Is
- Jail cannot reach external IPv6:
- Is
ipv6_gateway_enable="YES"on the host? - Are host PF rules allowing outbound traffic from
jail_neton the external interface? - Is DNS resolution working in the jail?
- Is
- Jail fails to start:
- Check
/var/log/jail_<jailname>_console.logfor errors duringexec.start. - Ensure
epaircreation commands inexec.prestartare correct.
- Check
By systematically checking these points, you can quickly identify and resolve most VNET jail networking problems. The flexibility and isolation offered by VNET jails, especially with an IPv6-only approach, provide a robust foundation for modern FreeBSD deployments.
๐ Optimize Your FreeBSD Infrastructure
Gain access to advanced VNET jail configurations, automated deployment scripts, and performance tuning guides for high-density, IPv6-only FreeBSD environments. Secure your services with expert-level PF firewall templates.
Request Access Browse DocumentationExternal Resources
- FreeBSD Handbook: Jails โ Official documentation on FreeBSD Jails.
- FreeBSD Handbook: PF โ Comprehensive guide to the Packet Filter firewall.
- epair(4) man page โ Details on the virtual Ethernet pair device.
- bridge(4) man page โ Information on the network bridge device.
- rtadvd(8) man page โ Router Advertisement Daemon for IPv6.