Infrastructure
How We Use FreeBSD Jails for monerod
How we run monerod in a Bastille-managed FreeBSD jail, install signed packages, route traffic with PF and check host resource limits.

We use FreeBSD jails to give monerod its own operating environment while keeping administration on the FreeBSD host. The jail contains the service's files, accounts, packages, and configuration. The host controls the kernel, storage, and network policy.
Our deployment includes a jail named monerod, managed through Bastille, with its root under /usr/local/bastille/jails/monerod/root. Its private address lets the host control public access through PF. The examples below illustrate that arrangement. Addresses, ports, and capacity limits should be adapted to the machine.
The jail's operating environment
A jail shares the host's kernel while restricting the environment visible to its processes. We maintain a separate userland and package set for the node. Restarting the jail does not require restarting the host. FreeBSD documents traditional and VNET arrangements in its jails handbook.
Install monerod from our FreeBSD repository
We use our FreeBSD package repository at pkg.monero.love to install the release inside the jail. Its monerod-love package follows stable Monero releases, distributed as an unofficial FreeBSD build. This gives us a consistent installation and update process through pkg, with signed repository metadata.
Repository setup happens inside the jail. Create the configuration and key directories, then download the public signing key:
install -d /usr/local/etc/pkg/keys /usr/local/etc/pkg/repos
fetch -o /usr/local/etc/pkg/keys/monero-love.pub \
https://pkg.monero.love/monero-love.pub
Independently verify the signing key before trusting it. Downloading a key from the repository establishes where it came from, but does not independently establish its authenticity.
Save this configuration as /usr/local/etc/pkg/repos/monero-love.conf:
monero_love: {
url: "https://pkg.monero.love/${ABI}/latest",
enabled: yes,
signature_type: "pubkey",
pubkey: "/usr/local/etc/pkg/keys/monero-love.pub"
}
The ${ABI} variable selects the package repository matching the jail's FreeBSD ABI. The public key lets pkg verify the signed repository metadata.
Install the packaged release and check its version:
pkg update
pkg install monerod-love
monerod-love --version
The package provides these locations:
- Configuration:
/usr/local/etc/monerod-love/monerod.conf - Data:
/var/db/monerod-love - FreeBSD service:
monerod_love
Installation leaves the service disabled. After configuring it, enable and start it inside the jail:
sysrc monerod_love_enable=YES
service monerod_love start
Later releases come through the same repository. We review the proposed package changes before upgrading, then restart the service and verify its version and operation:
pkg update
pkg upgrade -n monerod-love
The -n option previews the upgrade without installing it. This keeps the installed release under package management and places its configuration, service script, and data within the jail's maintenance boundary.
Choose the jail's networking arrangement
For the networking example, we use a traditional jail with a private IPv4 address and the host's networking stack. VNET is another option when a jail needs separate interfaces, routing tables, and network configuration. That distinction changes where network settings belong.
The jail definition identifies its root directory, hostname, address, and startup and shutdown commands. Manually managed definitions belong in jail.conf or separate files under jail.conf.d. Bastille manages its own per-jail definition. Keep permissions narrow and expose only necessary devices. The jail manual describes these controls.
The service should run under a dedicated account with write access to its data and logs. Host administration remains outside that account's responsibilities.
Route public node traffic through PF

PF handles public connectivity by translating incoming traffic to the jail's private address. Outbound connections use the host's public address. Here is an illustrative /etc/pf.conf excerpt:
ext_if = "ix0"
jail_ip = "10.170.100.10"
node_port = "18080"
set limit states 200000
nat on $ext_if inet from $jail_ip to any -> ($ext_if)
rdr on $ext_if inet proto tcp from any to ($ext_if) \
port $node_port -> $jail_ip
pass in on $ext_if inet proto tcp from any to $jail_ip \
port $node_port flags S/SA keep state
pass out on $ext_if inet from $jail_ip to any keep state
This assumes the jail address and host networking already exist. Merge the excerpt into the appropriate sections of an existing ruleset, retaining management access and the surrounding default policy. Filtering sees the translated destination, so the inbound pass rule names the private address.
Port 18080 is Monero's default mainnet peer-to-peer port. This example does not expose a wallet RPC service. Our monerod command reference covers daemon networking and RPC options.
The state ceiling covers the whole firewall, including other services and connections that are closing. It is not a target connection count. The PF configuration manual explains these settings.
Check syntax before loading changes:
pfctl -nf /etc/pf.conf
Check host and process resource limits
High connection counts also require sufficient file descriptors. Sockets consume descriptors alongside files and logs. Raising the firewall ceiling cannot remove a descriptor bottleneck.
An illustrative host /etc/sysctl.conf configuration is:
kern.maxfiles=262144
kern.maxfilesperproc=131072
kern.ipc.soacceptqueue=4096
These are starting values for testing on a suitably provisioned host. Check existing settings first, since the machine may already have higher limits. Account for every jail and host service sharing these resources.
kern.maxfiles controls the system-wide file table ceiling. kern.maxfilesperproc sets the kernel ceiling for an individual process. kern.ipc.soacceptqueue limits the listen backlog for connections awaiting acceptance. It does not determine how many established connections a service can maintain. The application's requested backlog also matters. See FreeBSD's tuning manual.
For this shared-stack example, apply the settings on the host:
sysctl kern.maxfiles=262144
sysctl kern.maxfilesperproc=131072
sysctl kern.ipc.soacceptqueue=4096
A process can still inherit a lower descriptor limit from its startup environment. Inspect its limits and open descriptors using its host-visible process ID:
procstat -l <PID>
procstat -f <PID>
If necessary, adjust the service's startup limits, restart it normally, and inspect the replacement process.
Measure behavior under load
Measure resource usage during sustained traffic and connection bursts:
pfctl -si
pfctl -sm
sysctl kern.openfiles
netstat -Lan
netstat -m
These show firewall states, configured limits, open files, listen queues, and network memory statistics. Larger limits permit more resource consumption, so memory availability and responsiveness belong in the same review.
Keep the host and jail userland on compatible, supported releases. Preserve configuration before package upgrades and verify the service after restarts. Our repository supplies the packaged release, the jail contains its operating environment, and the FreeBSD host provides the networking and resources it needs.