How to Set Up WireGuard VPN on Debian 12 Bookworm
- Last updated: Jun 15, 2025
Introduction
- WireGuard is a modern and highly efficient VPN solution, known for its:
- Excellent performance
- Robust security, thanks to a minimal attack surface and sound cryptographic design
- Ease of use and straightforward configuration
I previously worked with WireGuard VPN during the Debian 11 cycle. With the release of Debian 12 Bookworm, it’s the perfect time to revisit and improve that original tutorial.
This step-by-step guide will show you how to configure a Debian GNU/Linux server to act as a WireGuard VPN gateway, allowing remote Windows clients to securely connect and access a private LAN network.
Network Diagram
- WireGuard Server:
- OS: Debian GNU/Linux 12 (Bookworm)
- Role: VPN server and LAN gateway
- Windows Client:
- OS: Windows 11

Debian Server (Part I)
Installing
- Install the WireGuard package:
root@host:~# apt update && apt install wireguard
Network Configuration
- Create the
/etc/network/interfaces.d/wg0
file with the following content:
# Bring up the wg0 interface on boot
auto wg0
# Define the interface with a static IPv4 address
iface wg0 inet static
# Static IP address for the VPN interface
address 10.0.2.1/24
# Create the interface before bringing it up
pre-up ip link add $IFACE type wireguard
# Apply configuration from the main WireGuard config file
pre-up wg setconf $IFACE /etc/wireguard/$IFACE.conf
# Remove the interface when it is brought down
post-down ip link del $IFACE
Key Generation
- Navigate to the
/etc/wireguard/
directory and set a restrictiveumask
to ensure that newly created key files are only accessible by the owner:
root@host:~# cd /etc/wireguard/
root@host:/etc/wireguard# umask 077
- Generate the private key:
root@host:/etc/wireguard# wg genkey > wg-private.key
- Generate the corresponding public key (e.g., for a Windows client):
root@host:/etc/wireguard# wg pubkey < wg-private.key > w11-client01.key
Configuration
- Display the contents of the wg-private.key file and copy the value:
root@host:~# cat wg-private.key
2GIURzIDBgI1Y+1Ei+i2C5kEOR53mH172MaidaVpD3M=
- Create the
/etc/wireguard/wg0.conf
file and insert the PrivateKey you generated earlier:
# 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
- Restart the system to ensure all interface changes are applied properly:
root@host:~# reboot
Windows (Client)
- Download the latest version of WireGuard for Windows from the official website: https://wireguard.com/install/, then install it using the setup wizard:

- Open the WireGuard VPN client by searching for "WireGuard" in the Windows Start menu:

- Create a new tunnel profile manually in the WireGuard client:

- On the Debian server, retrieve the public key that was generated for the Windows client:
root@host:~# cat /etc/wireguard/w11-client01.key
hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU=
- Configure the WireGuard profile on the Windows client. Note: Do not modify the private key automatically generated by the WireGuard application — keep it as is.

[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=
# OPTIONALLY YOU CAN RESTRICT THE ALLOWED IPs WITH : AllowedIPs = 10.0.2.0/24, 192.168.0.0/24
AllowedIPs = 0.0.0.0/0
Endpoint = SERVER_PUBLIC_IP:51820
- Click on Activate in the WireGuard client to initiate the VPN connection:

The VPN client is now ready. The server still needs additional configuration to accept and route the incoming connection.
Debian Server (Part II)
WireGuard
- Bring up the WireGuard interface:
root@host:~# ifup wg0
- On the Windows 11 client, retrieve its public key from the WireGuard application interface:

- On the Debian server, authorize the Windows client by adding its public key to the WireGuard interface:
root@host:~# wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 0.0.0.0/0
- From the Windows client, verify connectivity by pinging the
wg0
interface IP of the Debian server (e.g.,10.0.2.1
):

- On the Debian server, display the list of active peers and connection statistics:
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
- To make the allowed peers persistent across reboots, edit the
/etc/network/interfaces.d/wg0
file and append the following lines at the end:
# 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
Gateway Mode
At this stage, remote clients can successfully reach the Debian WireGuard server. To allow them to access the full 192.168.0.0/24 internal network and communicate with other LAN devices, you now need to enable gateway mode.
Enable IP Forwarding
- Edit the
/etc/sysctl.conf
file to enable IPv4 forwarding at boot:
net.ipv4.ip_forward = 1
- Apply the change immediately with the following command:
root@host:~# sysctl -p /etc/sysctl.conf
- Verify that IP forwarding is now active:
root@host:~# cat /proc/sys/net/ipv4/ip_forward
1
nftables NAT Rules
With IP forwarding enabled, the next step is to configure NAT so that traffic from the VPN subnet can be routed to the LAN.
- First, identify the name of your internal network interface (e.g.,
ens18
in this example):
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
Temporary Rules
The following steps show how to enable NAT using temporary nftables rules (these will not persist after a reboot).
- Add a masquerade rule to allow traffic from the VPN subnet to access the internal network, making the Debian server act as a NAT gateway for connected Windows clients:
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
- Optionally, you can define additional firewall filter rules to further control traffic:
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
Persistent Rules
- To make the NAT configuration persistent across reboots, edit the
/etc/nftables.conf
file and include the following content:
#!/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"
}
}
- Enable the nftables service so that it loads automatically at boot:
root@host:~# systemctl enable nftables.service
- Apply the current nftables configuration immediately:
root@host:~# nft -f /etc/nftables.conf
Test from Windows
- From the Windows client, ping a host on the internal LAN (e.g.,
192.168.0.254
) to verify VPN connectivity beyond the server:

Troubleshooting
Source: https://serverfault.com.
Note: If peer configurations are not made persistent, you will need to re-run the wg set wg0 peer
command for each client after bringing the interface down and back up (ifdown wg0
followed by ifup wg0
).
- Enable debug mode for WireGuard (useful for troubleshooting connection issues):
root@host:~# modprobe -r wireguard && modprobe wireguard dyndbg
root@host:~# ifdown wg0; ifup wg0
- Monitor WireGuard logs in real time:
root@host:~# journalctl -f --grep wireguard
- Disable debug mode once you're done troubleshooting:
root@host:~# modprobe -r wireguard && modprobe wireguard
root@host:~# ifdown wg0; ifup wg0