Contents

wireguard VPN server

Wireguard is a tool for creating VPNs, it’s modern, secure and performant. Let’s see how to create a VPN server and send all our traffic through it.

prerequisites

We’ll need a server with at lease these specs:

  • 1 core
  • 1GB of RAM (it works even with less than that, but let’s try to be reasonable)
  • 5GB of storage
  • bandwidth!
  • linux with a recent kernel

Pay attention to the available bandwidth: if you’re so lucky to have a fast internet connection, let’s say >100 mbit, we need to have at least the same bandwidth on the server.

As of today, we can rent a VPN with these specs for less than 5€/month.

In this howto, we’ll consider these operating systems:

  • Debian 13 Trixie (as client or server)
  • Ubuntu Server 26.04 (as server)
  • Ubuntu 26.04 (as client)

definitions

We’ll use these conventions:

  • Tunnel network: 192.168.199.1/24
  • Server IP: 192.168.199.1
  • Clients IP: from 192.168.199.2
  • Server external IP: 11.22.33.44
  • Server external interface: ens18

server setup

For simplicity, suppose we start with a fresh server, to which we have access as root user.

Let’s install some packages:

apt install wireguard iptables resolvconf qrencode unattended-upgrades unbound

Wireguard needs a public and a private key for each connection member, so let’s create a key pair for the server:

wg genkey | tee /root/server.privkey
sL1UKqiqMraaQarCy7UIUpkQnpgzR6Gm+L1RCgp2TEM=

cat /root/server.privkey | wg pubkey | tee /root/server.pubkey
UgvmsCnMWWW9XAHNbc6+lkbCLSF5Mt3b85A4PrG4mRE=

To simplify the configuration, we’ll use a wireguard tool called wg-quick: this allows the management of each wireguard interface in a file; moreover, it’ll manage IP addresses and routing at each interface ativation/deactivation. The public keys and IP addresses of every other VPN member will be saved in this configuration file. Let’s start by creating the configuration file (the file name MUST be [interface_name].conf) and populate it:

touch /etc/wireguard/wg0.conf

cat > /etc/wireguard/wg0.conf <<EOF
[Interface]
Address = 192.168.199.1/24
SaveConfig = true
ListenPort = 51194
PrivateKey = sL1UKqiqMraaQarCy7UIUpkQnpgzR6Gm+L1RCgp2TEM=
MTU = 1420
PostUp = iptables -A FORWARD -i wg0 -o ens18 -j ACCEPT
PostUp = iptables -A FORWARD -i ens18 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
PostUp = iptables -t mangle -A FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
PostDown = iptables -D FORWARD -i wg0 -o ens18 -j ACCEPT
PostDown = iptables -D FORWARD -i ens18 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o ens18 -j MASQUERADE
PostDown = iptables -t mangle -D FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
EOF

systemctl enable wg-quick@wg0

wg-quick up wg0

wg
interface: wg0
  public key: UgvmsCnMWWW9XAHNbc6+lkbCLSF5Mt3b85A4PrG4mRE=
  private key: (hidden)
  listening port: 51194

Let’s put the server aside for a moment and check the client.

client setup (linux)

We have to replicate on the client the same commands for the server, with some little differences: clearly the key pair will be different, we’re also missing IP and port for connection receive because the connection will only be outgoing. We don’t need the iptables part.

Suppose we’re using an Ubuntu 20.04:

sudo apt install wireguard resolvconf

wg genkey | tee ~/server.privkey
AGrgj2nh5T/3VMddrno/FIOgiotgKVQ9ydjw2AHzbno=

cat ~/server.privkey | wg pubkey | tee ~/server.pubkey
7c0uRl/F4jcpEPLOTA8zs0vcpQ3lTiljYbWb2QmJ11M=

sudo touch /etc/wireguard/wg0.conf

sudo tee /etc/wireguard/wg0.conf <<EOF
[Interface]
SaveConfig = true
PrivateKey = AGrgj2nh5T/3VMddrno/FIOgiotgKVQ9ydjw2AHzbno=
Address = 192.168.199.2/32
DNS = 192.168.199.1
MTU = 1420

[Peer]
PublicKey = phoJ2IBLJXEjaJJXzcEM6TGidh/rGxCdpXvKOP0HK0E=
Endpoint = 11.22.33.44:51194
AllowedIPs = 0.0.0.0/0
# AllowedIPs = 192.168.199.0/24
PersistentKeepalive = 30
EOF

sudo systemctl enable wg-quick@wg0

sudo wg-quick up wg0

in summary, the client has a single IP address (/32) and a DNS. In the [Peer] section, we’re pointing at a specific server IP and port (but you can also use a FQDN), as well as the public key to certify its identity and decrypt traffic.

The AllowedIPs line indicates which IP addresses are forwarded over the tunnel, in this case all. Consequently, when the VPN is active, it will be impossible to see the local network (and any other network not reachable by the VPN server) and all traffic will pass through the VPN.

client configuration on the server

The server must be instructed to accept client connections:

wg set wg0 peer 7c0uRl/F4jcpEPLOTA8zs0vcpQ3lTiljYbWb2QmJ11M= \
allowed-ips 192.168.199.2 persistent-keepalive 30

wg
interface: wg0
  public key: UgvmsCnMWWW9XAHNbc6+lkbCLSF5Mt3b85A4PrG4mRE=
  private key: (hidden)
  listening port: 51194
  fwmark: 0xca6c

peer: 7c0uRl/F4jcpEPLOTA8zs0vcpQ3lTiljYbWb2QmJ11M=
  endpoint: 3.4.5.6:51820
  allowed ips: 192.168.199.2/32
  latest handshake: 39 seconds ago
  transfer: 7.20 MiB received, 6.63 MiB sent
  persistent keepalive: every 30 seconds

wg-quick save wg0

The last command is used to immediately commit the configuration into /etc/wireguard/wg0.conf.

We still need a couple of tweaks: masquerading and DNS.

Masquerading (and forwarding)

At the moment, the traffic from wireguard VPN arrives on the server and is discarded. Forwarding must be enabled:

# live modification
sysctl -w net.ipv4.ip_forward=1

# make it permanent
echo "net.ipv4.ip_forward=1" > /etc/sysctl.d/99-forwarding.conf

Firewall rules are created and removed when wg0 interface goes up or down. Here are the rules:

iptables -A FORWARD -i ens18 -o wg0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wg0 -o ens18 -j ACCEPT
iptables -t nat -A POSTROUTING -o ens18 -j MASQUERADE
iptables -t mangle -A FORWARD -o wg0 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

Which means:

  • the traffic to be forwarded, if related to existing connections, is accepted
  • the traffic to be forwarded, coming from the VPN is accepted
  • traffic coming out from ens18 server port is masqueraded
  • response traffic going throgh VPN is “clamped” (packet size gets limited)

DNS

Client uses the DNS service on the server, so we need to configure it:

cat > /etc/unbound/unbound.conf.d/custom.conf <<EOF
interface: 0.0.0.0
access-control: 192.168.199.0/24 allow
access-control: 127.0.0.1/8 allow

forward-zone:
        name: "."
        forward-addr: 9.9.9.9
        forward-addr: 1.1.1.2
EOF

# restart unbound
systemctl restart unbound

We simply enable the DNS service on ALL interface, but we’re accepting requests only from VPN and localhost; every request is forwarded to quad9 and cloudflare DNS.

TODO

  • malware/ad filtering