OpenBSD Handbook

    • Part I. Install & Configure
      • Introduction
      • Installing OpenBSD
      • The X Window System
      • Networking
      • System Configuration
      • OpenBSD Basics
      • Managing Software: Packages and Ports
    • Part II. Daily Operations
      • Graphical Environments
      • Multimedia
      • Printing
      • Linux Compatibility
      • Windows Compatibility
      • Games
    • Part III. System Administration
      • Security
      • Virtualization
      • Storage and File Systems
      • Updating and Upgrading
      • Localization
      • The OpenBSD Boot Process
    • Part IV. Networking & Daemons
      • Services
        • Database
          • MariaDB
          • PostgreSQL
          • Redis
          • memcached
        • Directory
          • YP (NIS)
          • LDAP
        • File
          • NFS
          • Samba
        • FTP Services
          • ftpd
          • ProFTPD
          • vsftpd
          • TFTP
        • Mail
          • Dovecot
          • smtpd
          • Postfix
          • Exim
          • Rspamd
        • Name
          • Named
          • Unbound
          • NSD
        • Networking
          • OpenBGPD
          • rtadvd
          • DHCP
          • slaacd
        • Web
          • Apache
          • nginx
          • httpd
          • relayd
        • Logging
          • syslogd
        • Monitoring
          • SNMP
        • Remote Access
          • Audit OpenSSH
          • sshd
        • File Synchronization
          • rsync
        • Messaging
          • RabbitMQ
        • Time
          • NTP
      • PF
        • pfctl cheat sheet
        • PF Anchors
        • PF Filter Rules
        • PF Forwarding
        • PF Lists and Macros
        • PF Load Balancing
        • PF Logging
        • PF NAT
        • PF Options
        • PF Policies
        • PF Shortcuts
        • PF Tables
      • Advanced Networking
        • High Availability and State Replication
        • Multi-WAN and Policy-Based Routing
        • VPN and Cryptographic Tunneling
        • Classic and Lightweight Tunnels
        • IPv6 at Scale
        • QoS and Traffic Shaping
        • MPLS and Label Distribution
        • Network Services at Scale
        • Virtualization and Host Networking
        • Large-Scale L2 and L3 Design
        • Telemetry, Logging, and Flow Export
        • Hardening and Operational Safety
        • Reference Architectures
        • Troubleshooting Playbooks
      • Serial Communication
    • Part V. Miscellaneous
      • Virtualization Cheat Sheet
      • OpenBSD Cheatsheet
      • Howto
        • Install Z shell (zsh)
        • Set Up WordPress
        • Build a Simple Router and Firewall
      • OpenBSD for Linux Users
      • OpenBSD for FreeBSD Users
      • OpenBSD for macOS Users
    • Package Search
      Network Services at Scale
      • Synopsis
      • Design Considerations
      • Configuration
        • 1) Authoritative DNS with nsd(8)
        • 2) Validating recursive DNS with unbound(8)
        • 3) DHCP for IPv4 with dhcpd(8)
        • 4) Time service with ntpd(8)
        • 5) PF allowances for DNS, DHCP, and NTP
      • Verification
      • Troubleshooting
      • See Also

      Network Services at Scale

      Synopsis #

      This chapter describes building reliable, scalable network services on OpenBSD: authoritative DNS with nsd(8) configured via nsd.conf(5) , validating recursive DNS with unbound(8) configured via unbound.conf(5) , IPv4 address allocation with dhcpd(8) configured via dhcpd.conf(5) , and time services with ntpd(8) configured via ntpd.conf(5) . Service lifecycle management uses rcctl(8) . Packet filtering allowances live in pf.conf(5) and are applied with pfctl(8) . Use these patterns for branch offices, campus networks, and PoPs where you operate DNS, DHCP, and NTP as first-class, redundant services.

      Design Considerations #

      • Separation of roles. Bind authoritative and recursive DNS to different IP addresses. A common pattern is NSD listening on a public/WAN address (or a dedicated service VIP) and Unbound listening on LAN addresses.
      • Redundancy. Use at least two servers per critical service. Combine with CARP and pfsync from the HA chapter for failover of service VIPs.
      • Security posture. Permit UDP/TCP 53 from the Internet only to authoritative servers. Restrict recursion to inside prefixes. Enable DNSSEC validation in Unbound.
      • Operational boundaries. NSD does not support dynamic updates; keep zone management explicit and version-controlled. Unbound is not authoritative; use stub zones to prefer local authoritative answers when both run on the same host.
      • IPv6. Provide AAAA records in authoritative zones and ensure recursive service answers both A and AAAA. Address assignment for IPv6 prefers RA (see the IPv6 chapter); DHCPv6 is out of scope here.
      • Time service. DNSSEC validation requires correct time. Ensure ntpd(8) is running on resolvers and that clients have a local NTP source.

      Configuration #

      Assumptions for examples:

      • Interfaces: em0 (WAN), em1 (LAN 10.10.10.0/24).
      • Service IPs: 203.0.113.53 (authoritative DNS on WAN), 10.10.10.1 (recursive DNS and NTP on LAN).
      • Authoritative zone: example.com. served by NSD.
      • Local recursion for LAN clients via Unbound.

      1) Authoritative DNS with nsd(8) #

      Bind NSD to the public address and serve example.com. from a local zone file. Control socket is enabled for safe reloads.

      ## /etc/nsd.conf — NSD authoritative service on WAN address
      
      server:
          ip-address: 203.0.113.53
          hide-version: yes
          do-ip4: yes
          do-ip6: no
          server-count: 1
          zonesdir: "/var/nsd/zones"
          # control socket for nsd-control(8)
          control-enable: yes
      
      zone:
          name: "example.com"
          zonefile: "example.com.zone"
      

      Create the zone file (minimal example; expand as required). The SOA and NS records must be correct for production.

      ## /var/nsd/zones/example.com.zone — minimal zone
      
      $ORIGIN example.com.
      $TTL 300
      @   IN SOA ns1.example.com. hostmaster.example.com. (
              2025010101 ; serial (YYYYMMDDNN)
              3600       ; refresh
              600        ; retry
              1209600    ; expire
              300        ; minimum
      )
          IN  NS  ns1.example.com.
      ns1 IN  A   203.0.113.53
      www IN  A   203.0.113.80
      api IN  A   203.0.113.81
      

      First-time control key setup and service start:

      # nsd-control-setup
        # Generate control keys/certs under /var/nsd/etc; one-time
      # rcctl enable nsd
        # Start on boot
      # rcctl start nsd
        # Launch now
      # nsd-control reload
        # Validate and load the zone
      

      2) Validating recursive DNS with unbound(8) #

      Unbound will listen on LAN, serve only inside clients, and validate DNSSEC. When NSD and Unbound share a host, forward a local zone to NSD via a stub to avoid conflicts on port 53 by running NSD on WAN and Unbound on LAN.

      ## /var/unbound/etc/unbound.conf — LAN recursion with validation
      
      server:
          interface: 127.0.0.1
          interface: 10.10.10.1
          access-control: 10.10.10.0/24 allow
          access-control: 127.0.0.0/8 allow
      
          # Hardening and privacy
          hide-identity: yes
          hide-version: yes
          qname-minimisation: yes
          harden-referral-path: yes
          harden-glue: yes
          prefetch: yes
      
          # DNSSEC
          auto-trust-anchor-file: "/var/unbound/db/root.key"
      
          # Root priming; Unbound can fetch root NS automatically if needed
          root-hints: "/var/unbound/db/root.hints"
      
      # Prefer local authoritative answers (optional internal zone served by NSD)
      stub-zone:
          name: "corp.example."
          stub-addr: 127.0.0.1@5353
      

      If you need Unbound to prefer a local zone while NSD keeps port 53 on WAN, bind NSD additionally to 127.0.0.1 port 5353 and serve corp.example. there (example below). Otherwise omit the stub-zone.

      ## Excerpt — additional NSD listener for an internal zone (loopback)
      
      server:
          # ... existing server block ...
          ip-address: 127.0.0.1@5353
      
      zone:
          name: "corp.example"
          zonefile: "corp.example.zone"
      

      Initialize Unbound control and start the resolver:

      # unbound-control-setup
        # Generate control keys/certs under /var/unbound/etc; one-time
      # rcctl enable unbound
      # rcctl start unbound
      # unbound-control status
        # Confirm validator is ready and loaded
      

      3) DHCP for IPv4 with dhcpd(8) #

      Serve 10.10.10.0/24, advertise the local resolver and default gateway, and provide a static mapping example.

      ## /etc/dhcpd.conf — IPv4 DHCP on LAN
      
      option domain-name "corp.example";
      option domain-name-servers 10.10.10.1;
      option routers 10.10.10.1;
      option subnet-mask 255.255.255.0;
      default-lease-time 3600;
      max-lease-time 7200;
      authoritative;
      
      subnet 10.10.10.0 netmask 255.255.255.0 {
          range 10.10.10.100 10.10.10.200;
      
          # Static reservation example
          host printer-01 {
              hardware ethernet 00:11:22:33:44:55;
              fixed-address 10.10.10.50;
          }
      }
      

      Enable the daemon and bind it to the LAN interface:

      # rcctl set dhcpd flags em1
        # Specify served interfaces (required)
      # rcctl enable dhcpd
      # rcctl start dhcpd
      # tail -n 20 /var/db/dhcpd.leases
        # Inspect active leases
      

      For IPv6 address assignment, prefer Router Advertisements with rad(8) as shown in the IPv6 chapter.

      4) Time service with ntpd(8) #

      Provide an internal NTP source on the LAN and maintain correct time on the server itself.

      ## /etc/ntpd.conf — serve LAN and sync from upstream pool
      
      listen on 10.10.10.1
      servers pool.ntp.org
      
      # rcctl enable ntpd
      # rcctl start ntpd
      # ntpctl -s status
        # Report synchronization state
      

      5) PF allowances for DNS, DHCP, and NTP #

      Permit external queries to NSD on WAN, recursive service on LAN, DHCP on LAN, and NTP for clients.

      ## /etc/pf.conf — service allowances (merge into your policy)
      
      set skip on lo
      
      wan = "em0"
      lan = "em1"
      
      # Base policy
      block all
      
      # Authoritative DNS on WAN (NSD)
      pass in on $wan proto { udp tcp } to 203.0.113.53 port 53 keep state
      
      # Recursive DNS on LAN (Unbound)
      pass in on $lan proto { udp tcp } to 10.10.10.1 port 53 keep state
      
      # DHCP on LAN (server replies from 67 to client port 68)
      pass in on $lan proto udp from port 68 to port 67 keep state
      pass out on $lan proto udp from port 67 to port 68 keep state
      
      # NTP on LAN
      pass in on $lan proto udp to 10.10.10.1 port 123 keep state
      
      # Resolver and NSD outbound as needed
      pass out on $wan proto { udp tcp } to any port { 53, 123 } keep state
      

      Reload after editing:

      # pfctl -f /etc/pf.conf
        # Load rules atomically
      

      Verification #

      • Authoritative zone served by NSD (from a remote host or the firewall itself), using drill(1) :
      $ drill @203.0.113.53 example.com SOA
        # Expect the SOA you configured
      $ drill @203.0.113.53 www.example.com A
        # Expect 203.0.113.80
      
      • Recursive resolution with DNSSEC validation (from a LAN client):
      $ drill @10.10.10.1 cloudflare.com A
        # General recursion works
      $ drill -S @10.10.10.1 dnssec-failed.org
        # Should return a validation failure (bogus), proving DNSSEC is active
      
      • Unbound and NSD control:
      # unbound-control list_stubs
        # Confirm any stub-zones are loaded
      # nsd-control stats
        # Basic qps/counters for authoritative service
      
      • DHCP leases and client config:
      $ grep dhcp /var/log/messages | tail -n 20
        # Server activity
      $ cat /var/db/dhcpd.leases | tail -n 10
        # Lease database updates
      
      • NTP status:
      $ ntpctl -s peers
        # Upstream peers and offsets
      $ ntpctl -s status
        # Overall sync status
      

      Troubleshooting #

      • Port conflict on 53. NSD and Unbound cannot both bind the same IP:port. Bind NSD to the WAN address and Unbound to LAN addresses, or move one to 127.0.0.1@5353 and use Unbound stub-zone for the internal domain.
      • Open resolver exposed. If outside hosts can recurse, restrict Unbound with access-control to inside prefixes only and confirm PF denies WAN access to the recursive listener.
      • DNSSEC failures. Validation requires correct system time and a current trust anchor. Ensure ntpd(8) is synchronized and that Unbound has a valid auto-trust-anchor-file.
      • Authoritative zone not loading. Check nsd-control reload output and syslog for syntax errors. Validate SOA/NS correctness and serial increments.
      • DHCP not serving. Confirm rcctl set dhcpd flags <lan-ifaces> and that PF allows UDP 67/68 on the LAN. Inspect /var/db/dhcpd.leases and logs for pool exhaustion or MAC mismatches.
      • Clients ignore NTP. Ensure LAN clients point to 10.10.10.1 for NTP and that PF allows UDP 123 from LAN to the server.

      See Also #

      • Networking
      • OpenBGPD
      • Related: High Availability and State Replication
      • Related: Telemetry, Logging, and Flow Export
      • Related: IPv6 at Scale
      Report a bug
      • Synopsis
      • Design Considerations
      • Configuration
        • 1) Authoritative DNS with nsd(8)
        • 2) Validating recursive DNS with unbound(8)
        • 3) DHCP for IPv4 with dhcpd(8)
        • 4) Time service with ntpd(8)
        • 5) PF allowances for DNS, DHCP, and NTP
      • Verification
      • Troubleshooting
      • See Also