Wireguard

This is the official WireGuard quickstart setup in a GNS3 netlab. Both linux WireGuard peers are connected using a routed IP connection via encrypted interface wg0. The IP addressing on both peers interface eth0 represent public IP addressing.

In the final step procedure, both linux peers automatically start up the wg0 tunnel connection at boot up sequence, and stop the service when the related wg0 interface has been stopped due to a system shutdown or manually reset as system root user. The procedure explained in this blog entry uses 2 linux system commands only, to configure a secure connection:

  • wg
  • ip

Topology

linux wireguard network topology

IP addressing

hostname eth0 wg0
peerA 192.0.2.10/24 10.0.0.0/31
peerB 198.51.100.10/24 10.0.0.1/31

State

Traceroute mtr command from peerA to peerB in the current state of network, before interface wg0 is created on both peers.

                             My traceroute  [v0.96]
peerA (192.0.2.10) -> 198.51.100.10 (198.51.100.10)    2026-01-21T12:51:42+0100
Keys:  Help   Display mode   Restart statistics   Order of fields   quit
                                       Packets               Pings
 Host                                Loss%   Snt   Last   Avg  Best  Wrst StDev
 1. 192.0.2.1                         0.0%     5    1.1   1.2   1.1   1.3   0.1
 2. 10.100.0.0                        0.0%     4    1.9   1.9   1.7   2.1   0.2
 3. 198.51.100.10                     0.0%     4    2.2   2.3   2.2   2.4   0.3

The full setup is explained if you

Configuration

  • Create public/private keys
  • Setup interface wg0
  • Setup site-to-site VPN using P-t-P link
  • Packet capture verification
  • Create wg0 system service

Create a private/public key

On both peers create the encryption related file using the wg genkey system command:

peerA

Use the umask 077 prior to generating the wg0 key, this sets the correct file settings on key file creation:

root # umask 077

Generate the key

root # wg genkey > private

Show the key

root # more private kHbnpVxsV6N3woOwn5MyJ9hJIFskeCXBFMzV+Y9cJ2I=

Ready.

peerB

It the umask 077 command has not been set, before using wg genkey command, following Warning appear:

root # wg genkey > private

Warning: writing to world accessible file. Consider setting the umask to 077 and trying again.

Once again, the same warning as above but this time in red:

📕 Warning
Writing to world accessible file. Consider setting the umask to 077 and trying again.

Fix the Warning shown above by using chmod command on the file:

root # chmod 600 private

Verify the current setting are set to 600

root # ls -lh private -rw------- 1 root root 45 Jan 23 14:42 private

Show the key

root # more private YFF1o/f7zbfJYn4w0c8OomuQ9LU1KS1lOubiluCXNGQ=

Create a wg0 interface

Of course assign a static IP address to the wg0 interface too. This example will be using a /31 ip addressing. See Using 31-Bit Prefixes on IPv4 Point-to-Point Links. Since only 2 Peers will have a secure VPN session, this IP prefix is perfect to that use-scenario, a Point-to-Point IP Link.

📗 Hint
Any IP network size can be configured for the wg0 interface like f.e: /24 or /29. In this example a /31 IP prefix size is used for the encrypted connection.

This example below shows how to take sure, on the IPv4 level, that only 2 participants can communicate using encrypted protocols by using this specific RFC3021 IPv4 prefix /31. Connecting another 3-rd participant, by configuration mistake is excluded by using the right connection prefix for the VPN communication, peer-to-peer only.

peerA

root # ip link add dev wg0 type wireguard
root # ip address add 10.0.0.0/31 dev wg0
root # wg set wg0 private-key ./private listen-port 51001

linux IP state before activating interface wg0

root # ip route

default via 192.0.2.1 dev eth0 proto dhcp src 192.0.2.10 metric 1002 192.0.2.0/24 dev eth0 proto dhcp scope link src 192.0.2.10 metric 1002

Activate the wg0 interface

root # ip link set wg0 up

Verify linux IP state, note that setting the wg0 link to UP, installed the configured P-t-P IP link into the ruoting table:

root # ip route

default via 192.0.2.1 dev eth0 proto dhcp src 192.0.2.10 metric 1002 10.0.0.0/31 dev wg0 proto kernel scope link src 10.0.0.0 192.0.2.0/24 dev eth0 proto dhcp scope link src 192.0.2.10 metric 1002

peerB

root # ip link add dev wg0 type wireguard
root # ip address add 10.0.0.1/31 dev wg0
root # wg set wg0 private-key ./private listen-port 51002
root # ip link set wg0 up

At this point the wg0 interface is ACTIVE and UP, but still not operative or working.

Verify state

Commands to verify the current networking state:

root # wg
root # ip add

peerA

Show the operational state of the wg0 interface:

root # wg

  interface: wg0 public key: UmoVma63KHqrn4lmebpCf77eDt0rKqzM0HKLWkQhA00= private key: (hidden) listening port: 51001

peerB

Show the operational state of the wg0 interface:

root # wg

  interface: wg0 public key: B3g2vEnmcWhBDfpN11th8YbFKWCIr9AuxaBy2OZR1TE= private key: (hidden) listening port: 51002

setup VPN site-to-site P-t-P

This is the a site-to-site VPN, only 2 peers involved. Notify, that the allowed peer has a /32 host netmask in the below command:

peerA

root # wg set wg0 peer B3g2vEnmcWhBDfpN11th8YbFKWCIr9AuxaBy2OZR1TE= allowed-ips 10.0.0.1/32 endpoint 198.51.100.10:51002

peerB

root # wg set wg0 peer UmoVma63KHqrn4lmebpCf77eDt0rKqzM0HKLWkQhA00= allowed-ips 10.0.0.0/32 endpoint 192.0.2.10:51001

Now the wg0 interface tunnel connection is established.

Verify connectivity again using the wg command shows the peers and the current state:

root # wg

interface: wg0 public key: UmoVma63KHqrn4lmebpCf77eDt0rKqzM0HKLWkQhA00= private key: (hidden) listening port: 51001   peer: B3g2vEnmcWhBDfpN11th8YbFKWCIr9AuxaBy2OZR1TE= endpoint: 198.51.100.10:51002 allowed ips: 10.0.0.0/32 latest handshake: 22 seconds ago transfer: 348 B received, 436 B sent

Use the wg show all dump command to receive the above output in more compact manner:

root # wg show all dump

wg0 kHbnpVxsV6N3woOwn5MyJ9hJIFskeCXBFMzV+Y9cJ2I= UmoVma63KHqrn4lmebpCf77eDt0rKqzM0HKLWkQhA00= 51001 off wg0 B3g2vEnmcWhBDfpN11th8YbFKWCIr9AuxaBy2OZR1TE= (none) 198.51.100.10:51002 10.0.0.1/32 0 0 0 off

Check interface wg0 and the configured subnet 10.0.0.0/31 is in the kernel IP routing table

root # ip route

default via 192.0.2.1 dev eth0 proto dhcp src 192.0.2.10 metric 1002 10.0.0.0/31 dev wg0 proto kernel scope link src 10.0.0.0 192.0.2.0/24 dev eth0 proto dhcp scope link src 192.0.2.10 metric 1002

Make the ICMP test from peerA to directly connected peerB's IP interface:

user % ping 10.1

PING 10.0.0.1 (10.0.0.1): 56 data bytes 64 bytes from 10.0.0.1: seq=0 ttl=64 time=1.848 ms 64 bytes from 10.0.0.1: seq=1 ttl=64 time=1.946 ms   --- 10.0.0.1 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 1.848/1.897/1.946 ms

IP packet caputure

Create packet caputure using tshark or wireshark.

ICMP packet capture

Ping command sent from peerA to peerB

  • source IP 192.0.2.10
  • dest IP 198.51.100.10

Command

root # ping 198.51.100.10

Packet capture

tshark -r peerA-peerB_ICMP_connectivity.pcapng | grep ICMP

    9  15.289637   192.0.2.10 → 198.51.100.10 ICMP 98 Echo (ping) request  id=0x205c, seq=0/0, ttl=63
   10  15.290465 198.51.100.10 → 192.0.2.10   ICMP 98 Echo (ping) reply    id=0x205c, seq=0/0, ttl=63 (request in 9)
   11  16.290107   192.0.2.10 → 198.51.100.10 ICMP 98 Echo (ping) request  id=0x205c, seq=1/256, ttl=63
   12  16.290992 198.51.100.10 → 192.0.2.10   ICMP 98 Echo (ping) reply    id=0x205c, seq=1/256, ttl=63 (request in 11)
   13  17.290617   192.0.2.10 → 198.51.100.10 ICMP 98 Echo (ping) request  id=0x205c, seq=2/512, ttl=63
   14  17.291597 198.51.100.10 → 192.0.2.10   ICMP 98 Echo (ping) reply    id=0x205c, seq=2/512, ttl=63 (request in 13)
   15  18.291135   192.0.2.10 → 198.51.100.10 ICMP 98 Echo (ping) request  id=0x205c, seq=3/768, ttl=63
   16  18.291884 198.51.100.10 → 192.0.2.10   ICMP 98 Echo (ping) reply    id=0x205c, seq=3/768, ttl=63 (request in 15)
   18  19.291636   192.0.2.10 → 198.51.100.10 ICMP 98 Echo (ping) request  id=0x205c, seq=4/1024, ttl=63
   19  19.292445 198.51.100.10 → 192.0.2.10   ICMP 98 Echo (ping) reply    id=0x205c, seq=4/1024, ttl=63 (request in 18)

WireGuard ICMP packet capture

This is the same ping command as above from peerA to peerB when wireguard is active.

  • source IP 10.0.0.0
  • destination IP 10.0.0.1

Command:

user % ping 10.1

Output:

tshark -r peerA-peerB_ICMP_wg0_connectivity.pcapng | rg -i wire

    8  12.799917   192.0.2.10 → 198.51.100.10 WireGuard 190 Handshake Initiation, sender=0xEBBA41F1
    9  12.801505 198.51.100.10 → 192.0.2.10   WireGuard 134 Handshake Response, sender=0x7F463EDD, receiver=0xEBBA41F1
   10  12.802548   192.0.2.10 → 198.51.100.10 WireGuard 170 Transport Data, receiver=0x7F463EDD, counter=0, datalen=96
   11  12.803425 198.51.100.10 → 192.0.2.10   WireGuard 170 Transport Data, receiver=0xEBBA41F1, counter=0, datalen=96
   12  13.799941   192.0.2.10 → 198.51.100.10 WireGuard 170 Transport Data, receiver=0x7F463EDD, counter=1, datalen=96
   13  13.801002 198.51.100.10 → 192.0.2.10   WireGuard 170 Transport Data, receiver=0xEBBA41F1, counter=1, datalen=96
   14  14.800166   192.0.2.10 → 198.51.100.10 WireGuard 170 Transport Data, receiver=0x7F463EDD, counter=2, datalen=96
   15  14.801295 198.51.100.10 → 192.0.2.10   WireGuard 170 Transport Data, receiver=0xEBBA41F1, counter=2, datalen=96
   16  15.800456   192.0.2.10 → 198.51.100.10 WireGuard 170 Transport Data, receiver=0x7F463EDD, counter=3, datalen=96
   17  15.801539 198.51.100.10 → 192.0.2.10   WireGuard 170 Transport Data, receiver=0xEBBA41F1, counter=3, datalen=96
   25  26.101463   192.0.2.10 → 198.51.100.10 WireGuard 74 Keepalive, receiver=0x7F463EDD, counter=4

The first 2 WireGuard packets are Handshake, both side initiate a encrypted communication, key verification. After the Handshake the data transport begins, encrypted. Last packet is a WireGuard Keepalive packet, send from source IP 192.0.2.10. The initiator of the encrypted communication. The ICMP packets has been sent from peerA.

Verify wg0 MTU

Notice, using WireGuard lowers the MTU to 1420 for transport. This is the IP MTU size set on the wg0 interface.

3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none
    inet 10.0.0.0/31 scope global wg0
       valid_lft forever preferred_lft forever

service wg0

On a alpinelinux use following commands, configure local static port 51001 on peerA using open-rc

root # rc-server add local default

This command will read the /etc/local.d/ directory and on start execute all *.start scripts. Likewise using the stop will execute all *.stop scripts in that directory.

peerA

Create a local.d wg0.start script:

cat <<EOF > /etc/local.d/00_wg0.start
#!/bin/sh
ip link add dev wg0 type wireguard
ip address add 10.0.0.0/31 dev wg0
wg set wg0 private-key /root/private listen-port 51001
wg set wg0 peer B3g2vEnmcWhBDfpN11th8YbFKWCIr9AuxaBy2OZR1TE= allowed-ips 10.0.0.1/32 endpoint 198.51.100.10:51002
ip link set wg0 up
EOF

Create a local.d wg0.stop script:

cat <<EOF > /etc/local.d/00_wg0.stop
ip link set wg0 down
ip link del dev wg0
EOF

Change the created files be executable:

root # chmod +x /etc/local.d/00_wg0.start && chmod +x /etc/local.d/00_wg0.stop

Restart the daemon

root # rc-service local restart

peerB

on peerB configure the local port to static 51002, the far end hosts peerA uses destination port 51001

cat <<EOF > /etc/local.d/00_wg0.start
#!/bin/sh
ip link add dev wg0 type wireguard
ip address add 10.0.0.1/31 dev wg0
wg set wg0 private-key /root/private listen-port 51002
wg set wg0 peer UmoVma63KHqrn4lmebpCf77eDt0rKqzM0HKLWkQhA00= allowed-ips 10.0.0.0/32 endpoint 192.0.2.10:51001
ip link set wg0 up
EOF

Create a wg0 stop script:

cat <<EOF > /etc/local.d/00_wg0.stop
ip link set wg0 down
ip link del dev wg0
EOF

Change both files to be have the execute bit set:

root # chmod +x /etc/local.d/00_wg0.start && chmod +x /etc/local.d/00_wg0.stop

Restart the daemon

root # rc-service local restart

Summary

wireguard as a system tool is quickly setup, loads its own kernel module. It does not need additional big software suite to install. And is configured on the command line only if used vanilla. If you rely on ad-hoc setup of encrypted connections then wg is the right tool for it.

References