Configuration
Pebble reads /usr/local/etc/pebble/pebble.yaml (override with -c).
examples/annotated.yaml documents every
setting and is itself a valid configuration. Further complete examples:
- examples/minimal.yaml — two-interface router
- examples/site-to-site.yaml — WireGuard between two sites
- examples/dns-filtering.yaml — DNS blocking
Parsing rules#
- One YAML document, at most 1 MiB.
- Unknown keys, duplicate keys and wrong types are errors.
- Omitted optional settings take documented, conservative defaults
(for example
firewall.default_inbound: deny,management.ssh.port: 22). - Durations:
90s,30m,12h,7d.
Validation#
pebble validate reports errors (which block deployment) and warnings
(which do not), each with its configuration path:
error: dhcp.start
the range 192.168.10.1-192.168.10.199 contains the router's own address 192.168.10.1; ...
warning: network.routes[0]
192.168.0.0/16 contains the directly connected LAN network 192.168.10.0/24; local traffic stays local ...
Highlights of the semantic checks:
- Addressing: the router address is a usable host address; LAN, static WAN and tunnel subnets do not overlap.
- Routes are longest-prefix aware: a route that contains a connected network is allowed (warning); a route equal to or inside a connected network is an error because it would divert local traffic; duplicate destinations (including WireGuard peer routes) are errors; the default route can only come from the WAN.
- DHCP: range and reservations inside the LAN, never the router's address, unique MACs, addresses and names.
- WireGuard: keys are valid, key files live directly in
/usr/local/etc/pebble/keys, no0.0.0.0/0, allowed IPs do not overlap the LAN, every route lies inside the peer's allowed IPs. A VPN exit (role: egress) is the exception: exactly one peer, with an endpoint andallowed_ips: [0.0.0.0/0], no routes, agatewaythat is not the router's own tunnel address;gatewayanddnsare refused on any other interface. - Reservations:
egressis empty,none, or the name of a VPN exit. - Exposure: DNS is never offered to WAN-only networks; SSH from a WAN-only
network requires
management.ssh.allow_wan: true; port forwards target internal hosts and never clash with WireGuard ports. - Escaping: names are restricted to DNS-safe characters; TXT values and rule descriptions cannot contain quotes, backslashes or control characters, so nothing in the YAML can inject syntax into generated files.
Checks that need the running machine — whether the interfaces exist,
whether key files exist with mode 0600 and root ownership, whether the time
zone exists, whether /etc/rc.conf overrides Pebble's variables, and
whether the new firewall would cut off your current SSH session — run
in pebble plan and pebble apply on FreeBSD.
Firewall rules#
firewall:
rules:
- description: Site B may reach the file server over SMB only
action: pass # pass | block
interface: wg0 # where the traffic ARRIVES: lan | wan | wgN
protocol: tcp # any | tcp | udp | tcp/udp | icmp
source: peer:site-b # any | self | lan | peer:<name> | list:<name> | address | prefix
destination: 192.168.10.5
ports: ["445"] # destination ports or ranges ("8000-8080")
log: false
Semantics:
- Policy is enforced on ingress. A rule applies to traffic arriving on its interface. Traffic that has been admitted, and traffic the router originates, may leave.
- Rules are evaluated in the order written; the first match wins.
- Before your rules: anti-spoofing, WAN DHCP replies and SSH management
access (so a mistaken rule cannot lock out
management.ssh.allow_from). - After your rules: the administrator anchor
pebble-local, router services (DNS, DHCP, ping from the LAN, WireGuard on the WAN), port forwards, then a block of anything else addressed to the router from internal networks, then LAN-to-Internet (ifallow_lan_to_wan). - Everything else is dropped. IPv6 is dropped entirely.
peer:<name> means the peer's allowed IPs; list:<name> means the
current contents of a downloaded IP list (see
networking.md). self means any address of the
router (a dynamic PF address, so it follows WAN DHCP changes).
allow_lan_to_wan lets the LAN reach destinations that are not internal
(LAN, tunnel, peer and static-route networks). Traffic between the LAN and
remote sites needs explicit rules unless default_forward: allow.
Use pebble firewall explain for a plain-language summary of the policy and
pebble firewall test to evaluate a flow.
Interface names#
Pebble never guesses which port is the WAN. Use pebble interfaces, unplug
and replug cables to identify ports, and set network.wan.device and
network.lan.device explicitly. Interface names must look like FreeBSD
device names (igb0, re1, vtnet0, em0.100).
Monitoring#
monitoring:
enabled: false # start pebble_agent and record events and traffic history
sample_interval: 10s # 2s to 5m
journal_max_mb: 32 # disk limit for the event journal
retention: 30d
probe_targets: [] # up to four IPv4 addresses for the external connectivity check
See flight-recorder.md.
Web interface#
management:
web:
enabled: false
listen: 127.0.0.1:8443 # loopback, the LAN address or a WireGuard address; never the WAN
tls: true # plain HTTP is accepted only on loopback
cert_file: "" # optional; a self-signed certificate is created otherwise
key_file: ""
allow_from: [] # client networks admitted by PF (required for LAN or tunnel listeners)
allowed_hosts: [] # extra Host names (DNS rebinding protection)
session_timeout: 30m
Accounts are not part of pebble.yaml; manage them with pebble web user.
See web.md.
Disabling without deleting#
Firewall rules and DNS feeds accept enabled: false. A disabled rule keeps
its position and label number; a disabled feed is neither downloaded nor
applied.