FreeBSD jails with a single public IP address

published on on FreeBSD, Jails, NAT

Jails in FreeBSD provide a simple yet flexible way to set up a proper server layout. In the most setups the actual server only acts as the host system for the jails while the applications themselves run within those independent containers. Traditionally every jail has it's own IP for the user to be able to address the individual services. But if you're still using IPv4 this might get you in trouble as the most hosters don't offer more than one single public IP address per server.

Create the internal network

In this case NAT ("Network Address Translation") is a good way to expose services in different jails using the same IP address.

First, let's create an internal network ("NAT network") at You could generally use any private IPv4 address space as specified in [RFC 1918 ( Here's an overview: Using pf, FreeBSD's firewall, we will map requests on different ports of the same public IP address to our individual jails as well as provide network access to the jails themselves.

First let's check which network devices are available. In my case there's em0 which provides connectivity to the internet and lo0, the local loopback device.

em0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
  inet netmask 0xffffff00 broadcast
  media: Ethernet autoselect (1000baseT <full-duplex>)
  status: active

lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
  inet6 ::1 prefixlen 128
  inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
  inet netmask 0xff000000

For our internal network, we create a cloned loopback device called lo1.
Therefore we need to customize the /etc/rc.conf file, adding the following two lines:


This defines a /29 network, offering IP addresses for a maximum of 6 jails:

Address:          11000000.10101000.00000000.00000 001
Netmask: = 29 11111111.11111111.11111111.11111 000
Wildcard:              00000000.00000000.00000000.00000 111
Network:       11000000.10101000.00000000.00000 000
HostMin:          11000000.10101000.00000000.00000 001
HostMax:          11000000.10101000.00000000.00000 110
Broadcast:          11000000.10101000.00000000.00000 111
Hosts/Net: 6                     Class C, Private Internet

Then we need to restart the network. Please be aware of currently active SSH sessions as they might be dropped during restart. It's a good moment to ensure you have KVM access to that server ;-)

service netif restart

After reconnecting, our newly created loopback device is active:

lo1: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
  inet netmask 0xfffffff8
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff
  inet netmask 0xffffffff

Setting up pf

pf part of the FreeBSD base system, so we only have to configure and enable it. By this moment you should already have a clue of which services you want to expose. If this is not the case, just fix that file later on. In my example configuration, I have a jail running a webserver and another jail running a mailserver:

# Public IP address

# Packet normalization
scrub in all

# Allow outbound connections from within the jails
nat on em0 from lo1:network to any -> (em0)

# webserver jail at
rdr on em0 proto tcp from any to $IP_PUB port 443 ->
# just an example in case you want to redirect to another port within your jail
rdr on em0 proto tcp from any to $IP_PUB port 80 -> port 8080

# mailserver jail at
rdr on em0 proto tcp from any to $IP_PUB port 25 ->
rdr on em0 proto tcp from any to $IP_PUB port 587 ->
rdr on em0 proto tcp from any to $IP_PUB port 143 ->
rdr on em0 proto tcp from any to $IP_PUB port 993 ->

Now just enable pf like this (which is the equivalent of adding pf_enable=YES to /etc/rc.conf):

sysrc pf_enable="YES"

and start it:

service pf start

Install ezjail

Ezjail is a collection of scripts by [erdgeist ( that allow you to easily manage your jails.

pkg install ezjail

As an alternative, you could install ezjail from the ports tree. Now we need to set up the basejail which contains the shared base system for our jails. In fact, every jail that you create get's will use that basejail to symlink directories related to the base system like /bin and /sbin. This can be accomplished by running

ezjail-admin install

In the next step, we'll copy the /etc/resolv.conf file from our host to the newjail, which is the template for newly created jails (the parts that are not provided by basejail), to ensure that domain resolution will work properly within our jails later on:

cp /etc/resolv.conf /usr/jails/newjail/etc/

Last but not least, we enable ezjail and start it:

sysrc ezjail_enable="YES"
service ezjail start

Create a jail

Creating a jail is as easy as it could probably be:

ezjail-admin create webserver
ezjail-admin start webserver

Now you can access your jail using:

ezjail-admin console webserver

Each jail contains a vanilla FreeBSD installation.

Deploy services

Now you can spin up as many jails as you want to set up your services like web, mail or file shares. You should take care not to enable sshd within your jails, because that would cause problems with the service's IP bindings. But this is not a problem, just SSH to the host and enter your jail using ezjail-admin console.