I previously worked with WireGuard VPN during the Debian 11 era, and the release of Debian 12 Bookworm presents a perfect opportunity to refresh and enhance that original tutorial.
This step-by-step guide will walk you through configuring a Debian GNU/Linux server to serve as a WireGuard VPN gateway, enabling remote Windows clients to securely connect and access a private LAN.
root@host:~# apt update && apt install wireguard
# indicate that wg0 should be created when the system boots, and on ifup -a
auto wg0
# describe wg0 as an IPv4 interface with static address
iface wg0 inet static
# static IP address
address 10.0.2.1/24
# before ifup, create the device using this ip link command
pre-up ip link add $IFACE type wireguard
# before ifup, set the WireGuard config from earlier
pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
# after ifdown, destroy the wg0 interface
post-down ip link del $IFACE
root@host:~# cd /etc/wireguard/
root@host:~# umask 077
root@host:~# wg genkey > wg-private.key
root@host:~# wg pubkey < wg-private.key > w11-client01.key
root@host:~# cat wg-private.key
2GIURzIDBgI1Y+1Ei+i2C5kEOR53mH172MaidaVpD3M=
# define the WireGuard service
[Interface]
# contents of file wg-private.key that was recently created
PrivateKey = 2GIURzIDBgI1Y+1Ei+i2C5kEOR53mH172MaidaVpD3M=
# UDP service port; 51820 is a common choice for WireGuard
ListenPort = 51820
root@host:~# reboot
root@host:~# cat /etc/wireguard/w11-client01.key
hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU=
[Interface]
PrivateKey = CLIENT_PRIVATE_KEY
# CLIENT IP :
Address = 10.0.2.2/24
# IF YOU WANT TO PUSH DNS CONFIG :
# DNS = 192.168.11.1
[Peer]
PublicKey = hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU=
# WE COULD BE MORE PRECISE WITH : AllowedIPs = 10.0.2.0/24, 192.168.0.0/24
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_PUBLIC_IP:51820
The server now requires additional configuration to allow the connection.
root@host:~# ifup wg0
root@host:~# wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 0.0.0.0/0
root@host:~# wg show interface: wg0 public key: hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU= private key: (hidden) listening port: 51820 peer: 3A5R|UPz7/c1r+sToEDSkxYY8kdou+Y7TwAvb2NIf0c= endpoint: WINDOWS_IP:52925 allowed ips: 0.0.0.0/0 latest handshake: 6 seconds ago transfer: 453.44 KiB received, 411.87 KiB sent
# indicate that wg0 should be created when the system boots, and on ifup -a
auto wg0
# describe wg0 as an IPv4 interface with static address
iface wg0 inet static
# static IP address
address 10.0.2.1/24
# before ifup, create the device with this ip link command
pre-up ip link add $IFACE type wireguard
# before ifup, set the WireGuard config from earlier
pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
# after ifdown, destroy the wg0 interface
post-down ip link del $IFACE
# allowed clients
up wg set wg0 peer CLIENT01_PUBLIC_KEY allowed-ips 0.0.0.0/0
up wg set wg0 peer CLIENT02_PUBLIC_KEY allowed-ips 0.0.0.0/0
At this step, and with this configuration we can have access to our Debian WireGuard server. We will now add the gateway mode which will allow remote client to communicate with the entire 192.168.0.0/24 network and communicate with other servers in this LAN.
net.ipv4.ip_forward = 1
root@host:~# sysctl -p /etc/sysctl.conf
root@host:~# cat /proc/sys/net/ipv4/ip_forward
1
Once IP forwarding is enabled, we will create a NAT rule to allow routing from the VPN subnet to the LAN.
root@host:~# ip addr sh
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: ens18: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether bc:24:12:48:bd:1a brd ff:ff:ff:ff:ff:ff
altname enp0s18
inet 192.168.0.200/24 brd 192.168.0.255 scope global ens18
valid_lft forever preferred_lft forever
inet6 fe80::be24:11ff:fe78:bd1a/64 scope link
valid_lft forever preferred_lft forever
3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
link/none
inet 10.0.2.1/24 brd 10.0.2.255 scope global wg0
valid_lft forever preferred_lft forever
Here I show how to enable NAT via temporary nftables rules.
root@host:~# nft add table ip NAT
root@host:~# nft add chain ip NAT my_masquerade '{ type nat hook postrouting priority 100; }'
root@host:~# nft add rule NAT my_masquerade ip saddr { 10.0.2.0/24 } oifname ens18 masquerade
root@host:~# nft add rule ip filter INPUT udp dport 51820 ct state new,established counter accept
root@host:~# nft add rule ip filter OUTPUT udp sport 51820 ct state established counter accept
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0;
udp dport 51820 ct state new,established counter accept
}
chain forward {
type filter hook forward priority 0;
}
chain output {
type filter hook output priority 0;
udp sport 51820 ct state established counter accept
}
}
table ip NAT {
chain my_masquerade {
type nat hook postrouting priority 100; policy accept;
ip saddr { 10.0.2.0/24 } oifname "ens18" masquerade comment "outgoing NAT"
}
}
root@host:~# systemctl enable nftables.service
root@host:~# nft -f /etc/nftables.conf
Source: https://serverfault.com.
Note: if you don't set peers as persistent, you will need to re-run the commands up wg set wg0 peer for each peer after running ifdown wg0; ifup wg0.
root@host:~# modprobe -r wireguard && modprobe wireguard dyndbg
root@host:~# ifdown wg0; ifup wg0
root@host:~# journalctl -f --grep wireguard
root@host:~# modprobe -r wireguard && modprobe wireguard
root@host:~# ifdown wg0; ifup wg0
Contact :