OpenBSD Handbook

    Theme
    • Part I. Install & Configure
      • Introduction to OpenBSD
      • Installing OpenBSD
      • The X Window System
      • Networking on OpenBSD
      • System Configuration
      • OpenBSD Basics
      • Managing Software: Packages and Ports
    • Part II. Daily Operations
      • OpenBSD Desktop Environments
      • Multimedia
      • Printing
      • Linux Compatibility
      • Windows Compatibility
      • Games
    • Part III. System Administration
      • Virtualization
      • Storage and File Systems
      • Updating and Upgrading
      • Localization
      • The OpenBSD Boot Process
      • Security
    • Part IV. Networking & Daemons
      • Services
        • Database
          • MariaDB
          • PostgreSQL
          • Redis
          • memcached
        • Directory
          • YP (NIS)
          • LDAP
        • File
          • NFS
          • Samba
        • FTP Services
          • ftpd
          • ProFTPD
          • vsftpd
          • Pure-FTPd
          • TFTP
        • Mail
          • OpenSMTPD Mail Server
          • Dovecot IMAP and POP3 Server
          • Postfix
          • Exim
          • Rspamd
        • Name
          • Unbound DNS Resolver
          • NSD Authoritative DNS Server
          • BIND DNS Server
        • Networking
          • OpenBGPD
          • rad
          • DHCP on OpenBSD
          • slaacd
        • Web
          • Apache HTTP Server
          • nginx Web Server
          • OpenBSD httpd Web Server
          • relayd Reverse Proxy
        • Logging
          • syslogd
        • Monitoring
          • SNMP
        • Remote Access
          • Audit OpenSSH
          • OpenSSH Server
        • File Synchronization
          • rsync
        • Messaging
          • RabbitMQ Message Broker
        • Time
          • NTP Time Synchronization
        • Load Balancing
          • Build Redundant Load-Balanced Gateways
          • Load Balance Services with NAT or DSR
          • Build an HA HTTPS Load Balancer
      • 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
      • How-to Guides
        • Installation and First Boot
          • Complete the First 30 Minutes After Installation
          • Check Hardware Compatibility Before Installing
          • Bootstrap Wi-Fi Firmware Without Ethernet
          • Run OpenBSD as a Virtual Machine Guest
          • Install OpenBSD on a VPS or Cloud Server
          • Install OpenBSD with Full-Disk Encryption
          • Dual-Boot OpenBSD on a UEFI System
          • Automate OpenBSD Installation with Autoinstall and PXE
        • Desktops and Laptops
          • Set Up OpenBSD as a Laptop or Workstation
          • Configure Laptop Power Management
          • Troubleshoot Web Browsers on OpenBSD
          • Use Plan 9 Tools on OpenBSD
          • Build a Minimal Desktop with suckless Tools
          • Install Z shell (zsh)
        • Networking and Remote Access
          • Create an Administrator Account with doas and SSH Keys
          • Configure Secure Remote Access with OpenSSH
          • Use FIDO Security Keys with OpenSSH
          • Configure Wi-Fi Roaming and Wired Failover
          • Troubleshoot PF, NAT, and Routing
          • Route Multiple VLANs with PF
          • Configure a Road-Warrior WireGuard VPN
          • Route IPv6 Networks without NAT
          • Test OpenBGPD with Two Providers
          • Unlock an Internal Service with an SSH Key
          • Build a Simple Router and Firewall
        • Servers and Services
          • Set Up WordPress on OpenBSD
          • Build a Mail Server with OpenSMTPD, Dovecot, and Rspamd
        • Security, Maintenance and Recovery
          • Establish a Conservative Security Baseline
          • Recover Access and Reset the Root Password
          • Collect OpenBSD Diagnostic Evidence
          • Troubleshoot Package Installation and Update Failures
          • Schedule Recurring Maintenance
          • Monitor an OpenBSD System
          • Back Up and Restore an OpenBSD System
          • Configure softraid RAID1
          • Recover an OpenBSD System That Does Not Boot
        • Building, Automation and Virtual Machines
          • Manage OpenBSD with Ansible
          • Build Third-Party Software from Ports
          • Build OpenBSD from Source
          • Plan a Docker Workload Migration to OpenBSD
      • OpenBSD for Linux Users
      • OpenBSD for FreeBSD Users
      • OpenBSD for macOS Users
    • OpenBSD FAQ
    • OpenBSD Architectures
      • OpenBSD Package Search
        • Synopsis
        • Example Topology
        • Generate and Protect Keys
        • Configure the Gateway
        • Configure the Gateway Firewall
        • Configure the Roaming Client
        • Verify the Tunnel
        • Add More Clients

        Configure a Road-Warrior WireGuard VPN

        Synopsis #

        OpenBSD includes the wg(4) WireGuard interface in the base system. This guide configures one OpenBSD gateway and one roaming OpenBSD client with a split tunnel. The client reaches a private LAN through the VPN while ordinary Internet traffic continues to use the client’s existing connection.

        WireGuard authenticates peers with public keys. It does not assign addresses, distribute routes, provide user authentication, or create firewall policy. Configure those functions separately.

        Example Topology #

        The example uses:

        RoleValue
        Gateway public address198.51.100.10
        Gateway UDP port51820
        VPN network10.64.0.0/24
        Gateway VPN address10.64.0.1
        Client VPN address10.64.0.2
        Private LAN behind gateway10.10.10.0/24
        Gateway LAN interfaceem1

        The public address is from a documentation-only range. Replace it with the gateway’s actual stable address or name.

        Private LAN through a split tunnelA roaming client uses wg0 at 10.64.0.2. Private LAN traffic crosses an encrypted UDP tunnel to the gateway at public endpoint 198.51.100.10 port 51820, whose wg0 address is 10.64.0.1. The gateway forwards it through em1 to 10.10.10.0/24. Other Internet traffic keeps using the client's existing connection. Replies from the LAN require a route to 10.64.0.0/24 through the gateway; constrained source NAT is an alternative only when that route cannot be supplied.Private LAN through a split tunnelRoaming OpenBSD clientwg0: 10.64.0.2InternettrafficEncryptedtunnelUDP 51820InternetExisting clientconnectionOpenBSD gateway198.51.100.10:51820wg0: 10.64.0.1LAN interface: em1Forwardingand PF policyPrivate LAN10.10.10.0/24Replies need a route back to 10.64.0.0/24through this gateway. Use source NAT only whena return route cannot be provided.
        Private LAN traffic uses the tunnel; other Internet traffic keeps the client's existing path. The LAN also needs a return route to the VPN network through the OpenBSD gateway.

        Generate and Protect Keys #

        On each peer, create a root-only directory and generate a 32-byte private key with openssl(1) :

        doas sh <<'ROOT'
        install -d -o root -g wheel -m 700 /etc/wireguard
          # Create a protected key directory
        openssl rand -base64 32 > /etc/wireguard/wg0.key
          # Generate this peer's private key
        chmod 600 /etc/wireguard/wg0.key
          # Prevent other accounts from reading it
        ROOT
        

        Never copy a private key to the other peer.

        To obtain the corresponding public key, create a temporary interface with ifconfig(8) , load the private key, record the wgpubkey value, and destroy the interface:

        doas ifconfig wg0 create
          # Create a temporary WireGuard interface
        doas sh -c 'ifconfig wg0 wgkey "$(cat /etc/wireguard/wg0.key)"'
          # Load the local private key
        doas ifconfig wg0 | grep wgpubkey
          # Record only the displayed public key
        doas ifconfig wg0 destroy
          # Remove the temporary interface
        

        Exchange the public keys through an authenticated channel. Record which device owns each key.

        Configure the Gateway #

        Create a protected /etc/hostname.wg0 on the gateway before inserting any key material. The first command preserves an existing configuration so the settings can be merged instead of replaced:

        doas test -e /etc/hostname.wg0 || doas install -o root -g wheel -m 600 /dev/null /etc/hostname.wg0
          # Create the empty file only when it does not already exist
        doas chown root:wheel /etc/hostname.wg0
        doas chmod 600 /etc/hostname.wg0
          # Enforce restrictive ownership and permissions before editing
        doas vi /etc/hostname.wg0
          # Insert the configuration shown below
        

        Replace CLIENT_PUBLIC_KEY with the client’s public key and replace SERVER_PRIVATE_KEY with the contents of the gateway key file:

        wgport 51820
        wgkey SERVER_PRIVATE_KEY
        wgpeer CLIENT_PUBLIC_KEY wgaip 10.64.0.2/32
        inet 10.64.0.1 255.255.255.0
        up
        

        Confirm the ownership and permissions after editing because the file contains a private key:

        doas chown root:wheel /etc/hostname.wg0
        doas chmod 600 /etc/hostname.wg0
        

        Enable forwarding persistently in /etc/sysctl.conf:

        net.inet.ip.forwarding=1
        

        Apply the interface and forwarding setting:

        doas sh /etc/netstart wg0
          # Create and configure the persistent interface
        doas sysctl net.inet.ip.forwarding=1
          # Permit routed IPv4 traffic immediately
        

        Configure the Gateway Firewall #

        Integrate the following fragment into the complete /etc/pf.conf:

        wan = "em0"
        lan = "em1"
        vpn_if = "wg0"
        
        vpn_net = "10.64.0.0/24"
        lan_net = "10.10.10.0/24"
        
        pass in on $wan proto udp to ($wan) port 51820
        pass in on $vpn_if inet proto icmp from $vpn_net to ($vpn_if) icmp-type echoreq
        pass in on $vpn_if from $vpn_net to $lan_net
        pass out on $lan from $vpn_net to $lan_net
        

        The LAN must have a route back to 10.64.0.0/24 through the OpenBSD gateway. If the OpenBSD host is not the LAN’s default router and the LAN router cannot accept that route, source NAT can provide a constrained fallback:

        match out on $lan from $vpn_net to $lan_net nat-to ($lan)
        

        Do not add this translation when normal routing can provide the return path. Parse and load the complete policy, then retain the recovery session:

        doas pfctl -nf /etc/pf.conf
          # Check syntax without changing the active policy
        doas pfctl -f /etc/pf.conf
          # Load the verified ruleset
        

        Configure the Roaming Client #

        Create a protected /etc/hostname.wg0 on the client before inserting any key material. The first command preserves an existing configuration so the settings can be merged instead of replaced:

        doas test -e /etc/hostname.wg0 || doas install -o root -g wheel -m 600 /dev/null /etc/hostname.wg0
          # Create the empty file only when it does not already exist
        doas chown root:wheel /etc/hostname.wg0
        doas chmod 600 /etc/hostname.wg0
          # Enforce restrictive ownership and permissions before editing
        doas vi /etc/hostname.wg0
          # Insert the configuration shown below
        

        Replace CLIENT_PRIVATE_KEY with the client private key and SERVER_PUBLIC_KEY with the gateway public key:

        wgkey CLIENT_PRIVATE_KEY
        wgpeer SERVER_PUBLIC_KEY wgendpoint 198.51.100.10 51820 wgaip 10.64.0.0/24 wgaip 10.10.10.0/24 wgpka 25
        inet 10.64.0.2 255.255.255.0
        !/sbin/route add -inet 10.10.10.0/24 10.64.0.1
        up
        

        The persistent keepalive is useful when the client is behind stateful NAT. The explicit route sends only the private LAN through the tunnel. Restrict and apply the file:

        doas chown root:wheel /etc/hostname.wg0
          # Preserve administrative ownership
        doas chmod 600 /etc/hostname.wg0
          # Protect the private key
        doas sh /etc/netstart wg0
          # Create the tunnel and private-LAN route
        

        Verify the Tunnel #

        On both peers, inspect the interface:

        doas ifconfig wg0
        

        The status shows public keys, allowed IP ranges, endpoints, and recent handshakes. WireGuard is connectionless; no permanent connected state exists.

        From the client, generate tunnel traffic and confirm routing:

        route -n get 10.10.10.1
          # Confirm that the private LAN route uses wg0
        ping 10.64.0.1
          # Test the WireGuard gateway address
        ping 10.10.10.1
          # Test the private LAN path
        

        On the gateway, observe encrypted and decrypted traffic with tcpdump(8) :

        doas tcpdump -ni em0 udp port 51820
          # Observe encrypted WireGuard datagrams
        doas tcpdump -ni wg0
          # Observe decrypted tunnel traffic
        

        If no handshake appears, compare both public keys, the gateway endpoint, UDP reachability, and PF counters. If the handshake succeeds but LAN traffic fails, inspect forwarding, the client’s route, the gateway’s PF policy, and the LAN return path.

        Add More Clients #

        Generate a unique key pair and VPN address for every client. Add one wgpeer line per public key on the gateway, with a unique /32 allowed IP. Do not reuse a private key or VPN address.

        Remove the peer from /etc/hostname.wg0 and also remove it from the running interface with doas ifconfig wg0 -wgpeer PUBLIC_KEY, substituting that peer’s public key. Reapplying the hostname file alone does not flush peers that were removed from the file. Key rotation requires installing the replacement public key on the opposite peer before removing the old key.

        A full-tunnel VPN needs a route to the gateway outside the tunnel, new default routes, DNS configuration, Internet forwarding, and a policy for traffic when the tunnel fails. This guide configures only a split tunnel.

        See VPN and Cryptographic Tunneling for protocol selection and site-to-site designs.

        Report a bug
        • Synopsis
        • Example Topology
        • Generate and Protect Keys
        • Configure the Gateway
        • Configure the Gateway Firewall
        • Configure the Roaming Client
        • Verify the Tunnel
        • Add More Clients