rss logo

How to Set Up WireGuard VPN on Debian 12 Bookworm

WireGuard Logo

Introduction

  • WireGuard is a modern and efficient VPN solution, known for its:
    • Excellent performance
    • Strong security (with a minimal attack surface and cryptographically sound design)
    • Ease of use and simplicity of configuration

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.

Network diagram

  • WireGuard Server:
    • OS: Debian gnu/linux 12 (bookworm)
    • Role: wireguard server + gateway
  • Windows Client:
    • OS: Windows 11
Diagram showing WireGuard VPN architecture with a Debian 12 server and Windows client connected via secure tunnel
WireGuard Network Architecture

Debian Server (Part I)

Debian Logo

Installing

  • Install WireGuard:
root@host:~# apt update && apt install wireguard

Network Configuration

  • Create the /etc/network/interfaces.d/wg0 file:
# 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

Key Generation

  • Move to /etc/wireguard/ directory and change the mask to allow read and write access only for the owner for the next created key:
root@host:~# cd /etc/wireguard/
root@host:~# umask 077
  • Create private key:
root@host:~# wg genkey > wg-private.key
  • Create public keys which will be used for our Windows host:
root@host:~# wg pubkey < wg-private.key > w11-client01.key

Configure

  • Copy the wg-private.key file content:
root@host:~# cat wg-private.key 
2GIURzIDBgI1Y+1Ei+i2C5kEOR53mH172MaidaVpD3M=
  • Create a /etc/wireguard/wg0.conf file and replace PrivateKey value with the one created just above:
# 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 to apply the network changes:
root@host:~# reboot

Windows (Client)

Microsoft Logo Screenshot of WireGuard official website highlighting the Windows installer download button
  • Open WireGuard VPN Client:
Searching and launching the WireGuard app from Windows start menu
  • Add a new profile:
WireGuard Windows interface showing how to add an empty tunnel configuration manually
  • From Debian copy the public key:
root@host:~# cat /etc/wireguard/w11-client01.key
hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU=
  • Configure profile (Note: keep your own private key generated by the WireGuard Windows Application.):
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=
# 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
  • Click on Activate to establish the VPN:
WireGuard Windows application showing the 'Activate' button to start the tunnel

The server now requires additional configuration to allow the connection.

Debian Server (Part II)

Debian Logo

WireGuard

  • Enable wireguard:
root@host:~# ifup wg0
  • Retrieve the public key from the Windows 11 client:
Active WireGuard tunnel on Windows showing public key and peer configuration details
  • From the Debian server allow the client public key with this command:
root@host:~# wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 0.0.0.0/0
  • From Windows, ping the Debian wg0 IP to see if it works:
Windows command prompt successfully pinging the Debian WireGuard interface at IP 10.0.2.1
  • Show current active peers connections:
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 host persistent, edit the /etc/network/interfaces.d/wg0 file and add:
 # 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 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.

Enable IP forwarding

  • Edit /etc/sysctl.conf to enable the IP forwarding at boot:
net.ipv4.ip_forward = 1
  • Run this command to apply the change:
root@host:~# sysctl -p /etc/sysctl.conf
  • Check IP forwarding is enabled:
root@host:~# cat /proc/sys/net/ipv4/ip_forward
1

nftables NAT rules

Once IP forwarding is enabled, we will create a NAT rule to allow routing from the VPN subnet to the LAN.

  • Identify your internal interface name (ens18 in my configuration):
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

Here I show how to enable NAT via temporary nftables rules.

  • Add the masquerade rules to make your internal network reachable from Windows:
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
  • Optional, we may also define filter rules:
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 our NAT configuration persistent, edit /etc/nftables.conf file:
#!/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 nftables service with systemctl:
root@host:~# systemctl enable nftables.service
  • Apply nftables configuration:
root@host:~# nft -f /etc/nftables.conf

Test from Windows

  • Ping any internal host:
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 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.

  • Enable WireGuard debug:
root@host:~# modprobe -r wireguard && modprobe wireguard dyndbg
root@host:~# ifdown wg0; ifup wg0
  • Watch for WireGuard logs:
root@host:~# journalctl -f --grep wireguard
  • Disable WireGuard debug:
root@host:~# modprobe -r wireguard && modprobe wireguard
root@host:~# ifdown wg0; ifup wg0

References

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

Contact :

contact mail address