Skip to content
FreeBSDDeep Dive Published Updated 9 min readViews unavailable

FreeBSD Service Jails: RC-Managed Process Confinement and Its Limits

Operate FreeBSD 15 service jails through rc.conf and rc scripts, grant only required jail properties, and understand why shared filesystems are not isolation.

FreeBSD service jails are an rc-managed way to run an individual service inside a jail without building and maintaining a second filesystem tree. FreeBSD 15 introduced the feature: an rc script and rc.conf settings can ask the startup framework to create a service jail for a daemon, apply the permissions it needs, and launch the process there. That is materially different from defining a conventional jail in jail.conf, but it is not a small virtual machine or a filesystem sandbox.

The distinction is central to using service jails safely. It uses the host’s root filesystem and account files, without a separate filesystem root. It limits what the service sees and can do through jail properties, including process visibility and optional network access, but it does not hide host files behind a separate root. A compromised service may still read or modify any host file its credentials and ordinary filesystem permissions allow. Treat the feature as a useful process-confinement layer, not as a substitute for file permissions, MAC policy, a separately rooted jail, or a VM when those boundaries are required.

The operational controls are intentionally integrated with the familiar rc system: per-service name_svcj and name_svcj_options variables, sysrc(8) for configuration, and service(8) for lifecycle actions. The details below follow FreeBSD 15.1’s rc.conf(5) and current Handbook guidance; verify the installed release’s manuals and rc script before applying a setting on another branch.

A service jail is not a conventional jail with a shorter config

A traditional jail has its own configured root path and lifecycle, commonly described in jail.conf. A service jail is created as part of starting one rc service. Its path is /, so it shares the host’s files and accounts. FreeBSD’s Handbook describes automatic confinement of services as the intended use: the service can run under jail restrictions without requiring the administrator to identify and populate every library or configuration file it needs.

That reduces packaging and deployment overhead, but it changes the security question. With a separately rooted jail, administrators can control which files exist inside the jail and manage its base-system files as a distinct tree. With a service jail, access to a host file depends on the same UID, group, mode, ACL, and other host policy that govern that file outside the jail. A service jail should therefore narrow process and resource visibility only where that helps; it should not be described as preventing the daemon from touching host data.

The rc script remains part of the trust boundary. It determines whether a service is suitable for service-jail execution and which extra jail properties the service needs. The Handbook notes that some base services are excluded because their start or stop actions mount filesystems or configure host-level resources such as routes or firewalls. Third-party scripts may be ready, may need an explicit set of jail options, or may not support the feature at all. A service that starts successfully on the host is not automatically safe or functional inside a service jail.

The per-service contract lives in rc.conf and the rc script

The primary switch is <name>_svcj. When set to YES, FreeBSD starts that service in a service jail named svcj-<name>. Its optional <name>_svcj_options setting controls which jail properties the service receives. The rc script can declare defaults or reject service-jail use; the configuration can override supported options. Read the installed script before making a change rather than assuming an option name or default from another service.

For a base service, the Handbook recommends checking whether the rc script declares _svcj_options. The FreeBSD 15.1 syslogd script used here declares syslogd_svcj_options="net_basic", so this command should show that default:

grep _svcj_options /etc/rc.d/syslogd

For another service, no matching line is ambiguous: the service may not be service-jail-ready, or it may not need any additional restricted resource such as network access. Inspect the surrounding rc script and the release-specific rc.conf(5) manual. For a port-installed daemon, inspect its actual file under the local rc directory; do not assume that a newer ports tree’s script matches an older installed package.

If a service needs ordinary IPv4 and IPv6 networking, the 15.1 manual defines net_basic as enabling both netv4 and netv6. Those individual jail properties are not combined with each other. Both permit their respective network access and binding reserved ports; net_raw permits raw sockets, while net_all grants broader networking access, including raw sockets and protocol stacks without jail functionality. Add only what a documented daemon requirement justifies. Where network access is enabled, <name>_svcj_ipaddrs can constrain which assigned addresses are visible; without that list, all assigned addresses are visible to the service jail according to the manual.

Avoid treating the global svcj_all_enable switch as a convenient shortcut for a first rollout. It enables service jails broadly for services that are not explicitly excluded, and a service’s behavior can change when it runs in the jail. The manual specifically notes that sshd is excluded from that global default because it would no longer see other jails, although it can still be enabled explicitly. Inventory each daemon’s needs and exclusions before considering a system-wide policy.

Start with a stopped service and verify the jail that was created

FreeBSD requires the service to be stopped before changing its _svcj setting. Otherwise the rc framework may not detect the service’s new state correctly. On a disposable FreeBSD 15 VM, the Handbook’s syslogd example illustrates the basic sequence:

service syslogd stop
sysrc syslogd_svcj=YES
service syslogd start

# Confirm the service's rc status and inspect the service jail.
service syslogd status
jls -j svcj-syslogd

Use a lab VM or a maintenance plan before changing logging, authentication, network, storage, or other critical services: moving a daemon can affect the host’s normal operations. sysrc changes persistent rc configuration; service performs the lifecycle operation. jls can confirm that an active jail with the expected service-jail name exists, but it does not prove that the daemon is healthy, that it received only the intended jail properties and network permissions, or that its logs and dependent clients still work. Verify those separately using the service’s own status, logs, and a representative client check.

To revert a test, stop the service again, disable its service-jail setting, then start it in the normal host context:

service syslogd stop
sysrc syslogd_svcj=NO
service syslogd start
jls -c -j svcj-syslogd

Here jls -c -j is an existence check. For a correctly formed invocation, status 0 confirms that the named jail exists; status 1 means it does not. The -c option must be paired with -j; a usage error, such as omitting -j, also exits nonzero but prints a diagnostic. After this revert, a nonzero status with no diagnostic is expected because the service jail should be gone; status 0 means it still exists. Without -c, a missing named jail is reported as an error rather than an empty successful listing. The existence result is not proof that every service-level effect was reversed. Check the daemon’s status and logs, and compare the test configuration to the recorded baseline. A service whose startup script has custom setup or teardown steps may need additional validation.

Do not confuse service -j with enabling a service jail

FreeBSD’s service(8) has a -j jail option. It runs a service action under an already named jail; for example, the manual demonstrates restarting named inside a jail called dns. The option is not limited to conventional jails and can target an already-created service jail too. It is distinct from automatic service-jail lifecycle management: name_svcj and the rc framework configure and create that environment, while service -j runs an action inside a jail that already exists.

Service jails instead use the service-specific name_svcj setting and the ordinary service name start|stop actions documented for rc-managed services. That lifecycle differs from creating and managing a conventional jail with jail(8); jls(8) can inspect the resulting active jail either way. Keeping those interfaces distinct avoids a common operational mistake: managing a regular jail’s services is not the same operation as asking the rc framework to launch a host service in its automatically created svcj-name environment.

A deployment and recovery checklist

Before enabling a service jail, record the FreeBSD release, package version, rc script path, current rc variables, required network families, and any mount, routing, firewall, device, or host-file actions performed by the service. Confirm that the service is intended to run with the shared host filesystem and account database. If the goal is to protect host files from the service, stop here and use an isolation design with a separate filesystem root and an explicit data-sharing policy instead.

Change one service at a time. Stop it, set the opt-in variable, set only the required jail options, and start it again. Verify the resulting svcj-name jail with jls, inspect the service’s actual process state, test its network or local-client behavior, and check that expected files and logs are updated. Then exercise a controlled stop and restart. A successful start banner alone does not demonstrate that the service is reachable or correctly confined.

For a fleet rollout, use a canary service on a test host, compare its behavior before and after, and keep a rollback command that restores the original rc value. Avoid copying a name_svcj_options string from another service: these are jail properties, not generic performance flags or Capsicum capabilities. Keep an inventory of services excluded from a global rollout and review that inventory after FreeBSD or package updates, because rc scripts are executable policy and can change with the installed release.

Service jails fill a practical gap between an unconfined host daemon and a separately provisioned jail. Their low filesystem and administration overhead is attractive for services that need modest process confinement while continuing to use host-installed files. The trade-off is equally clear: there is no filesystem separation, and the process still operates with the host’s identities and data permissions. Naming both sides of that contract makes service jails useful without assigning them protections they do not provide.

Related:

Sources:

Comments