pebble.
Pebble / Docs / Start

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:

Parsing rules#

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:

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:

  1. 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.
  2. Rules are evaluated in the order written; the first match wins.
  3. Before your rules: anti-spoofing, WAN DHCP replies and SSH management access (so a mistaken rule cannot lock out management.ssh.allow_from).
  4. 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 (if allow_lan_to_wan).
  5. 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.