Creating a VPN tunnel via Wireguard #2 (Wireguard)

To establish secure communication between a WireGuard server and client, we need to understand the basics of how WireGuard handles encryption.

WireGuard uses asymmetric encryption, a cryptographic approach where each party generates a pair of keys: one public and one private.

Here’s a quick breakdown of how it works:

  • The server generates a key pair: a public key and a private key.

  • The client also generates its own public and private key pair.

  • The server stores the client’s public key in order to decrypt incoming packets from the client and verify its identity.

  • The client stores the server’s public key to encrypt packets sent to the server.

  • When a packet is received, each side uses its private key to decrypt data encrypted with its corresponding public key.

This ensures that private keys are never shared or transmitted, making the communication secure by design. If you’re familiar with SSH key authentication or end-to-end encryption (E2EE) in messaging apps, this model will feel very familiar. It's the same fundamental concept—secure communication using asymmetric cryptography where only public keys are shared.


Figure. 1


By default, the WireGuard server runs on UDP port 51820. WireGuard is designed to be minimal and silent—meaning it doesn’t respond to unsolicited traffic, making it harder to detect on the network.

In Figure 1, you can see a high-level overview of the infrastructure we'll be setting up. We'll rely on an external DNS server on the internet to resolve domain names. Later in this series, we’ll introduce dnsmasq to forward DNS queries as needed.

As shown in Figure 1, we’ll need to generate public/private key pairs for both the server and the client. Fortunately, WireGuard includes a built-in tool that makes key generation straightforward.

To streamline the setup process, let’s write a script to automate key generation and configuration. While you can definitely do this manually, I prefer having a reusable, automated setup—making it faster and more consistent when spinning up new environments in the future.


Generate public/private keys

The wg genkey command is used to generate a private key. To derive the corresponding public key, we pipe the private key into the wg pubkey command, like so:

SERVER_PRIVATE_KEY=$(wg genkey)
SERVER_PUBLIC_KEY=$(echo $SERVER_PRIVATE_KEY | wg pubkey)

CLIENT_PRIVATE_KEY=$(wg genkey)
CLIENT_PUBLIC_KEY=$(echo $CLIENT_PRIVATE_KEY | wg pubkey)


Server configuration

Our internal network will use the IPv4 subnet 10.8.0.1/24, which falls within the range of private IP addresses. This subnet will be used to assign internal addresses to both the WireGuard server and clients.

We also want to support IPv6. For that, we can assign any random private 128-bit IPv6 address block, as long as it’s reserved for internal use only. These addresses won’t be routed on the public internet, so you’re free to choose a range that suits your needs.

If you’re unsure which IPv6 address block to use, you can safely select one from the ULA (Unique Local Address) range: fc00::/7. A commonly recommended practice is to generate a random /48 subnet within this range.

Here’s an example:

  • IPv6 subnet: fd42:42:42::/48


Address = 10.8.0.1/24,fd86:ea04:1111::1/64
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -I POSTROUTING -o enp1s0 -j MASQUERADE
PostUp = ip6tables -t nat -I POSTROUTING -o enp1s0 -j MASQUERADE

PostDown = ip6tables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o enp1s0 -j MASQUERADE

SaveConfig = true
MTU = 1280
ListenPort = 51820
PrivateKey = $SERVER_PRIVATE_KEY

[Peer]
PublicKey = $CLIENT_PUBLIC_KEY
AllowedIPs = 10.8.0.1/24, fd86:ea04:1111::2/128


In WireGuard, the PostUp and PostDown commands are part of the wg-quick tool. These hooks are triggered automatically:

  • PostUp runs after wg-quick up <interface>, and is typically used to set up networking rules.

  • PostDown runs after wg-quick down <interface>, and is used to tear down or clean up those rules.

Let’s walk through why these commands are important, using a couple of common examples:



1) iptables -A FORWARD -i wg0 -j ACCEPT 

This rule allows packets arriving on the wg0 interface (your WireGuard tunnel) to be forwarded to other interfaces. In simpler terms, it enables VPN clients connected to wg0 to access the internet or other parts of the network via the host machine.

2) iptables -t nat -I POSTROUTING -o enp1s0 -j MASQUERADE 

This rule tells the system to masquerade (i.e., rewrite) the source IP address of outgoing packets leaving the enp1s0 interface. This is crucial for routing VPN client traffic to the internet:

  • It uses the POSTROUTING chain of the nat table.

  • It ensures that outgoing packets appear as if they originated from the server’s public IP.

  • This allows return traffic to find its way back to the correct VPN client.


We also explicitly set the MTU to 1280: Why? Because the default MTU provided by WireGuard can sometimes lead to issues—especially with SSH connections, which may get refused or hang due to packet fragmentation. Setting a safe MTU like 1280 helps avoid such issues.

At the end of the server configuration, don’t forget to define:

  • The client’s public key

  • The allowed IP range for the client

This tells the server which keys to accept and what IP range the client is expected to use within the VPN.


Client configuration

[Interface]
PrivateKey = $CLIENT_PRIVATE_KEY
Address = 10.8.0.2/24, fd86:ea04:1111::2/64
DNS = 185.228.168.9 # Change this at some point to the 10.8.0.1:53 -> dnsmasque
MTU = 1280

[Peer]
PublicKey = $SERVER_PUBLIC_KEY
Endpoint = $VPN_IP:51820
# AllowedIPs = 10.8.0.1/24
AllowedIPs = 0.0.0.0/0,::/0 # route all my traffic
PersistentKeepalive = 25


As shown in Figure 1, we also need to specify a DNS server for our VPN clients. There are several publicly available DNS servers you can use. Some of the most popular options include:

  • Cloudflare DNS: 1.1.1.1

  • Google DNS: 8.8.8.8

However, keep in mind that even if your traffic is encrypted via VPN, your DNS queries can still reveal which websites you're visiting. That means your ISP (Internet Service Provider) or the DNS provider (like Google or Cloudflare) could still see your browsing activity.

To improve privacy, in the next post, I’ll demonstrate how to set up dnsmasq on your server. This allows you to use Vultr’s DNS servers from within your instance, providing more control. That said, even Vultr will still have visibility into DNS queries—so it’s not 100% foolproof, but it's a step in the right direction.

The VPN_IP refers to the public IP address of your WireGuard server, which the client needs to connect to.

To forward all internet traffic through the VPN, we use the following in the client config:

  • 0.0.0.0/0 forwards all IPv4 traffic through the VPN.

  • ::/0 does the same for all IPv6 traffic.

This effectively routes all client internet traffic through your WireGuard server, providing a full tunnel VPN setup.


The full code can be found in my Github page


Comments