Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Deploy WireGuard VPN Server on Debian 12

Steps

Setup Server

Install WireGuard

sudo apt update
sudo apt-get install wireguard -y

Create Server Keys

Now we need to create the public and private key-pairs used for establishing a secure connection. We can do this by running the following command:

wg genkey | tee privatekey | wg pubkey > publickey

This will result in there being two files locally called privatekey and publickey which contain the private and public keys accordingly.

Configure Tunnel Interface

WIREGUARD_INTERFACE_NAME="wg0"
sudo touch /etc/wireguard/${WIREGUARD_INTERFACE_NAME}.conf

The interface does not need to be called wg0, but whatever you call it, the file must be at that path and have the same name with the .conf extension.

Now either fill it in manually by substituting the variables in the content below:

[Interface]
PrivateKey=${PRIVATE_KEY}


# Specify the subnet that should be used for assigning IP addresses to the clients on this
# tunnel interface. This needs to not overlap with your server's private IP subnet!
Address=${WIREGUARD_SUBNET}


SaveConfig=false


# Add rules to iptables networking when the interface come up in order to have the server do the 
# following:
# 1. Accept every forwarding packet on the tunnel interface (wg0)
# 2. have packets that we forward out on the eth0 interface be manipulated to look like they came 
#    from this server instead of having their original source IP.
PostUp=iptables -A FORWARD -i ${WIREGUARD_INTERFACE_NAME} -j ACCEPT; iptables -t nat -A POSTROUTING -o ${NORMAL_INTERFACE_NAME} -j MASQUERADE;


# Remove the rules we added, when the wireguard tunnel interface comes down
PostDown=iptables -D FORWARD -i ${WIREGUARD_INTERFACE_NAME} -j ACCEPT; iptables -t nat -D POSTROUTING -o ${NORMAL_INTERFACE_NAME} -j MASQUERADE;


# Specify which port you wish for the wireguard to listen on for this interface. This can be anything
# you want, but 51820 is the default for wireguard.
ListenPort=${PORT}

... or you can use the following BASH script to fill in the variables and have it create the file for you:

# Settings - fill these in as appropriate to you.
PRIVATE_KEY=""
WIREGUARD_INTERFACE_NAME="wg0"
WIREGUARD_SUBNET="10.172.0.1/24"
NORMAL_INTERFACE_NAME="eth0"
PORT=51820

sudo echo "
[Interface]
PrivateKey=${PRIVATE_KEY}


# Specify the subnet that should be used for assigning IP addresses to the clients on this
# tunnel interface. This needs to not overlap with your server's private IP subnet!
Address=${WIREGUARD_SUBNET}


# Specify if you want wireguard to overwrite the config on shutdown.
SaveConfig=false


# Add rules to iptables networking when the interface come up in order to have the server do the 
# following:
# 1. Accept every forwarding packet on the tunnel interface (wg0)
# 2. have packets that we forward out on the eth0 interface be manipulated to look like they came from this
#    server instead of having their original source IP.
PostUp=iptables -A FORWARD -i ${WIREGUARD_INTERFACE_NAME} -j ACCEPT; iptables -t nat -A POSTROUTING -o ${NORMAL_INTERFACE_NAME} -j MASQUERADE;


# Remove the rules we added, when the wireguard tunnel interface comes down
PostDown=iptables -D FORWARD -i ${WIREGUARD_INTERFACE_NAME} -j ACCEPT; iptables -t nat -D POSTROUTING -o ${NORMAL_INTERFACE_NAME} -j MASQUERADE;


# Specify which port you wish for the wireguard to listen on for this interface. This can be anything
# you want, but 51820 is the default for wireguard.
ListenPort=${PORT}

" | sudo tee /etc/wireguard/${WIREGUARD_INTERFACE_NAME}.conf

Enable Packet Forwarding

Finally, we need to ensure that the server will allow packet forwarding. This can be enabled by running:

sudo echo 1 | sudo tee /proc/sys/net/ipv4/ip_forward

Then we also need to enable it in the sysctl kernel configuration file:

SEARCH="#net.ipv4.ip_forward.*"
REPLACE="net.ipv4.ip_forward=1"
FILEPATH="/etc/sysctl.conf"
sudo sed -i "s;$SEARCH;$REPLACE;" $FILEPATH

# Have the change take immediate effect
sudo sysctl -p

Setup Client

Now we need to setup and configure wireguard on the client, for it to connect to the server.

Install Wireguard

Install wireguard as you did before:

sudo apt update
sudo apt-get install wireguard -y

Create a public/private key-pair as you did before on your client computer:

wg genkey | tee privatekey | wg pubkey > publickey

Configure Tunnel Interface

Now create a wireguard configuration file by running:

touch /etc/wireguard/wg0.conf

The file doesn't have to be called wg0.conf and can be labelled anything with the .conf extension. However, it does need to be in the /etc/wireguard folder and the name of the file will reflect the name of the interface. The name of the interface doesn't need to match the name of the interface on the server either.

Now either fill it with the following, taking care to manually swap out the values as appropriate to your setup:

[Interface]
PrivateKey=${PRIVATE_KEY}


# Specify the subnet that should be used for assigning IP addresses to the clients on this
# tunnel interface. This needs to not overlap with your private IP subnet, and needs to be
# unique on the subnet you created. E.g. we used 10.172.0.1 for the server, so we
# are using 10.172.0.2 here. If you were to create another client it would need to be
# 10.172.0.3 etc.
Address=10.172.0.2/24


# Specify if you wish for WireGuard to overwrite the config on shutdown.
SaveConfig=false


[Peer]

# Specify the public key of the server you wish to connect to.
PublicKey=${SERVER_PUBLIC_KEY}


# Specify the IP address of the server to connect to. If you are using something like DigitalOcean,
# then this would be that instance's public/internet IP address. If  you are using a computer on
# your private network, then you could specify its private IP address.
Endpoint=${SERVER_PUBLIC_IP_ADDRESS}


# Specify which subnets that wireguard should send through the tunnel. We wish to send all traffic 
# through the server, so we will use 0.0.0.0/0, but if you only wanted to forward traffic for 
# another private subnet that your server is on, you could specify it's subnet instead.
AllowedIPs=0.0.0.0/0


# Optionally have the following to have wireguard send a keepalive packet every x seconds so 
# that the connection remains open, otherwise routers/gateways between the server and the client 
# may close the connection because wireguard uses UDP and is stateless.
PersistentKeepalive=30

... or run the following BASH script with setting the variables at the top:

CLIENT_PRIVATE_KEY=""
SERVER_PUBLIC_KEY=""
WIREGUARD_INTERFACE_NAME="wg0"
WIREGUARD_SUBNET_CLIENT_IP="10.172.0.2/24"
SERVER_PUBLIC_IP_ADDRESS=""
AllowedIPs="0.0.0.0/0"
PORT=51820

sudo echo "
[Interface]
PrivateKey=${CLIENT_PRIVATE_KEY}


# Specify the subnet that should be used for assigning IP addresses to the clients on this
# tunnel interface. This needs to not overlap with your private IP subnet, and needs to be
# unique on the subnet you created. E.g. we used 10.172.0.1 for the server, so we
# are using 10.172.0.2 here. If you were to create another client it would need to be
# 10.172.0.3 etc.
Address=${WIREGUARD_SUBNET_CLIENT_IP}


# Specify if you wish for WireGuard to overwrite the config on shutdown.
SaveConfig=false


[Peer]

# Specify the public key of the server you wish to connect to.
PublicKey=${SERVER_PUBLIC_KEY}


# Specify the IP address of the server to connect to. If you are using something like DigitalOcean,
# then this would be that instance's public/internet IP address. If  you are using a computer on
# your private network, then you could specify its private IP address.
Endpoint=${SERVER_PUBLIC_IP_ADDRESS}:${PORT}


# Specify which subnets that wireguard should send through the tunnel. We wish to send all traffic 
# through the server, so we will use 0.0.0.0/0, but if you only wanted to forward traffic for 
# another private subnet that your server is on, you could specify it's subnet instead.
AllowedIPs=${AllowedIPs}


# Optionally have the following to have wireguard send a keepalive packet every x seconds so that 
# the connection remains open, otherwise routers/gateways between the server and the client may 
# close the connection because wireguard uses UDP and is stateless.
PersistentKeepalive=30" | sudo tee /etc/wireguard/${WIREGUARD_INTERFACE_NAME}.conf

Add Client To Server

We now need to tell the server to allow the client to be able to connect. This is similar to how we add a public SSH key to the $HOME/.ssh/authorized_keys file to tell the server we allow a client to connect with their SSH Key.

Run the following BASH script to append the peer connection details to your WireGuard interface's configuration file:

WIREGUARD_INTERFACE_NAME="wg0"


# Specify the *public* key of the client
CLIENT_PUBLIC_KEY="xxxxxxxxxxxxxxxxxxxx"


# Specify the IP address you gave the client in their `Address` setting but use /32 for the CIDR
# so that the client can only use that single IP.
CLIENT_TUNNEL_IP="10.172.0.2/32"

echo "
[Peer]
PublicKey = ${CLIENT_PUBLIC_KEY}
AllowedIPs = ${CLIENT_TUNNEL_IP}
" >> /etc/wireguard/${WIREGUARD_INTERFACE_NAME}.conf

... or you can do it manually, with the following template:

[Peer]
PublicKey = ${CLIENT_PUBLIC_KEY}
AllowedIPs = ${CLIENT_TUNNEL_IP}

Alternative Method

The alternative would be to just run the following command to add the client to wireguard interface:

WIREGUARD_INTERFACE="wg0"
CLIENT_PUBLIC_KEY="xxxxxxxxxxxxxxxxxxxx"


# Specify the IP address you gave the client in their `Address` setting but use /32 for the CIDR
# so that the client can only use that single IP.
CLIENT_TUNNEL_IP="10.172.0.2/32"

sudo wg set $WIREGUARD_INTERFACE peer ${CLIENT_PUBLIC_KEY} allowed-ips ${CLIENT_TUNNEL_IP}

However, that relies on having set SaveConfig to true for it to be remembered, and relies on Wireguard overwriting your config file for the client to be kept, and you not having to keep entering the line. The downside to this is that it will also write in the endpoint for the client, which is not ideal in which your client's may likely change IP such as a laptop you travel with. Thus I go with just adding the peers manually to the config file, and having SaveConfig set to false.

Using The Wireguard Tunnel

Now in order to get the tunnel running and make use of it, you first need to run the connect/up command shown below on the server first, before then running it on the client.

Connect

Finally, after all of that configuration, you should be able to use the following command on your client in order to create the WireGuard tunnel connection to the server and start making use of it:

WIREGUARD_INTERFACE_NAME="wg0"
sudo wg-quick up $WIREGUARD_INTERFACE_NAME

Using Filepath Instead

If you don't like sticking the configuration files in the /etc/wireguard folder, and placed them somewhere else, then you can just specify the full path to the configuration file instead:

CONFIG_FILEPATH="/path/to/my/wireguard/config.conf"
sudo wg-quick up $CONFIG_FILEPATH

Disconnect

To disconnect, you an then run the down command like so:

WIREGUARD_INTERFACE_NAME="wg0"
sudo wg-quick down $WIREGUARD_INTERFACE_NAME

Show WireGuard Status

If you wish to see your current wireguard connections and how much traffic is being sent over them, you can run the following command:

sudo wg show

Taking It Further With Web Proxying

I tested running Nginx proxy manager on the WireGuard server, and having it forward the traffic to the WireGuard client IP address. This actually worked without a hitch, and I had expected it to fail due to the fact that Docker likes to mess with the iptables rules. If you want to run the Nginx proxy manager on a public IP, and create a client for each of your home VPS's, this would absolutely work. You would have to create a tunnel for each server though which is a bit tedious!

Using a web proxy allows you to know where the traffic is really coming from, using the forwarding headers in the request. If you just use port forwarding, then all traffic will appear as if it is just coming from your WireGuard server. This impacts things like your WAF etc.

Taking It Further With Port Forwarding

I wanted to see if I could forward incoming traffic on my eth0 interface through to one of my WireGuard clients. For testing purposes, I tried forwarding TCP traffic on port 8080 to my client's port 80, in order to test serving up web content on a custom port. This worked by having the following PostUp and PostDown rules:

# Accept forwarding from wg0 -> eth0 (traditional vpn)
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT

# Accept forwarding from eth0 -> wg0 (tunnelling into home NAT e.g. cloudflare tunnels)
PostUp = iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -j ACCEPT

# forward incoming port 8080 to NAT on port 80
PostUp = iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.172.0.2:80
PostDown = iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 8080 -j DNAT --to-destination 10.172.0.2:80

# set up masquerading on wg0 (NAT tunnel)
PostUp = iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
PostDown = iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE

# set up masquerading on eth0 (traditional VPN)
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE;

This assumes your public interface is named eth0, your WireGuard client is on a "made up" IP of 10.172.0.2 Many online tutorials use PreUp instead of PostUp but I found this would cause things to break. One has to test this works by using a different computer than the one that is acting as the client that the traffic is being forwarded to!

Debugging

Vultr Instances

When I tried to set this up on Vultr, I found that the instance came pre-deployed with a UFW firewall in place that was preventing things from working. I fixed this by running the following command to disable this firewall:

sudo ufw disable

Then I just rebooted the instance.

Since we use iptables for network manipulation, I suggest you add the relevant iptables rules manually/carefully to implement your own firewall. That way you can be sure you take everything into account, having the full context.

I also tested on DigitalOcean which did not have this issue.

Interface Name

Also, Vultr instances will have an interface called enp1s0 instead of DigitalOcean's eth0.

References

Last updated: 16th May 2025
First published: 15th May 2025

This blog is created by Stuart Page

I'm a freelance web developer and technology consultant based in Surrey, UK, with over 10 years experience in web development, DevOps, Linux Administration, and IT solutions.

Need support with your infrastructure or web services?

Get in touch