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
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
In WireGuard, the PostUp
and PostDown
commands are part of the wg-quick
tool. These hooks are triggered automatically:
-
PostUp
runs afterwg-quick up <interface>
, and is typically used to set up networking rules. -
PostDown
runs afterwg-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:
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.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.
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
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.
Comments
Post a Comment