In today’s world, VPNs aren’t a must to keep your personal data safe online, unless you’re on holiday in a hotel or café with a bunch of open Wi-Fi networks, which you probably shouldn’t connect to anyway without using encryption to protect your data. Even so, protecting my privacy is my top priority, wherever I am and whatever I’m doing. That’s why I always use a VPN as an extra layer of protection, and it’s always switched on on my computer and phone.

I would say that WireGuard is one of the best VPN technologies out there at the moment. It’s modern, lightweight, and highly effective. It’s built for speed and security, with strong encryption that doesn’t drain your battery or consume excessive system resources. Another plus is that it’s an open-source solution, which is exactly what we’re looking for.

However, a VPN alone isn’t enough to fully secure your connection. If your DNS queries aren’t encrypted, they can be intercepted and monitored, undermining your privacy. That’s why it’s vital to use encrypted DNS protocols like DNS over HTTPS (DoH) or DNS over TLS (DoT). These protocols ensure your DNS queries remain private and protected from tampering or snooping.

Basically, if you combine a reliable VPN like WireGuard with encrypted DNS, you’ll have strong protection for your internet access. In this article, I’ll give you a quick tutorial on how to install WireGuard on a server and DNSCrypt to encrypt DNS protocols through DoH.

Let’s dive into the installation and configuration of WireGuard and DNSCrypt.


Steps for installing WireGuard and DNSCrypt on a server running Debian or Ubuntu#

First, we need to update your server. It’s a good idea to do this regularly to keep things secure and stable.

sudo apt update
sudo apt list --upgradable
sudo apt upgrade

Next, install WireGuard and DNSCrypt (and UFW if you haven’t already).

sudo apt install wireguard dnscrypt-proxy ufw

Configuring WireGuard#

WireGuard uses a system called Cryptokey Routing to manage VPN tunnels. In simple terms, it checks public keys that are linked to specific IP addresses to securely route traffic through the VPN.

To set up WireGuard properly, you’ll need a private key and a public key—these keys are essential for creating and connecting to your VPN interface.

Let’s walk through how to create a new WireGuard interface and generate the necessary key pair on your server.

Step 1: Generate the Private Key#

Run the following command to generate a new WireGuard private key and save it to /etc/wireguard/server_private.key:

sudo wg genkey | sudo tee /etc/wireguard/server_private.key

After running the command, you’ll see your newly generated private key, which will look something like this:

8MWigFiRt1F3/2ytArJ/mI9EZ7oSRE87dHpLP62E3mQ=

Important: Make sure to copy and securely store this key. You’ll need it later, and you should never share your private key with anyone.

Step 2: Set Secure Permissions for the Private Key#

To keep your private key safe, it’s important to restrict access so that only the root user can read it. You can do this by changing the file permissions to 600:

sudo chmod 600 /etc/wireguard/server_private.key

Why this matters: Setting the correct permissions ensures that no other users on the system can view or modify your private key.

Step 3: Generate the Public Key#

Now, let’s create the public key that matches your private key. Run the following command to generate it and save it to /etc/wireguard/server_public.key:

sudo cat /etc/wireguard/server_private.key | wg pubkey | sudo tee /etc/wireguard/server_public.key

Once the command runs, you’ll see your public key displayed, which will look something like this:

0G5VzvG9RRnFjeWGJDt0ld4iu2G9EAYMYs6WrIqrFzA=

Tip: This public key can safely be shared with peers you want to connect to. It’s used to identify your server in the VPN tunnel, but it doesn’t compromise your security.

Step 4: Check Your Server’s Network Interface#

Before setting up WireGuard, you’ll need to know the name of your server’s main network interface and its public IP address. Run the following command to list all available network interfaces:

ip a

Look for the primary interface in the output. It will look something like this:

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: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether aa:bb:cc:dd:ee:ff brd ff:ff:ff:ff:ff:ff
    altname enp5s0
    inet 203.0.113.10/32 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 2001:db8::/64 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::aabb:ccff:fedd:eeff/64 scope link 
       valid_lft forever preferred_lft forever
4: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
    link/none 
    inet 10.8.0.1/24 scope global wg0
       valid_lft forever preferred_lft forever

In this example:

  • The main public network interface is eth0.
  • The public IP address is 203.0.113.10.

Tip: WireGuard will use your main network interface to forward traffic to the internet. Make sure to use the correct interface name for your server in the next steps.

Step 5: Create the WireGuard Configuration File#

Next, you’ll create the WireGuard configuration file using a text editor like Nano:

sudo nano /etc/wireguard/wg0.conf

Add the following configuration to the file. Remember to:

  • Replace 8MWigFiRt1F3/2ytArJ/mI9EZ7oSRE87dHpLP62E3mQ= with the private key you generated earlier.
  • Replace eth0 with your main network interface name if it’s different.
[Interface]
Address = 10.8.0.1/24
MTU = 1420
SaveConfig = true
PostUp = ufw route allow in on wg0 out on eth0
PostUp = iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
PreDown = ufw route delete allow in on wg0 out on eth0
PreDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
ListenPort = 51820
PrivateKey = 8MWigFiRt1F3/2ytArJ/mI9EZ7oSRE87dHpLP62E3mQ=    # Replace with your server private key

When you’re done, save and close the file.

The wg0.conf file is the main configuration file for your WireGuard VPN interface. It tells WireGuard how to set up and manage the VPN tunnel on your server. Each part of the file plays a specific role.

Setting Purpose
[Interface] Starts interface configuration
Address Server’s internal VPN IP
MTU Maximum packet size
SaveConfig Auto-save changes
PostUp/PreDown Firewall and NAT setup/cleanup
ListenPort Port for VPN connections
PrivateKey Server’s unique VPN identity key

Here’s what each part does:

[Interface]#

This section defines the local WireGuard VPN interface (in this case, wg0). All the settings underneath apply to this server’s VPN endpoint.

Address = 10.8.0.1/24#

This is the internal VPN IP address assigned to the server.

  • 10.8.0.1 is like the “gateway” for VPN clients.
  • /24 means the VPN network supports addresses from 10.8.0.1 to 10.8.0.254.

MTU = 1420#

MTU stands for Maximum Transmission Unit.

  • It defines the largest packet size that can pass through the VPN tunnel.
  • 1420 is a commonly recommended size for WireGuard to prevent packet fragmentation.

SaveConfig = true#

When this is set to true, WireGuard will automatically save any changes made to the configuration at runtime. Optional: If you prefer to manage the configuration manually, you can set this to false.

PostUp and PreDown commands#

These run automatically when the VPN starts (PostUp) or stops (PreDown).

PostUp commands:#

  • ufw route allow in on wg0 out on eth0 This allows traffic from the VPN (wg0) to pass through the server’s main network interface (eth0).

  • iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE This sets up NAT (Network Address Translation), which is required to route VPN traffic to the internet using the server’s public IP.

PreDown commands:#

These reverse the PostUp settings when the VPN stops:

  • ufw route delete allow in on wg0 out on eth0 This removes the VPN routing rule.

  • iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE This deletes the NAT rule to clean up.

ListenPort = 51820#

This is the UDP port WireGuard will listen on for incoming VPN connections.

  • 51820 is WireGuard’s default port, but you can change it if needed.

PrivateKey = 8MWigFiRt1F3/2ytArJ/mI9EZ7oSRE87dHpLP62E3mQ=#

This is the server’s private key.

  • It authenticates and encrypts traffic for the VPN.
  • Important: Keep this private! Never share it.

Generate WireGuard Client Configurations#

For each WireGuard client (like your phone, laptop, or another server) to connect to your VPN, it needs a unique key pair. You’ll also need to add each client’s public key to your server’s configuration to allow them to connect.

Follow these steps to create a new WireGuard client configuration and enable secure VPN tunnels.

Step 1: Generate the Client Private Key#

Let’s start by creating a new private key for your client. This key will be used to authenticate the client in your VPN network.

sudo wg genkey | sudo tee /etc/wireguard/client1_private.key

Tip: You can replace client1 with any name you like (for example, laptop, phone, or workpc) to help you keep track of your devices.

Step 2: Generate the Client Public Key#

Now, create the public key for your client based on the private key you just generated:

sudo cat /etc/wireguard/client1_private.key | wg pubkey | sudo tee /etc/wireguard/client1_public.key

What this does: The public key is safe to share and will be added to your server’s WireGuard configuration to authorize this client.

Step 3: View the Client Private Key#

To set up the client device later, you’ll need to copy its private key. Run the following command to display it:

sudo cat /etc/wireguard/client1_private.key

Example output:

UHkIk3+cj6Lo3Jc21jHkCI6/90MHBDCALHsGz92BsVA=

Important: This private key should only be stored on the client device. Never share it publicly, as it’s used to secure the connection.

Step 4: View the Client Public Key#

Now, let’s display the client’s public key so we can add it to the server later:

sudo cat /etc/wireguard/client1_public.key

Example output:

S3Em6/sSGfkkeGShQPmJAIXb0hALHHcv1jmjWEBbdAU=

Tip: Copy this public key to your clipboard — you’ll need it soon.

Step 5: Create the Client Configuration File#

Next, we’ll create a WireGuard configuration file for the client.

sudo nano /etc/wireguard/client1.conf

Step 6: Add the Client Configuration Details#

Paste the following example into the client1.conf file. Make sure to replace:

  • UHkIk3+cj6Lo3Jc21jHkCI6/90MHBDCALHsGz92BsVA= with your generated client private key
  • 0G5VzvG9RRnFjeWGJDt0ld4iu2G9EAYMYs6WrIqrFzA= with your server public key
  • 203.0.113.10 with your actual server’s public IP address
[Interface]
PrivateKey = UHkIk3+cj6Lo3Jc21jHkCI6/90MHBDCALHsGz92BsVA=   # Client Private Key
Address = 10.8.0.2/24
DNS = 8.8.8.8

[Peer]
PublicKey = 0G5VzvG9RRnFjeWGJDt0ld4iu2G9EAYMYs6WrIqrFzA=    # Server Public Key
AllowedIPs = 0.0.0.0/0
Endpoint = 203.0.113.10:51820                               # Server public IP
PersistentKeepalive = 15

Save and close the file when you’re done.

Configuration Breakdown:#

  • PrivateKey: The unique private key for the client.
  • Address: The VPN IP address for the client. Each client should have a unique address (e.g. 10.8.0.2 for client 1, 10.8.0.3 for client 2).
  • DNS: Optional, but here it’s set to Google DNS (8.8.8.8). Later, we will change it to our own DNSCrypt!
  • PublicKey: The server’s public key, used to encrypt traffic to the server.
  • AllowedIPs: 0.0.0.0/0 routes all the client’s internet traffic through the VPN.
  • Endpoint: The public IP address and port where the WireGuard server is listening.
  • PersistentKeepalive: Keeps the connection alive by sending a ping every 15 seconds, especially useful if the client is behind NAT.

Step 7: Move the Client Config for Easy Access#

Let’s copy the client configuration file to your home directory so you can easily transfer it to the client device:

sudo cp /etc/wireguard/client1.conf ~/client1.conf

Note: This prevents permission issues when downloading the file.

Step 8: Add the Client to the Server Configuration#

Now, open your WireGuard server’s configuration file to add the new client’s details.

sudo nano /etc/wireguard/wg0.conf

Step 9: Add the New Client as a Peer#

Scroll to the bottom of the server configuration and add the following section. Be sure to replace S3Em6/sSGfkkeGShQPmJAIXb0hALHHcv1jmjWEBbdAU= with the public key you generated for the client.

[Peer]
PublicKey = S3Em6/sSGfkkeGShQPmJAIXb0hALHHcv1jmjWEBbdAU=   # Client public key
AllowedIPs = 10.8.0.2/32

Explanation:

  • PublicKey: The client’s public key that authorizes them to connect.
  • AllowedIPs: Specifies the IP address assigned to this client (in this case, 10.8.0.2). The /32 ensures only this address is used by this client.

Save and close the file.


Set Up DNSCrypt-Proxy for DNS over HTTPS (DoH) and DNS Filtering#

Using encrypted DNS is an essential step to protect your online privacy and prevent DNS-based tracking or manipulation. With dnscrypt-proxy, you can encrypt your DNS traffic, use trusted DNS resolvers, and even block specific domains using customizable blacklists.

Step 1: Configure DNSCrypt-Proxy#

Open the DNSCrypt-Proxy configuration file:

sudo nano /etc/dnscrypt-proxy/dnscrypt-proxy.toml

Below is a breakdown of important options you should configure:

# DNSCrypt-Proxy will listen on the VPN server's private WireGuard IP on port 53
listen_addresses = ['10.8.0.1:53']

# Use a secure DNS provider that supports filtering and DoH (this example uses Quad9)
server_names = ['quad9-dnscrypt-ip4-filter-pri']

# Enable DNSCrypt and DoH servers
ipv4_servers = true
ipv6_servers = false
dnscrypt_servers = true
doh_servers = true

# Enable DNS caching for faster lookups
cache = true
cache_size = 4096
cache_min_ttl = 2400
cache_max_ttl = 86400
cache_neg_min_ttl = 60
cache_neg_max_ttl = 600

# Enable DNS query logging
[query_log]
file = '/var/log/dnscrypt-proxy/query.log'
format = 'tsv'

# Log non-existing domain queries (NXDOMAIN)
[nx_log]
file = '/var/log/dnscrypt-proxy/nx.log'
format = 'tsv'

# Quad9 resolver source configuration
[sources.'quad9-resolvers']
urls = ['https://www.quad9.net/quad9-resolvers.md']
minisign_key = 'RWQBphd2+f6eiAqBsvDZEBXBGHQBJfeG6G+wJPPKxCZMoEQYpmoysKUN'
cache_file = 'quad9-resolvers.md'
prefix = 'quad9-'

# Keep the default public resolver list for flexibility
[sources.'public-resolvers']
url = 'https://download.dnscrypt.info/resolvers-list/v2/public-resolvers.md'
cache_file = '/var/cache/dnscrypt-proxy/public-resolvers.md'
minisign_key = 'RWQf6LRCGA9i53mlYecO4IzT51TGPpvWucNSCh1CBM0QTaLn73Y7GFO3'
refresh_delay = 72
prefix = ''

Step 2: Enable Domain Filtering (Blacklist)#

DNSCrypt-Proxy makes it easy to block unwanted domains using a blacklist file. In the configuration file, enable the blacklist:

[blacklist]
blacklist_file = '/etc/dnscrypt-proxy/blacklist.txt'

You can block specific domains by adding them to blacklist.txt:

sudo nano /etc/dnscrypt-proxy/blacklist.txt

Example:

ads.example.com
tracking.example.net
malicious-site.com

Important: The blacklist file only works for domain names. If you want to block IP addresses, you need to use a separate IP block list. DNSCrypt-Proxy does not support mixing domains and IPs in the same blacklist file.

To block IP addresses, create a separate file and configure it in the ip_blacklist section:

[ip_blacklist]
blacklist_file = '/etc/dnscrypt-proxy/ip-blacklist.txt'

Example IP blacklist (ip-blacklist.txt):

203.0.113.10
198.51.100.22

Step 3: Restart DNSCrypt-Proxy#

After saving all changes, restart the DNSCrypt-Proxy service to apply your new configuration:

sudo systemctl restart dnscrypt-proxy

You can verify the service is running properly with:

sudo systemctl status dnscrypt-proxy

Look for Active: active (running) to confirm.

Step 4: Test DNS Resolution#

Try querying a domain using dig or nslookup to verify that your DNS requests are being handled by your DNSCrypt server:

dig @10.8.0.1 example.com

You should see a fast response, and if the domain is blacklisted, you will not receive a valid IP address.

You can also check the log files:

sudo tail -f /var/log/dnscrypt-proxy/query.log

Step 5: Add DNSCrypt-Proxy to Your WireGuard Configuration#

In your WireGuard client configuration, set the DNS to the VPN server’s private WireGuard IP (where DNSCrypt-Proxy is listening).

[Interface]
PrivateKey = UHkIk3+cj6Lo3Jc21jHkCI6/90MHBDCALHsGz92BsVA=
Address = 10.8.0.2/24
DNS = 10.8.0.1 # DNSCrypt-Proxy address

[Peer]
PublicKey = 0G5VzvG9RRnFjeWGJDt0ld4iu2G9EAYMYs6WrIqrFzA=
AllowedIPs = 0.0.0.0/0
Endpoint = 203.0.113.10:51820
PersistentKeepalive = 15

This ensures all DNS traffic from your VPN client will be securely sent to the DNSCrypt-Proxy on the VPN server.

Step 6: Test DNS Resolution Over VPN#

Once your VPN tunnel is active, test DNS resolution through the secure proxy:

dig @10.8.0.1 example.com

This confirms that your VPN clients are successfully using the encrypted DNS resolver provided by your DNSCrypt-Proxy setup.


UFW Configuration for SSH, DNS and WireGuard#

First, see if UFW is installed and running (if it’s inactive, don’t worry—you’ll enable it later):

sudo ufw status verbose

Step 1: Allow Essential Services#

Before enabling UFW, make sure you allow SSH (to avoid locking yourself out):

sudo ufw allow ssh

Go ahead and allow those WireGuard and DNSCrypt ports. WireGuard usually uses UDP port 51820 by default, and DNSCrypt uses port 53:

sudo ufw allow 51820/udp        # WireGuard
sudo ufw allow 53/udp           # DNSCrypt (DNS over UDP)

If your DNSCrypt setup is also handling TCP (some DNS servers support TCP fallback), you may also want to allow TCP port 53:

sudo ufw allow 53/tcp

Step 2: Enable the Firewall#

Activate UFW:

sudo ufw enable

You can check whether the rules are active:

sudo ufw status numbered

Example output:

Status: active

     To                         Action      From
     --                         ------      ----
[ 1] 51820/udp                  ALLOW IN    Anywhere                  
[ 2] 22/tcp                     ALLOW IN    Anywhere                  
[ 3] 53/udp                     ALLOW IN    Anywhere                  
[ 4] Anywhere on eth0           ALLOW FWD   Anywhere on wg0           
[ 5] 51820/udp (v6)             ALLOW IN    Anywhere (v6)             
[ 6] 22/tcp (v6)                ALLOW IN    Anywhere (v6)             
[ 7] 53/udp (v6)                ALLOW IN    Anywhere (v6)             
[ 8] Anywhere (v6) on eth0      ALLOW FWD   Anywhere (v6) on wg0  

For maximum security, make sure UFW denies all other incoming connections:

sudo ufw default deny incoming
sudo ufw default allow outgoing

This means only ports you specifically allow (like SSH, WireGuard and DnsCrypt) will be open. If you make any changes, just reload the ufw to accept the new rules:

sudo ufw reload

Step 3: Enable IP Forwarding#

Enable IPv4 packet forwarding to allow traffic between your VPN clients and the internet:

echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf

Output:

net.ipv4.ip_forward = 1

Then just reload your sysctl configuration to apply the changes:

sudo sysctl -p

Step 4: Configure NAT#

Enable your main server network interface (in this case, it’s eth0) to translate network requests from the WireGuard VPN subnet 10.8.0.0/24:

sudo iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

The iptables rule will be lost after a reboot unless you save it.

On Ubuntu/Debian, you can install iptables-persistent to save rules across reboots:

sudo apt install iptables-persistent

Save current iptables rules:

sudo netfilter-persistent save

To reload the rules at boot, the service should automatically enable itself:

sudo systemctl enable netfilter-persistent

Connect Your VPN Client to the WireGuard VPN Server#

WireGuard clients connect to your VPN server using configuration files. To connect to your WireGuard VPN server and test the connection using the ping utility, follow these steps.

Step 1: Download the WireGuard Client Configuration#

From your VPN server, securely transfer the client configuration file to your local device using scp. Run this command in your local terminal (replace wireguard-server-ip with your server’s actual IP address and linuxuser with your server username):

scp linuxuser@wireguard-server-ip:client1.conf .

Step 2: Install WireGuard on Your Device#

Download and install the latest WireGuard VPN application for your device .

Step 3: Import the VPN Tunnel#

  1. Open the WireGuard application on your device.

  2. Click Add Tunnel or Import tunnel(s) from file to load your client configuration file.

Import tunnel(s) from file

  1. Browse and open the client configuration file you just downloaded. It should now appear in your tunnel list.

Step 4: Activate the VPN Tunnel#

  • Click Activate to connect to your WireGuard VPN server.

Imported tunnel

  • Verify that your tunnel is connected and review the connection statistics.

Tunnel active

Step 5: Test VPN Connectivity#

  • Open a terminal window on your device.
  • Test connectivity to the WireGuard VPN server’s private IP address (e.g., 10.8.0.1) using the ping utility:
ping -c 4 10.8.0.1

Example successful output:

PING 10.8.0.1 (10.8.0.1): 56 data bytes
64 bytes from 10.8.0.1: icmp_seq=0 ttl=64 time=31.234 ms
64 bytes from 10.8.0.1: icmp_seq=1 ttl=64 time=31.003 ms
64 bytes from 10.8.0.1: icmp_seq=2 ttl=64 time=32.522 ms
64 bytes from 10.8.0.1: icmp_seq=3 ttl=64 time=43.121 ms

--- 10.8.0.1 ping statistics ---
4 packets transmitted, 4 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 31.003/34.470/43.121/5.028 ms

Success! If you see similar results, your VPN tunnel is active and you’re securely connected to your WireGuard VPN server.


Conclusion#

By following this guide, you’ve successfully built a secure and private VPN environment using WireGuard and DNSCrypt-Proxy. This setup not only encrypts your internet traffic but also ensures your DNS requests are protected through DNS over HTTPS and customizable filtering, adding an extra layer of privacy and security.

With proper firewall rules in place, IP forwarding enabled, and NAT configured, your VPN server is now fully capable of routing traffic for connected clients while blocking unwanted domains and potentially harmful connections.

This solution is lightweight, fast, and highly configurable, making it ideal for personal use or small teams that value both security and control. Remember to:

  • Regularly monitor your DNSCrypt logs.
  • Keep your WireGuard keys secure.
  • Periodically review your firewall settings and DNS blocklists.

If you ever need to scale, add more clients, or improve filtering, your foundation is solid and easy to expand.

Thank you for following along—enjoy your private and secure VPN!