How to Isolate Nginx with a FreeBSD 15 Service Jail

Updated on Nov 9, 2026
Mathew M
9 MINS READ
Table of Contents
FreeBSD Nginx Service Jail

A service jail puts one rc.d service, such as Nginx, inside its own small jail with one line in rc.conf. You do not need a jail folder, a jail.conf file, or a second copy of FreeBSD. In this guide, you will learn to build a FreeBSD 15 service jail for Nginx, choose between pkgbase and distribution sets, open the ports with pf, and test the isolation.

Prerequisites

Before you build a FreeBSD 15 service jail for Nginx, you need:

  • A fresh FreeBSD 15.1 server with root access and console access.
  • A non-root admin user and key-based SSH.
  • A public IPv4 address.
  • Ports 80 and 443 free on the host.

If you need a server, a Linux VPS plan from PerLod lists FreeBSD as an available system. Check that version 15.1 is offered for your plan before you order.

If you have not done the base setup yet, you can follow our FreeBSD 15.1 setup guide.

FreeBSD Jail Types: Service, Thick, and Thin

This table shows how a FreeBSD 15 service jail for Nginx differs from the older jail types. All three use the FreeBSD jail system. The difference is in the files and the setup work.

Type Files Setup Best for
Thick jail Full private copy of the userland jail.conf, folder, network Strong separation, own updates
Thin jail Shared base through nullfs or ZFS clones jail.conf, folder, network Many jails, less disk use
Service jail Uses the host files (path=/) One line in rc.conf Locking down one daemon

A service jail is not a small virtual machine. It shares the host file system, so it does not hide /etc or /home. It limits what the service can see and do. It cannot see other processes, and it only gets the network access you give it.

Use a service jail when the daemon should run on the host with less power. Use a thick or thin jail when you need a separate file system, its own IP, or its own packages.

How to Choose a FreeBSD Base System Method

Your FreeBSD 15 service jail for Nginx uses the host's base system, so this choice affects the whole server. You make it once. There are two ways to install and update the base system:

  • pkgbase: the base system is a set of packages from the FreeBSD-base repository. You update it with pkg. The 15.1 installer uses pkgbase by default, and the old distribution-set install is still available.
  • Distribution sets: the classic base.txz install. You update it with freebsd-update.

You can check which one your server uses with the commands below:

Bash
pkg info -g 'FreeBSD-*' | headfreebsd-version -kru

If you see packages such as FreeBSD-runtime, the host uses pkgbase. If the list is empty, it uses distribution sets. Do not mix the two update tools on one system. For a new server, pkgbase is the better choice because one tool updates both the base system and Nginx.

Install Nginx on the Host

Because the service jail uses the host files, you must install Nginx in the normal way:

Bash
pkg updatepkg install nginx -ynginx -v

Then, create a test page so you can see the result later:

Bash
echo '<h1>Nginx in a service jail</h1>' > /usr/local/www/nginx/index.htmlnginx -t

The nginx -t command must report that the syntax is OK. The default config listens on port 80 and runs workers as the www user.

Deploy a FreeBSD 15 Service Jail for Nginx

At this point, you must set the service variables with sysrc. The nginx_svcj setting turns on the jail. The nginx_svcj_options setting gives it network access. Nginx does not ship a default network option, so you must set it:

Bash
sysrc nginx_enable=YESsysrc nginx_svcj=YESsysrc nginx_svcj_options=net_basic

The net_basic option allows IPv4 and IPv6 access and lets the jail bind to ports below 1024, such as 80 and 443. Without it, the service gets no network access.

Now start Nginx and look at the jail with:

Bash
service nginx startjls name path

You should see a jail named svcj-nginx with the path /. This is the FreeBSD 15 service jail for Nginx you just created, and rc.d built it for you at start time.

Limit the Jail to One IP

By default, the jail can see every address on the host. To limit it to one IPv4 address, you can use netv4 and nginx_svcj_ipaddrs. Replace the example address with your own:

Bash
sysrc nginx_svcj_options=netv4sysrc nginx_svcj_ipaddrs=203.0.113.10service nginx restart

Make sure Nginx does not listen on an address that the jail cannot see. With netv4, the jail only sees the IPv4 address in the list. Note that netv4 and netv6 cannot be combined. Use net_basic for both.

Allow Nginx Traffic Through the pf Firewall

A FreeBSD 15 service jail for Nginx uses the same network as the host. So the host's pf firewall controls its traffic, and you do not need NAT or rdr rules. A thick or thin jail on a private network works differently and needs those rules.

For a full walkthrough of pf rules, you can read our FreeBSD pf firewall setup guide. Below is a short ruleset that is enough for this web server.

Keep your server console open. A wrong rule can lock you out of SSH. Run route -n get default to find your network interface, and change vtnet0 below if yours has a different name:

Bash
cat > /etc/pf.conf <<'EOF'ext_if = "vtnet0"web_ports = "{ 80, 443 }" set skip on lo0block in allpass out all keep state pass in on $ext_if proto tcp to ($ext_if) port 22 flags S/SA keep statepass in on $ext_if proto tcp to ($ext_if) port $web_ports flags S/SA keep statepass in inet proto icmp allpass in inet6 proto ipv6-icmp allEOF pfctl -nf /etc/pf.conf

No output from pfctl -nf means the syntax is correct. If your provider uses DHCP, you must add a rule that allows DHCP replies. Then, enable pf and load the rules:

Bash
sysrc pf_enable=YESservice pf startpfctl -sr

Also, check that your provider firewall allows TCP ports 80 and 443.

Verify the FreeBSD Service Jail Isolation

Now you can verify that your FreeBSD 15 service jail for Nginx works and is isolated. First, you must test that Nginx answers. Run this on the server and then from another computer:

Bash
fetch -qo - http://127.0.0.1/curl -I http://SERVER_IP/

You should get an HTTP 200 reply. Then, check the jail:

Bash
jls name jid pathps -axo jid,pid,user,command | grep '[n]ginx'sockstat -4 -l | grep nginx

The Nginx processes should show the jail ID of svcj-nginx, not 0. The master process runs as root, and the workers run as www.

Now enter the jail and check what you can see:

Bash
jexec svcj-nginx ps -ax

You should only see Nginx processes. SSH, the shell, and other services are not visible. This proves the process isolation.

Note: Keep in mind that the file system is shared. If someone hacks Nginx, they can still read any file the www user can read. You must set private files to mode 600 or 640 and give them a different owner. Also, keep your Nginx config clean.

Update the FreeBSD Host and Nginx

Upgrading a FreeBSD 15 service jail for Nginx is simple. There is no separate jail root to rebuild. You must update the host, then restart the service.

To update Nginx, you can run these commands:

Bash
pkg updatepkg upgrade -y nginxnginx -tservice nginx restart

If your host uses pkgbase, update the base system with these commands:

Bash
pkg upgrade -r FreeBSD-baseshutdown -r now

If your host uses distribution sets, update the base system with these commands:

Bash
freebsd-update fetchfreebsd-update installshutdown -r now

Read the prompts before you accept any change. Before a major upgrade, read the official upgrade notes for your release. After the reboot, run freebsd-version -kru and jls to confirm the jail is back.

Later, you may add a thick or thin jail on a pkgbase host. Do this only if you need a separate file system. To install the jail's base system, run:

Bash
pkg -r /jails/web install FreeBSD-set-minimal-jail

To update it, run:

Bash
pkg -j web upgrade -r FreeBSD-base

Fix Nginx Bind and Network Problems on FreeBSD

Most problems come from the _svcj_options setting or the IP list. You can use this table to find the cause.

Problem Common Cause Fix
Nginx fails to bind port 80 or 443 No network option in the jail sysrc nginx_svcj_options=net_basic, then restart
"Can't assign requested address" listen uses an IP that is not in nginx_svcj_ipaddrs Fix the IP list or the listen line
"Address already in use" Another service owns the port Run sockstat -4 -l and stop the other service
Nginx starts but nobody can connect pf blocks the port Run pfctl -sr and add the port rule
Jail cannot reach other hosts No network option, blocked by pf, or bad DNS Check the options, pass out rule, and /etc/resolv.conf
Service does not start in a jail Config test fails Run nginx -t and read the error

You can run these commands to find the problem:

Bash
sysrc nginx_svcj nginx_svcj_options nginx_svcj_ipaddrsservice nginx stopservice nginx starttail -n 30 /var/log/nginx-error.logsockstat -46 -l | grep ':80'jexec svcj-nginx fetch -o /dev/null https://www.freebsd.org/

If another program on the host already listens on all addresses for a port, Nginx cannot use that port. Run sockstat and look for *:80.

If Nginx must connect to other servers, for example as a reverse proxy, the jail needs net_basic or netv4. Also, pf must allow outgoing traffic.

If your proxy only talks to a backend on the same server, use 127.0.0.1. The rule set skip on lo0 already allows this traffic.

To turn off the service jail, run these commands:

Bash
service nginx stopsysrc nginx_svcj=NOservice nginx start

Conclusion

At this point, you have a working FreeBSD 15 service jail for Nginx. Nginx runs in its own jail named svcj-nginx. It only gets the network access you gave it, and the host pf firewall filters its traffic.

A service jail limits processes and privileges, but it does not hide files. Use pkgbase for easy updates. Use a thick or thin jail when you need a separate file system.

If you plan to run many jails or heavy traffic, a physical server gives you full control of CPU, disks, and network. You can look at PerLod dedicated server hosting after you confirm FreeBSD support with the provider.

For more details about jail types, you can check the FreeBSD docs on jails.

It is a jail that rc.d creates for one service. You can turn it on with name_svcj=YES in rc.conf.

No. Everything is set in rc.conf with sysrc.

Use pkgbase on new servers. Use freebsd-update only if the host was installed from distribution sets.

Yes. Base services are ready for it, and most others work with net_basic.