Tedson MyriamHere's the cleaned-up Markdown version of your HTML article for dev.to: # FreeBSD Jails VNET...
I've been running FreeBSD jails for years, but when I discovered VNET jails with proper network isolation, my infrastructure game changed completely. The ability to give each service its own network stack with full firewall control is a game-changer for security and flexibility. Let me walk you through how I set up modern VNET jails with IPv6-only networking on FreeBSD 14.
This guide covers setting up FreeBSD 14 VNET jails with:
epair interfaces and bridge networkingFreeBSD Jails offer lightweight virtualization with process and filesystem isolation. While traditional jails share the host's network stack, VNET jails get their own independent network environment. This setup gives you complete control over each jail's networking, including IP addresses, routing tables, and firewalls.
Before creating VNET jails, you need to configure your FreeBSD host properly.
First, let's load the necessary kernel modules and configure them to load automatically:
# 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 bridge interface in /etc/rc.conf:
# /etc/rc.conf additions
cloned_interfaces="bridge0"
ifconfig_bridge0="inet6 -ifdisabled up" # IPv6-only bridge
ipv6_enable="YES"
ipv6_gateway_enable="YES"
rtsold_enable="YES" # If host needs to get IPv6 from upstream
rtadvd_enable="YES" # If host acts as a router for jails
We need to adjust some sysctl parameters for proper networking:
# Enable IP forwarding for IPv4 and IPv6
sysctl net.inet.ip.forwarding=1
sysctl net.inet6.ip6.forwarding=1
# Configure bridge filtering
sysctl net.link.bridge.pfil_member=0
sysctl net.link.bridge.pfil_bridge=1
# 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
Here's a basic PF configuration for the host:
# /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
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
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:
pfctl -e -f /etc/pf.conf
Now let's create our first VNET jail with proper network isolation.
An epair interface is a virtual network device that comes in pairs (epairXa and epairXb). Here's how it works with VNET jails:
epairXa goes into the jail's network stackepairXb stays on the host and connects to the bridgeHere's an example jail.conf entry for an IPv6-only VNET jail:
# /etc/jail.conf
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";
# Commands executed on the host before jail starts
exec.prestart += "ifconfig epair0 create";
exec.prestart += "ifconfig bridge0 addm epair0b";
# Commands executed inside the jail after it starts
exec.start += "ifconfig epair0a up";
exec.start += "ifconfig epair0a inet6 accept_rtadv";
exec.start += "route add -inet6 default fe80::1%epair0a";
# Commands executed on the host after jail stops
exec.poststop += "ifconfig bridge0 deletem epair0b";
exec.poststop += "ifconfig epair0b destroy";
mount.devfs;
allow.raw_sockets;
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";
}
To start the jail:
jail -c vnet-jail-01
To enter the jail's console:
jexec vnet-jail-01 /bin/csh
An IPv6-only architecture simplifies network management and prepares your infrastructure for the future.
Configure the host to act as a router for the jails:
# /etc/rc.conf (ensure these are present)
ipv6_enable="YES"
ipv6_gateway_enable="YES"
rtadvd_enable="YES"
# /etc/rtadvd.conf
bridge0:\
:addrs#1:prefix:2001:db8:jails::/64:
Start the rtadvd service:
service rtadvd start
Inside the jail, ensure IPv6 is enabled:
# /jails/vnet-jail-01/etc/rc.conf
ipv6_enable="YES"
rtsol_enable="YES"
Configure DNS in the jail:
# /jails/vnet-jail-01/etc/resolv.conf
nameserver 2001:4860:4860::8888
nameserver 2001:4860:4860::8844
For services needing stable addresses:
# In /etc/jail.conf for vnet-jail-01
# ...
exec.start += "ifconfig epair0a inet6 2001:db8:jails::10/64 up";
exec.start += "route add -inet6 default 2001:db8:jails::1";
# ...
PF provides powerful firewall capabilities at both host and jail levels.
The host's PF controls traffic between external networks and the jail bridge:
# /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
Each jail can run its own PF instance:
# /jails/vnet-jail-01/etc/pf.conf
int_if = "epair0a"
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
pass in on $int_if proto icmp6 from any to any icmp6-type { echoreq, echorep, routersol, routeradvert, neighbrsol, neighbradvert } keep state
Enable PF in the jail's /etc/rc.conf:
pf_enable="YES"
pf_rules="/etc/pf.conf"
For more complex setups:
# /etc/rc.conf
cloned_interfaces="bridge0 bridge1"
ifconfig_bridge0="inet6 -ifdisabled up"
ifconfig_bridge1="inet6 -ifdisabled up"
Consider using tools like:
Key commands for debugging:
ifconfig -a (host and jail)netstat -rn (host and jail)pfctl -sr, pfctl -sa (host and jail)tcpdump -i <interface>dmesg, /var/log/messages, jail console logsSetting up VNET jails with proper network isolation gives you incredible flexibility and security for your FreeBSD infrastructure. The IPv6-only approach simplifies configuration while preparing your systems for the future.
Have you tried setting up VNET jails before? What challenges did you face? I'd love to hear about your experiences in the comments below!
Originally published on synthetic-context.net