Wireguard jail on FreeBSD

2025-11-25 - How to configure Wireguard in a jail on FreeBSD
Tags: FreeBSD Vpn Wireguard

Introduction

One of my readers contacted me with an interesting question regarding Wireguard on FreeBSD: how to run Wireguard inside a jail instead of on the host. I only ever ran Wireguard on the host since that’s where I centralize all my routing and firewalling but was curious to explore the question.

Requirements

Nowadays Wireguard is easier to configure than ever on FreeBSD! But running it inside a jail needs two things that might not be straightforward: adding the if_wg module to /boot/loader.conf and using a vnet jail that exposes the right devfs rule set.

I did not take for granted that the if_wg kernel module handled jails properly, so I checked the sources. Good news is that the module seems to have enough jail specific code to properly handle the network segregation.

Loader.conf

When running Wireguard on the host, the kernel module is loaded automatically by an ifconfig wg0 create command. While running inside a jail though, this command will produce the following error message:

SIOCIFCREATE2 (wg0): Invalid argument

This is because ifconfig inside the jail does not have permissions to load kernel modules. To work around that, ensure that the module is loaded on boot by adding the following to /boot/loader.conf:

if_wg_load="YES"

Vnet jail

A standard vnet jail without special permissions is enough to run Wireguard. Just make sure to use the same devfs statements that I have in my /etc/jail.conf.d/wireguard.conf file:

wireguard {
  exec.consolelog = "/var/log/jail_console_${name}.log";

  # PERMISSIONS
  exec.clean;
  mount.devfs;
  devfs_ruleset = 5;

  # HOSTNAME/PATH
  host.hostname = "${name}";
  path = "/usr/local/jails/containers/${name}";

  # NETWORK
  vnet;
  vnet.interface = "${epair}b";
  $id = "154";
  $ip = "10.0.2.${id}/24";
  $gateway = "10.0.2.2";
  $bridge = "bridge0";
  $epair = "epair${id}";
  # ADD TO bridge INTERFACE
  exec.prestart  = "/sbin/ifconfig ${epair} create up";
  exec.prestart += "/sbin/ifconfig ${epair}a up descr jail:${name}";
  exec.prestart += "/sbin/ifconfig ${bridge} addm ${epair}a up";
  exec.start     = "/sbin/ifconfig ${epair}b ${ip} up";
  exec.start    += "/sbin/route add default ${gateway}";
  exec.start    += "/bin/sh /etc/rc";
  exec.stop      = "/bin/sh /etc/rc.shutdown";
  exec.poststop  = "/sbin/ifconfig ${bridge} deletem ${epair}a";
  exec.poststop += "/sbin/ifconfig ${epair}a destroy";
}

Make sure to change the jail Id, IP network, gateway and bridge interface name to match your setup.

Wireguard

Create the following files inside your jail. An easy way to do that is to start the jail and exec into it:

service jail start wireguard
jexec wireguard sh

Wireguard works out of the box on an up to date FreeBSD 14.3 without any additional packages like wireguard-tools. Just set the interface name and IP address in /etc/rc.conf:

network_interfaces="wg0"
ifconfig_wg0="inet 10.1.2.18/24"

Then add the following to /etc/start_if.wg0:

ifconfig wg0 create
wg syncconf wg0 /etc/wg0.conf

And finally your Wireguard configuration at /etc/wg0.conf:

[Interface]
PrivateKey = XXXXXX
ListenPort = 342

[Peer]
PublicKey = R4A01RXXqRJSY9TiKQrZGR85HsFNSXxhRKKEu/bEdTQ=
Endpoint = 168.119.114.183:342
AllowedIPs = 10.1.2.9/32
PersistentKeepalive = 60

Start or restart your jail, then confirm that Wireguard is running properly with:

jexec wireguard ifconfig wg0
jexec wireguard wg

Conclusion

All this made me think that an interesting next step to take would be to configure a Wireguard interface on the host, then pass it as the only interface for a jail instead of a bridge. The ramifications of this sound pretty neat!