FreeBSD.software
Home/Guides/How to Set Up WireGuard VPN on FreeBSD
tutorial·2026-03-29·19 min read

How to Set Up WireGuard VPN on FreeBSD

Step-by-step guide to setting up WireGuard VPN on FreeBSD. Covers kernel module installation, key generation, server and client configuration, PF firewall rules, and troubleshooting.

How to Set Up WireGuard VPN on FreeBSD

WireGuard is a modern, high-performance VPN protocol that has become the preferred choice for securing network traffic on FreeBSD servers. Unlike older solutions such as OpenVPN or IPsec, WireGuard uses a minimal codebase, modern cryptography, and a straightforward configuration model. This tutorial walks through every step of setting up a WireGuard VPN server on FreeBSD and connecting clients from Linux, macOS, Windows, iOS, and Android.

By the end of this guide you will have a fully working WireGuard VPN with NAT, PF firewall integration, DNS forwarding, and multiple peer support.

Why WireGuard on FreeBSD

Three reasons make WireGuard the right VPN choice for FreeBSD administrators.

Simplicity. A WireGuard configuration file is typically under 20 lines. Compare that to the hundreds of lines required for OpenVPN or the complexity of IPsec/IKEv2 setups. Fewer lines mean fewer misconfigurations and faster audits.

Performance. WireGuard runs inside the kernel as the if_wg module on FreeBSD. It avoids the overhead of userspace TLS processing that OpenVPN relies on. In benchmarks, WireGuard consistently delivers higher throughput and lower latency, often saturating a 1 Gbps link with minimal CPU usage.

Modern cryptography. WireGuard uses Curve25519 for key exchange, ChaCha20 for symmetric encryption, Poly1305 for authentication, BLAKE2s for hashing, and SipHash24 for hashtable keys. There are no cipher negotiations, no legacy algorithm choices, and no configuration knobs that could weaken security. The protocol either uses its fixed, audited cryptographic suite or it does not connect at all.

FreeBSD has shipped the if_wg kernel module since FreeBSD 13.0. The implementation is maintained in the base kernel tree and receives regular updates. If you are running FreeBSD 13 or later, WireGuard support is available without patching or compiling a custom kernel.

Prerequisites

Before starting, confirm you have the following:

  • FreeBSD 13.0 or later -- The if_wg kernel module is included in the base system starting with FreeBSD 13. Earlier versions require a third-party port.
  • Root access -- All commands in this guide require root privileges. Use su - or sudo as appropriate.
  • A public IP address -- Your FreeBSD server needs a publicly reachable IP (or at minimum, a port-forwarded UDP port). A FreeBSD-compatible VPS works well for this.
  • UDP port 51820 open -- WireGuard uses UDP exclusively. Ensure your hosting provider or upstream firewall permits inbound UDP on port 51820.
  • A basic understanding of networking -- You should be comfortable with IP addresses, subnets, and routing concepts.

Verify your FreeBSD version:

sh
freebsd-version

Expected output: 13.2-RELEASE or newer.

Step 1: Install WireGuard Tools

The if_wg kernel module ships with FreeBSD, but the userspace tools for key generation and interface management come from the wireguard-tools package.

sh
pkg install wireguard-tools

This installs wg (the configuration utility) and wg-quick (the interface management wrapper).

Step 2: Load the Kernel Module

Load the if_wg kernel module immediately:

sh
kldload if_wg

Verify it loaded:

sh
kldstat | grep if_wg

You should see a line containing if_wg.ko.

To load the module automatically at boot, add it to /etc/rc.conf:

sh
sysrc kld_list+="if_wg"

This appends if_wg to the kld_list variable. After a reboot, the module will load before any network services start.

Step 3: Generate Server Keys

WireGuard uses asymmetric Curve25519 key pairs. Each peer -- server and client -- needs its own private and public key.

Create the configuration directory and set restrictive permissions:

sh
mkdir -p /usr/local/etc/wireguard chmod 700 /usr/local/etc/wireguard

Generate the server key pair:

sh
wg genkey | tee /usr/local/etc/wireguard/server_private.key | wg pubkey > /usr/local/etc/wireguard/server_public.key chmod 600 /usr/local/etc/wireguard/server_private.key

View the keys (you will need them for configuration):

sh
cat /usr/local/etc/wireguard/server_private.key cat /usr/local/etc/wireguard/server_public.key

Each key is a single line of Base64-encoded text. Keep the private key secret. The public key is safe to share with clients.

Step 4: Generate Client Keys

Repeat the key generation for each client. You can do this on the server or on the client machine itself. Generating on the client is more secure because the private key never crosses the network.

On the server (for convenience):

sh
wg genkey | tee /usr/local/etc/wireguard/client1_private.key | wg pubkey > /usr/local/etc/wireguard/client1_public.key chmod 600 /usr/local/etc/wireguard/client1_private.key

If you generate keys on the client side, use the same wg genkey | wg pubkey pipeline and transfer only the public key to the server.

Step 5: Create the Server Configuration

Create the file /usr/local/etc/wireguard/wg0.conf with the following complete configuration. Replace placeholder values with your actual keys and network details.

ini
# /usr/local/etc/wireguard/wg0.conf # FreeBSD WireGuard Server Configuration [Interface] # Server private key (from server_private.key) PrivateKey = SERVER_PRIVATE_KEY_HERE # VPN subnet address for this server Address = 10.0.0.1/24 # UDP listen port ListenPort = 51820 # Optional: run commands after interface comes up / goes down PostUp = /usr/local/etc/wireguard/postup.sh PostDown = /usr/local/etc/wireguard/postdown.sh # --- Peer: Client 1 --- [Peer] # Client 1 public key (from client1_public.key) PublicKey = CLIENT1_PUBLIC_KEY_HERE # IP address assigned to this client inside the VPN AllowedIPs = 10.0.0.2/32 # Optional: keep NAT mappings alive (useful if client is behind NAT) # PersistentKeepalive = 25

Key points:

  • Address = 10.0.0.1/24 assigns the server the first address in the VPN subnet.
  • ListenPort = 51820 is the standard WireGuard port. Change it if you need to avoid detection or conflict.
  • Each [Peer] block defines one client. AllowedIPs restricts which source IPs are accepted from that peer.
  • PersistentKeepalive is only needed when a client sits behind NAT and needs to keep the connection alive. The server typically does not need it.

Step 6: Create the Client Configuration

Each client needs its own wg0.conf. Below is a complete client configuration that routes all traffic through the VPN (full tunnel).

ini
# Client WireGuard Configuration (wg0.conf) [Interface] # Client private key PrivateKey = CLIENT1_PRIVATE_KEY_HERE # VPN IP address for this client Address = 10.0.0.2/24 # DNS server to use through the VPN DNS = 10.0.0.1 [Peer] # Server public key PublicKey = SERVER_PUBLIC_KEY_HERE # Route all traffic through VPN (full tunnel) AllowedIPs = 0.0.0.0/0, ::/0 # Server public IP and port Endpoint = YOUR_SERVER_PUBLIC_IP:51820 # Keep connection alive behind NAT PersistentKeepalive = 25

If you only want to route VPN subnet traffic through the tunnel (split tunnel), change AllowedIPs to:

ini
AllowedIPs = 10.0.0.0/24

Client Setup by Platform

Linux:

sh
sudo apt install wireguard # Debian/Ubuntu sudo cp wg0.conf /etc/wireguard/wg0.conf sudo wg-quick up wg0

macOS:

Install the WireGuard app from the Mac App Store or use Homebrew:

sh
brew install wireguard-tools sudo wg-quick up /path/to/wg0.conf

Alternatively, import the configuration file into the WireGuard macOS GUI app.

Windows:

Download the WireGuard installer from wireguard.com/install. Open the application, click "Import tunnel(s) from file", select your wg0.conf, and click "Activate".

iOS and Android:

Install the WireGuard app from the App Store or Google Play. You can either import a .conf file or scan a QR code. To generate a QR code from the server:

sh
pkg install libqrencode qrencode -t ansiutf8 < /usr/local/etc/wireguard/client1.conf

This prints a scannable QR code directly in your terminal.

Step 7: Configure PF Firewall Rules

FreeBSD's PF firewall needs rules to allow WireGuard traffic and (for full-tunnel VPNs) NAT the outbound traffic. If you are not yet familiar with PF, read the PF firewall guide first.

Add the following to /etc/pf.conf:

pf
# /etc/pf.conf -- WireGuard additions # Define interfaces and networks ext_if = "vtnet0" # Your external interface (check with ifconfig) wg_if = "wg0" # WireGuard interface wg_net = "10.0.0.0/24" # WireGuard VPN subnet # NAT: masquerade VPN traffic going out the external interface nat on $ext_if from $wg_net to any -> ($ext_if) # Allow WireGuard UDP port pass in on $ext_if proto udp from any to any port 51820 # Allow all traffic on the WireGuard interface pass in on $wg_if from $wg_net to any pass out on $wg_if from any to $wg_net # Allow forwarded traffic from VPN to external pass out on $ext_if from $wg_net to any # Allow established connections back pass in on $ext_if from any to $wg_net flags any

Check your external interface name with ifconfig. Common names on FreeBSD VPS providers include vtnet0, em0, igb0, or bge0.

Verify the PF configuration syntax:

sh
pfctl -n -f /etc/pf.conf

If no errors appear, reload the ruleset:

sh
pfctl -f /etc/pf.conf

Enable PF if it is not already running:

sh
sysrc pf_enable="YES" sysrc pflog_enable="YES" service pf start

Step 8: Enable IP Forwarding and NAT

For the server to route traffic between the WireGuard interface and the external network, IP forwarding must be enabled.

Enable it immediately:

sh
sysctl net.inet.ip.forwarding=1

For IPv6 (if needed):

sh
sysctl net.inet6.ip6.forwarding=1

Make it persistent across reboots by adding to /etc/rc.conf:

sh
sysrc gateway_enable="YES" sysrc ipv6_gateway_enable="YES"

The gateway_enable directive sets net.inet.ip.forwarding=1 at boot time. This is the FreeBSD-specific equivalent of Linux's net.ipv4.ip_forward.

Step 9: Start and Enable WireGuard

Start the WireGuard interface:

sh
wg-quick up wg0

You should see output indicating the interface was created and configured. Verify the interface exists:

sh
ifconfig wg0

Expected output:

shell
wg0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1420 options=80000<LINKSTATE> inet 10.0.0.1 netmask 0xffffff00 groups: wg nd6 options=109<PERFORMNUD,IFDISABLED,NO_DAD>

To start WireGuard automatically at boot, add to /etc/rc.conf:

sh
sysrc wireguard_enable="YES" sysrc wireguard_interfaces="wg0"

Here is what your complete /etc/rc.conf additions should look like:

sh
# /etc/rc.conf -- WireGuard related entries kld_list="if_wg" gateway_enable="YES" ipv6_gateway_enable="YES" pf_enable="YES" pflog_enable="YES" wireguard_enable="YES" wireguard_interfaces="wg0"

To stop the interface:

sh
wg-quick down wg0

To restart after configuration changes:

sh
wg-quick down wg0 && wg-quick up wg0

Step 10: Test the Connection

On the server

Check WireGuard status:

sh
wg show

Sample output:

shell
interface: wg0 public key: <server_public_key> private key: (hidden) listening port: 51820 peer: <client1_public_key> allowed ips: 10.0.0.2/32 latest handshake: 23 seconds ago transfer: 1.24 MiB received, 3.87 MiB sent

The latest handshake field confirms the client connected successfully. If this field is missing, the client has not yet initiated a connection.

On the client

After running wg-quick up wg0 on the client:

sh
# Ping the server's VPN address ping 10.0.0.1 # Verify your public IP changed (full tunnel only) curl ifconfig.me # Trace the route traceroute 1.1.1.1

If the ping succeeds and curl ifconfig.me returns your FreeBSD server's public IP, the full tunnel is working correctly.

Step 11: Adding Multiple Peers

To add more clients, generate a new key pair for each and append a [Peer] block to the server's wg0.conf.

Generate keys for a second client:

sh
wg genkey | tee /usr/local/etc/wireguard/client2_private.key | wg pubkey > /usr/local/etc/wireguard/client2_public.key chmod 600 /usr/local/etc/wireguard/client2_private.key

Add the peer to the server configuration:

ini
# --- Peer: Client 2 --- [Peer] PublicKey = CLIENT2_PUBLIC_KEY_HERE AllowedIPs = 10.0.0.3/32

Create the client configuration for client 2:

ini
# Client 2 WireGuard Configuration [Interface] PrivateKey = CLIENT2_PRIVATE_KEY_HERE Address = 10.0.0.3/24 DNS = 10.0.0.1 [Peer] PublicKey = SERVER_PUBLIC_KEY_HERE AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = YOUR_SERVER_PUBLIC_IP:51820 PersistentKeepalive = 25

Assign each client a unique IP address within the 10.0.0.0/24 subnet. With a /24 subnet you can support up to 253 peers (10.0.0.2 through 10.0.0.254).

Reload the server without dropping existing connections:

sh
wg syncconf wg0 <(wg-quick strip wg0)

This applies configuration changes without restarting the interface, so existing peer sessions remain active.

Step 12: DNS Configuration

If clients use the VPN as a full tunnel, they need DNS resolution through the VPN. You have three options.

Option A: Use a Public DNS Resolver

Set DNS = 1.1.1.1, 9.9.9.9 in the client configuration. This is the simplest approach but means DNS queries exit the VPN tunnel at the server and go to a third-party resolver.

Option B: Run a Local DNS Resolver on the Server

Install Unbound on your FreeBSD server:

sh
pkg install unbound

Configure /usr/local/etc/unbound/unbound.conf:

yaml
server: interface: 10.0.0.1 interface: 127.0.0.1 access-control: 10.0.0.0/24 allow access-control: 127.0.0.0/8 allow hide-identity: yes hide-version: yes do-not-query-localhost: no forward-zone: name: "." forward-addr: 1.1.1.1 forward-addr: 9.9.9.9

Enable and start Unbound:

sh
sysrc unbound_enable="YES" service unbound start

Set DNS = 10.0.0.1 in client configurations. Now all DNS queries from VPN clients are resolved by your own server, preventing DNS leaks.

Option C: Use DNS Over TLS

Configure Unbound to forward queries over TLS for additional privacy:

yaml
server: interface: 10.0.0.1 interface: 127.0.0.1 access-control: 10.0.0.0/24 allow access-control: 127.0.0.0/8 allow tls-cert-bundle: /etc/ssl/cert.pem forward-zone: name: "." forward-tls-upstream: yes forward-addr: 1.1.1.1@853#cloudflare-dns.com forward-addr: 9.9.9.9@853#dns.quad9.net

This encrypts DNS queries between your server and the upstream resolver.

Step 13: Performance Tuning

WireGuard is fast by default, but a few tweaks can extract more throughput on FreeBSD.

MTU Optimization

The default MTU for WireGuard is 1420. If your external interface has a standard 1500-byte MTU, 1420 is correct (1500 minus 80 bytes of WireGuard overhead). If your provider uses jumbo frames or a non-standard MTU, adjust accordingly:

ini
[Interface] MTU = 1420

For providers with a 1500-byte path MTU, do not increase this value. If you experience fragmentation issues (large transfers stalling), try lowering it to 1400 or 1380.

Increase Socket Buffer Sizes

For high-throughput scenarios, increase the UDP socket buffers:

sh
sysctl net.inet.udp.recvspace=4194304 sysctl net.inet.udp.maxdgram=4194304

Make them persistent in /etc/sysctl.conf:

shell
net.inet.udp.recvspace=4194304 net.inet.udp.maxdgram=4194304

CPU Affinity

On multi-core servers handling many peers, you can pin the WireGuard processing to specific CPU cores using cpuset. This avoids cache thrashing:

sh
cpuset -l 0-1 -p $(pgrep -f wg0)

Disable TSO/LRO on the External Interface

If you observe poor throughput or packet drops, disabling TCP segmentation offload and large receive offload on the external interface can help:

sh
ifconfig vtnet0 -tso -lro

Make it persistent in /etc/rc.conf:

sh
ifconfig_vtnet0="inet YOUR_IP netmask YOUR_MASK -tso -lro"

Step 14: Troubleshooting Common Issues

Problem: Client cannot reach the server

  1. Check UDP port. Confirm port 51820 is open. From another machine: nc -zu YOUR_SERVER_IP 51820. If it times out, check your PF rules or hosting provider firewall.
  2. Check the kernel module. Run kldstat | grep if_wg. If missing, run kldload if_wg.
  3. Check the interface. Run ifconfig wg0. If the interface does not exist, run wg-quick up wg0.

Problem: Handshake does not complete

  1. Verify keys. The most common cause is mismatched keys. Confirm the server's [Peer] PublicKey matches the client's actual public key, and vice versa.
  2. Check AllowedIPs. The server's AllowedIPs for the peer must include the client's assigned IP (e.g., 10.0.0.2/32).
  3. Check Endpoint. The client config must have the correct server IP and port in the Endpoint field.
  4. Clock skew. WireGuard uses timestamps for replay protection. If the server or client clock is significantly off, handshakes can fail. Install and enable NTP: sysrc ntpd_enable="YES" && service ntpd start.

Problem: Handshake completes but no traffic flows

  1. IP forwarding. Verify sysctl net.inet.ip.forwarding returns 1.
  2. NAT rule. Confirm PF is running (pfctl -s info) and the NAT rule is active (pfctl -s nat).
  3. Routing. On the server, check that packets from 10.0.0.0/24 are being NATed. Run tcpdump -i wg0 -n to see if packets arrive on the WireGuard interface.

Problem: DNS is not working through the VPN

  1. Check the DNS setting. The client wg0.conf must have a DNS line.
  2. Check the resolver. If using DNS = 10.0.0.1, Unbound must be running and listening on that address.
  3. Test directly. From the client: dig @10.0.0.1 example.com. If this works but normal resolution fails, the client's DNS override is not being applied. On Linux, check /etc/resolv.conf. On macOS, check scutil --dns.

Problem: Slow performance

  1. Check MTU. Run ping -D -s 1392 10.0.0.1 from the client. If packets are lost, reduce the MTU in your WireGuard config.
  2. Check CPU. Run top on the server. If a single core is at 100%, WireGuard processing is CPU-bound. Consider a server with faster single-core performance.
  3. Check the host. On virtualized environments, network performance can vary. Consider a dedicated server or a higher-tier VPS plan.

Problem: Connection drops after some time

  1. PersistentKeepalive. If the client is behind NAT, ensure PersistentKeepalive = 25 is set in the client's [Peer] block.
  2. Firewall state timeout. PF's default UDP state timeout may be too short. Add to /etc/pf.conf: set timeout { udp.first 120, udp.multiple 120, udp.single 60 }.

Step 15: Server Hardening

A VPN server is a high-value target. Apply these additional measures. For a comprehensive approach, follow the FreeBSD server hardening guide.

Restrict SSH to the VPN

Once WireGuard is working, restrict SSH access to the VPN subnet:

pf
pass in on $wg_if proto tcp from $wg_net to any port 22 block in on $ext_if proto tcp from any to any port 22

Log blocked traffic

Enable PF logging for dropped packets:

pf
block log all

View the log with:

sh
tcpdump -n -e -ttt -r /var/log/pflog

Restrict WireGuard access by source IP

If your clients have known IPs, restrict UDP 51820 to those addresses:

pf
table <wg_clients> { 203.0.113.10, 198.51.100.20 } pass in on $ext_if proto udp from <wg_clients> to any port 51820

PostUp and PostDown Scripts

Earlier the server configuration referenced postup.sh and postdown.sh. These scripts handle firewall rules dynamically so you do not need to manually edit /etc/pf.conf every time WireGuard starts or stops.

Create /usr/local/etc/wireguard/postup.sh:

sh
#!/bin/sh # Enable IP forwarding sysctl net.inet.ip.forwarding=1 # Reload PF rules pfctl -f /etc/pf.conf

Create /usr/local/etc/wireguard/postdown.sh:

sh
#!/bin/sh # Reload PF rules (NAT will be removed since wg0 is down) pfctl -f /etc/pf.conf

Make them executable:

sh
chmod 700 /usr/local/etc/wireguard/postup.sh chmod 700 /usr/local/etc/wireguard/postdown.sh

If you prefer to keep your PF rules static (as shown in Step 7), you can remove the PostUp and PostDown lines from wg0.conf.

Complete Configuration Reference

Below are the final, complete configuration files for reference.

Server: /usr/local/etc/wireguard/wg0.conf

ini
[Interface] PrivateKey = aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuU= Address = 10.0.0.1/24 ListenPort = 51820 PostUp = /usr/local/etc/wireguard/postup.sh PostDown = /usr/local/etc/wireguard/postdown.sh [Peer] # Client 1 - Laptop PublicKey = xXyYzZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrR= AllowedIPs = 10.0.0.2/32 [Peer] # Client 2 - Phone PublicKey = wWxXyYzZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQ= AllowedIPs = 10.0.0.3/32 [Peer] # Client 3 - Remote office PublicKey = vVwWxXyYzZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpP= AllowedIPs = 10.0.0.4/32

Client: wg0.conf (full tunnel)

ini
[Interface] PrivateKey = uUvVwWxXyYzZaAbBcCdDeEfFgGhHiIjJkKlLmMnNoO= Address = 10.0.0.2/24 DNS = 10.0.0.1 [Peer] PublicKey = pPqQrRsStTuUvVwWxXyYzZaAbBcCdDeEfFgGhHiIjJ= AllowedIPs = 0.0.0.0/0, ::/0 Endpoint = 203.0.113.1:51820 PersistentKeepalive = 25

Server: /etc/rc.conf additions

sh
kld_list="if_wg" gateway_enable="YES" ipv6_gateway_enable="YES" pf_enable="YES" pflog_enable="YES" wireguard_enable="YES" wireguard_interfaces="wg0" unbound_enable="YES" ntpd_enable="YES"

Server: /etc/pf.conf

pf
ext_if = "vtnet0" wg_if = "wg0" wg_net = "10.0.0.0/24" set skip on lo0 set timeout { udp.first 120, udp.multiple 120, udp.single 60 } scrub in all nat on $ext_if from $wg_net to any -> ($ext_if) block all # Allow SSH (restrict to VPN for better security) pass in on $ext_if proto tcp from any to any port 22 pass in on $wg_if proto tcp from $wg_net to any port 22 # Allow WireGuard pass in on $ext_if proto udp from any to any port 51820 # Allow all VPN traffic pass in on $wg_if from $wg_net to any pass out on $wg_if from any to $wg_net # Allow VPN traffic out to the internet pass out on $ext_if from $wg_net to any # Allow outbound from server itself pass out on $ext_if from ($ext_if) to any # Allow DNS to Unbound pass in on $wg_if proto { tcp, udp } from $wg_net to 10.0.0.1 port 53

Frequently Asked Questions

Is WireGuard included in the FreeBSD base system?

The if_wg kernel module is included in the FreeBSD kernel source tree starting with FreeBSD 13.0 and is available as a loadable module on all supported releases from 13.0 onward. The userspace tools (wg, wg-quick) are installed separately via pkg install wireguard-tools.

Can I run WireGuard and OpenVPN on the same server?

Yes. They use different interfaces and different ports. WireGuard uses UDP 51820 by default and creates a wg0 interface, while OpenVPN typically uses UDP 1194 and creates a tun0 interface. Assign them non-overlapping VPN subnets (e.g., 10.0.0.0/24 for WireGuard and 10.0.1.0/24 for OpenVPN) and add appropriate PF rules for both.

How do I revoke a client's access?

Remove the client's [Peer] block from the server's wg0.conf and reload:

sh
wg syncconf wg0 <(wg-quick strip wg0)

There is no certificate revocation list as in OpenVPN. Removing the peer's public key from the server configuration is sufficient. The client will no longer be able to complete a handshake.

Does WireGuard support IPv6?

Yes. WireGuard natively supports IPv6. Add an IPv6 address to the [Interface] section:

ini
[Interface] Address = 10.0.0.1/24, fd00:wg::1/64

Add IPv6 to peer AllowedIPs:

ini
[Peer] AllowedIPs = 10.0.0.2/32, fd00:wg::2/128

On the client, include ::/0 in AllowedIPs to route all IPv6 traffic through the VPN.

How many clients can a single FreeBSD WireGuard server support?

There is no hard protocol limit. Practical limits depend on CPU speed, network bandwidth, and available memory. A modern 4-core VPS can handle hundreds of peers with light traffic. For high-throughput deployments with many simultaneous clients, monitor CPU usage and consider scaling horizontally with multiple servers.

Can I use WireGuard for site-to-site VPN between FreeBSD servers?

Yes. Configure each server as a peer of the other. Set AllowedIPs to include the remote site's LAN subnet. For example, if Site A has 192.168.1.0/24 behind it and Site B has 192.168.2.0/24, configure Site A's peer block for Site B with AllowedIPs = 10.0.0.2/32, 192.168.2.0/24 and vice versa. Enable IP forwarding on both sides and add routes for the remote subnets.

How do I update WireGuard on FreeBSD?

Update the userspace tools via pkg:

sh
pkg upgrade wireguard-tools

The kernel module updates with FreeBSD system updates (freebsd-update fetch install). After a kernel update, reboot to load the new module.

Summary

WireGuard on FreeBSD gives you a fast, auditable, and simple VPN with kernel-level performance. The setup consists of five essential steps: install the tools, load the kernel module, generate keys, write the configuration, and open the firewall. Everything after that -- DNS, multi-peer, performance tuning -- builds on the same straightforward foundation.

For related guides, see OpenVPN on FreeBSD for a comparison with the traditional approach, PF firewall configuration for deeper firewall coverage, and FreeBSD server hardening for locking down the rest of your system.

Get more FreeBSD guides

Weekly tutorials, security advisories, and package updates. No spam.