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

  • Navigate to the /etc/wireguard/ directory and set the umask to restrict read and write access to the owner for the next generated key:
root@host:~# cd /etc/wireguard/
root@host:~# umask 077
  • Generate the private key:
root@host:~# wg genkey > wg-private.key
  • Generate the public key for the Windows client:
root@host:~# wg pubkey < wg-private.key > w11-client01.key

Configuration

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

Windows (Client)

Microsoft Logo
  • Download the latest version of the software from the official website: https://wireguard.com/, then install it.
Screenshot of WireGuard official website highlighting the Windows installer download button
  • Open the 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 the Debian server, copy the public key:
root@host:~# cat /etc/wireguard/w11-client01.key
hlKy6azGCB0uVbCdkW8Htx23k57iWzOFJRLAYHTx5wU=
  • Configure the profile (Note: keep the private key generated by the WireGuard application on Windows.)
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 to establish the VPN connection:
WireGuard Windows application showing the 'Activate' button to start the tunnel

The server now requires additional configuration to accept 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
  • On the Debian server, authorize the client's public key using the following command:
root@host:~# wg set wg0 peer CLIENT_PUBLIC_KEY allowed-ips 0.0.0.0/0
  • From Windows, ping the Debian wg0 interface IP to verify connectivity:
Windows command prompt successfully pinging the Debian WireGuard interface at IP 10.0.2.1
  • Display the list of currently active peer connections on the server:
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, edit the /etc/network/interfaces.d/wg0 file and add 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, with the current setup, remote clients can successfully reach the Debian WireGuard server. The next step is to enable gateway mode, which will allow clients to access the entire 192.168.0.0/24 network and communicate with other devices on the LAN.

Enable IP Forwarding

  • Edit the /etc/sysctl.conf file to enable IP forwarding at boot time:
net.ipv4.ip_forward = 1
  • Apply the configuration changes using the following command:
root@host:~# sysctl -p /etc/sysctl.conf
  • Verify that IP forwarding is currently enabled:
root@host:~# cat /proc/sys/net/ipv4/ip_forward
1

nftables NAT Rules

Once IP forwarding is enabled, create a NAT rule to allow traffic from the VPN subnet to be routed to the LAN.

  • 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 demonstrate how to enable NAT using temporary nftables rules.

  • Add masquerade rules to allow the internal network to be accessible from the Windows client:
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 may also define firewall filter rules for added control:
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, edit the /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 the nftables service using systemctl:
root@host:~# systemctl enable nftables.service
  • Apply the nftables configuration:
root@host:~# nft -f /etc/nftables.conf

Test from Windows

  • Ping any internal host to verify connectivity through the VPN:
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 commands for each peer after executing ifdown wg0 followed by ifup wg0.

  • Enable WireGuard debug mode:
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