# 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](/blog/openvpn-freebsd-setup/) 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](/blog/best-vps-hosting-freebsd/) 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](https://www.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](/blog/pf-firewall-freebsd/) 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:
wg0: flags=8051 metric 0 mtu 1420
options=80000
inet 10.0.0.1 netmask 0xffffff00
groups: wg
nd6 options=109
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:
interface: wg0
public key:
private key: (hidden)
listening port: 51820
peer:
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:
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](/blog/best-vps-hosting-freebsd/).
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](/blog/hardening-freebsd-server/).
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 { 203.0.113.10, 198.51.100.20 }
pass in on $ext_if proto udp from 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](/blog/openvpn-freebsd-setup/) 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](/blog/openvpn-freebsd-setup/) for a comparison with the traditional approach, [PF firewall configuration](/blog/pf-firewall-freebsd/) for deeper firewall coverage, and [FreeBSD server hardening](/blog/hardening-freebsd-server/) for locking down the rest of your system.