Simple and Secure VPN in FreeBSD – Introducing WireGuard

Not open for further replies.


Staff member

Reaction score: 352
Messages: 1,787

Simple and Secure VPN in FreeBSD – Introducing WireGuard

Wireguard is a Virtual Private Network (VPN) technology that aims to enable the easy deployment and configuration of servers and clients. Wireguard is intended to replace the use of IPSec or OpenVPN for many VPN applications.

Wireguard offers both kernel and userspace implementations. The first kernel implementation was offered for Linux, but there are now in-kernel implementations for FreeBSD and OpenBSD, with a NetBSD implementation in progress. The main userspace implementation of wireguard is a go application and it is available via the wireguard-go tool.

A wireguard network configuration is formed of interfaces and peers. Each interface is defined by its private key, peers are defined by their public key and the set of addresses they are allowed to use. This forms a network that uses cryptokey routing.


With cryptokey routing, hosts are defined by their cryptographic keys, where public keys are used as identifiers. This means that most of the configuration for wireguard requires dealing with these public keys. Wireguard does not specify mechanisms for key distribution and management, and it is fair to compare this with the distribution of ssh keys. Unlike ssh, the public keys need to be available for both sides of the connection.

Wireguard uses INI files for static configuration, a config file might look like:

PrivateKey = EO9b4bV1qg0veyFelERv69v4eamKu/evGZ/Hbiq82Eg=
ListenPort = 51820

PublicKey = ZBF6vo22sBkT0ro/xnlZ0EZsFGTYPTOvY8luFFtJwwk=    
AllowedIPs =

PublicKey = fnpAo3hMf9UOtPpM+CUtpB+ETvB5XjNLIuTGsX+qnCc
AllowedIPs =

PublicKey = Tpdhd68aaDAQfNsnqzoViMl1h+yHeYuVZ22xrY1BM0I=
AllowedIPs =,

The allowed IP addresses act as a routing table. When traffic is sent, the packet is compared to the peer’s AllowedIPs, and if it matches the address or the subnet, the packet is encrypted using the peer’s public key and forwarded over the tunnel in its direction.

On receipt of a packet, AllowedIPs acts as a receive filter. The packet is first decrypted and authenticated, and then it is evaluated against the AllowedIPs to verify that it is allowed to enter the network.


Wireguard makes a serious effort to make the tools it offers consistent on all platforms. To do this, there are two components that determine whether the kernel or userspace implementation is used. The first is a method to create a wireguard interface; this is the component that changes between kernel and userspace versions, then there is a single configuration wg(8) tool to manage the VPN.

This approach intends to smooth over platform configuration differences. The platform specific part is creating and configuring the network interface, but the VPN configuration should all be manageable with a single consistent tool.

FreeBSD has an in-kernel implementation of wireguard that landed in the tree in November 2020. The in-kernel implementation is still very fresh, so in this article we will use the userspace tool, as it is available in the most supported versions of FreeBSD today. The in-kernel implementation key generation is still performed using the wg(8) tool.

We can install wg(8) and wireguard-go (the wireguard userspace implementation) from ports with:

# pkg install wireguard wireguard-tools

Creating Keys

Our host’s identity is defined by its key pair. We need to generate a wireguard private key using the wg genkey command; wireguard helpfully (if frustratingly) insists that we don’t place the private key in a world readable file. To generate the key and append it to a file, we need to temporally change our umask:

# (umask 0077; wg genkey > private.key)
# cat private.key

This command creates a private key and places it into a file called private.key. We can view the private key by catting the file; anyone with the private key can act as the server, so you should be very careful with your wireguard private keys. Wireguard tools will typically censor the private key in output unless additional flags are given.

Did you know?

You can maximize the power of your FreeBSD infrastructure with our Support Subscription!

Check it out!

Wireguard uses our public key as our identity, wg(8) will show us the public key for a private key with the wg pubkeycommand:

# wg pubkey < private.key

Server Side

First, let’s configure our server side. The server is going to act as the central host in our network with several clients connecting to it for access. This host needs to have a stable public IP and a known shared port to accept wireguard traffic on.

We need to generate keys for the server:

wg-server (umask 0077; wg genkey > server.key)
wg-server # cat server.key
wg-server # wg pubkey < server.key      # show the public key for this private key

Next, there are a few steps to set up the network. We need to create a wireguard interface using wireguard-go:

wg-server # wireguard-go wg0
INFO: (wg0) 2021/01/08 14:35:42 Starting wireguard-go version 0.0.20200320

This interface needs to be configured to have an address in our private network on the host and a route to direct traffic into the wireguard tunnel. We use the wg(8) tool to configure the interface’s private key.

wg-server # ifconfig wg0 inet
wg-client # route add -interface wg0
wg-server # wg set wg0 private-key ./server.key listen-port 51820

If we don’t specify the listen port here, then wireguard will select a random port when the wg0 interface is brought up. For a destination server like this, we need to have a consistent port between addresses, and so we use one common to wireguard here.

wg-server # ifconfig wg0 down
wg-server # ifconfig wg0 up

Finally, we need to configure each peer with its public key and the IP addresses it is allowed to use:

wg-server # wg set wg0 peer <peer pubkey> allowed-ips

FreeBSD Client

Each client needs to go through similar steps: first create a private key to identify the client:

wg-client (umask 0077; wg genkey > client.key)
wg-client # cat client.key
wg-client # wg pubkey < client.key      # show the public key for this private key

We need to create the wireguard interface by running wireguard-go:

wg-client # wireguard-go wg0 # create the wg0 interface

This interface is then configured for the internal VPN network. wireguard-go creates a tun interface, and tun interfaces need to be configured with a source and destination address. As that is not important here, we can use the same address for both parts. To make sure we can reach the internal network properly, we also need to add a route:

wg-client # ifconfig wg0 inet
wg-client # route add -interface wg0

With the interface configured, we can now configure wireguard:

wg-client # wg set wg0 private-key ./client.key

Finally, we need to configure the wg0 interface with an endpoint for the server:

wg-client # ifconfig wg0 down
wg-client # ifconfig wg0 up
wg-client # wg set wg0 peer <peer pubkey> allowed-ips endpoint

When we configured the client peer, we told wireguard where to find the server with the endpoint parameter. In this case the client doesn’t listen on a predictable port, but instead it first has to create the wireguard tunnel with the server when traffic needs to be sent.

Now that we have created and configured our client, we need to add its public key and allowed-ip addresses to the server.

wg-server # wg set wg0 peer jILr21Xt+3sXDZepu5Z3syuJMXyc29f5GBXHZFPulEE= allowed-ips

Finally we can test the set up using ping:

wg-client # ping
PING ( 56 data bytes                 
64 bytes from icmp_seq=0 ttl=64 time=3.253 ms  
64 bytes from icmp_seq=1 ttl=64 time=1.358 ms  
64 bytes from icmp_seq=2 ttl=64 time=1.089 ms                                                            
64 bytes from icmp_seq=3 ttl=64 time=1.649 ms                                                            

Wireguard is not a ‘chatty’ protocol. If there is no traffic to send from the client for a long time, and it doesn’t have a fixed address, the server can ‘forget’ how to reach the client.

It might be the case that the client is behind a NAT network. To support this case, wireguard supports the persistent-keepalive configuration option. This option is disabled by default, but it allows our client to receive packets from the server when it is not sending traffic itself.

RFC4787 recommends a 2 minute timeout interval for NATs and middleboxes, but recent UDP protocols (such as QUIC), recommend sending packets every 30 seconds to prevent middleboxes from losing state for UDP flows.

If you need the tunnel connection kept alive, add the persistent-keepalive parameter:

wg-client # wg set wg0 peer <peer pubkey> allowed-ips persistent-keepalive 30 endpoint


It can help to run wireguard-go in the foreground and you can enable debug output from wireguard-go with the LOG_LEVEL environment variable:

# wireguard-go -f wg0
INFO: (wg0) 2021/01/11 10:43:49 Starting wireguard-go version 0.0.20201118
DEBUG: (wg0) 2021/01/11 10:43:49 Debug log enabled
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: event worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: encryption worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: decryption worker - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: TUN reader - started
DEBUG: (wg0) 2021/01/11 10:43:49 Routine: handshake worker - started
INFO: (wg0) 2021/01/11 10:43:49 Device started
INFO: (wg0) 2021/01/11 10:43:49 UAPI listener started

If you are launching wireguard-go using sudo, remember that sudo uses its own environment:

$ sudo LOG_LEVEL=debug wireguard-go -f wg0

wireguard-go doesn’t seem to always detect that the wg0 interface has been brought up and ends up not creating the UDP sockets required to send packets. You can check this in sockstat by looking for wireguard-go listening on UDP for v4 and v6, or you can check the wireguard-go log. If you don’t see a “Interface set up” message in the log, try toggling it by taking wg0 up and down:

# ifconfig wg0 down
# ifconfig wg0 up

Wireguard does not appear to offer a mechanism to decrypt packets based on pre-shared keys in the way that IPSec enables. This might appear in the future and I would check with wireshark.

Even without decryption, we can verify that packets are making it through the wg interface and across the network by checking for packets on the client and server’s wg port. If 51820 is the server’s port, then listening with tcpdump for udp traffic should return some packets:

# tcpdump udp and port 51820 
[tj@freebsd-wg-client] $ sudo tcpdump udp and port 51820                                                        
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode                             
listening on hn0, link-type EN10MB (Ethernet), capture size 262144 bytes                               
19:54:31.217241 IP > UDP, length 116
19:54:32.248686 IP > UDP, length 116         
19:54:33.321003 IP > UDP, length 116
19:54:34.027525 IP > UDP, length 32          
19:54:34.348493 IP > UDP, length 116         
19:54:35.398882 IP > UDP, length 116
19:54:36.425414 IP > UDP, length 116
19:54:37.448561 IP > UDP, length 116
19:54:38.520893 IP > UDP, length 116

The above capture is from a debugging session where the wg interface on the client was configured with the wrong address. It shows ping packets going from the client to the server, but no ICMP replies returning. These packets were encrypted, but their presence helped point to which part of the configuration wasn’t working.

FreeBSD Kernel

A wireguard kernel implementation was committed to FreeBSD in November 2020. The wg tool doesn’t support the use of the in-kernel implementation yet, and so ifconfig has to be used to configure the interface. So, while all the concepts remain the same, you need to use ifconfig, as of the writing of this article, there is not yet any documentation in the man page for how to set up wireguard.

On FreeBSD-13 we can create and configure the in-kernel wireguard like so:

# ifconfig wg create listen-port 51820 private-key  `cat server.key`
# ifconfig wg0 peer public-key <peer's public key>  endpoint allowed-ips


This has been an introduction on how you can use wireguard on FreeBSD to create VPNs. We haven’t covered the detail of static configuration or how best to manage key distribution to a large number of hosts. You can check for more information, documentation and pointers to tools that help with distribution of keys and mass configuration.

<strong>Meet the Author</strong>: Tom Jones

Meet the Author: Tom Jones
Tom Jones is an Internet Researcher and FreeBSD developer that works on improving the core protocols that drive the Internet. He is a
contributor to open standards in the IETF and is enthusiastic about using FreeBSD as a platform to experiment with new networking ideas as they progress towards standardisation.

Read more

Like this article? Share it!

You might also want be interested in

Get more out of your FreeBSD networking

Networking is crucial to many companies. If you have a FreeBSD networking implementation or a driver you’re looking at, our team can help you further enable your efforts.

Read More

More on this topic

Continue reading...
Not open for further replies.