rss logo

How to Set Up WireGuard VPN on Debian 12 Bookworm

WireGuard Logo

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
Diagram showing a WireGuard VPN architecture: Debian 12 server as VPN gateway and a Windows 11 client connected via secure tunnel
WireGuard Network Architecture Overview

Debian Server (Part I)

Debian Logo

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 restrictive umask 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)

Microsoft Logo
  • Download the latest version of WireGuard for Windows from the official website: https://wireguard.com/install/, then install it using the setup wizard:
Screenshot of WireGuard official website highlighting the Windows installer download button
  • Open the WireGuard VPN client by searching for "WireGuard" in the Windows Start menu:
Searching and launching the WireGuard app from Windows start menu
  • Create a new tunnel profile manually in the WireGuard client:
WireGuard Windows interface showing how to add an empty tunnel configuration manually
  • 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.
WireGuard Windows interface showing manual configuration of a new tunnel including private key, peer public key, and endpoint
[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:
WireGuard Windows application showing the 'Activate' button to start the tunnel

The VPN client is now ready. The server still needs additional configuration to accept and route the incoming connection.

Debian Server (Part II)

Debian Logo

WireGuard

  • Bring up the WireGuard interface:
root@host:~# ifup wg0
  • On the Windows 11 client, retrieve its public key from the WireGuard application interface:
Active WireGuard tunnel on Windows showing public key and peer configuration details
  • 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):
Windows command prompt successfully pinging the Debian WireGuard interface at IP 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:
Windows terminal showing successful ping to internal LAN gateway at 192.168.0.254 through the VPN tunnel

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

References