Configure a Zero-Trust WireGuard VPN with Per-User Access and DNS Blocking on Linux

A basic VPN is easy to set up, but a modern network needs more than “connect and hope for the best.” A Zero-Trust WireGuard VPN is a practical way to give remote users access to only what they need, while keeping everything else blocked by default. In this tutorial you will build a WireGuard server on Linux, create per-user profiles, restrict each user to specific internal subnets, and add DNS-based blocking to reduce malware and ads on connected devices.

Prerequisites

You will need: (1) a Linux server with a public IP (Ubuntu/Debian examples are used), (2) root or sudo access, (3) a domain name (optional but useful), and (4) one or more internal networks to protect (for example, 10.10.0.0/16). You should also know which UDP port you want for WireGuard (the default is 51820/UDP).

Step 1: Install WireGuard

Update packages and install WireGuard:

Ubuntu/Debian:

sudo apt update && sudo apt install -y wireguard iptables-persistent

Enable IP forwarding so the server can route traffic between VPN and internal networks:

sudo sysctl -w net.ipv4.ip_forward=1

Make it persistent:

echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf

Step 2: Generate Server Keys

WireGuard uses simple public/private keys. Create them with tight permissions:

umask 077

wg genkey | tee /etc/wireguard/server.key | wg pubkey > /etc/wireguard/server.pub

Read the public key (you will use it in client configs):

sudo cat /etc/wireguard/server.pub

Step 3: Create the WireGuard Interface (wg0)

Create /etc/wireguard/wg0.conf. In this example, the VPN network is 10.44.0.0/24 and the server is 10.44.0.1. Replace eth0 with your public interface name (check with ip a).

[Interface]
Address = 10.44.0.1/24
ListenPort = 51820
PrivateKey = (paste contents of /etc/wireguard/server.key)
SaveConfig = false

Now add firewall/NAT rules so VPN clients can reach internal networks (and optionally the internet). Add these lines under the interface section:

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -D FORWARD -o wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

Start and enable the service:

sudo systemctl enable --now wg-quick@wg0

Verify:

sudo wg

Step 4: Add Per-User Keys and “Allow Only What’s Needed”

For Zero-Trust behavior, you should avoid giving every user full access to your entire internal network. WireGuard supports this with AllowedIPs per peer. Generate a key pair for a user (example: user “alice”):

umask 077
wg genkey | tee /etc/wireguard/alice.key | wg pubkey > /etc/wireguard/alice.pub

Decide what Alice is allowed to reach. Example: only a file server subnet 10.10.20.0/24 and the VPN IP for Alice (10.44.0.10/32). Add a peer block to /etc/wireguard/wg0.conf:

[Peer]
PublicKey = (paste contents of /etc/wireguard/alice.pub)
AllowedIPs = 10.44.0.10/32, 10.10.20.0/24
PersistentKeepalive = 25

Apply the changes by restarting WireGuard:

sudo systemctl restart wg-quick@wg0

This approach gives Alice an address on the VPN and only routes the required internal subnet through the tunnel. Everything else stays outside the VPN, reducing accidental exposure.

Step 5: Create a Secure Client Configuration

On the client side, create a config that uses Alice’s private key and the server’s public key. If your server public IP is 203.0.113.10 and the port is 51820, a minimal client config looks like this:

[Interface]
PrivateKey = (paste contents of /etc/wireguard/alice.key)
Address = 10.44.0.10/32
DNS = 10.44.0.1

[Peer]
PublicKey = (server public key)
Endpoint = 203.0.113.10:51820
AllowedIPs = 10.10.20.0/24
PersistentKeepalive = 25

Notice that the client’s AllowedIPs includes only the internal subnet she needs. That keeps her internet traffic off the VPN and limits risk if the client device is compromised.

Step 6: Add DNS Blocking (Optional but Recommended)

A simple way to reduce malicious domains is to run a lightweight DNS resolver with blocklists, such as Unbound plus a blocklist, or dnsmasq. A practical option on a small server is dnsmasq:

sudo apt install -y dnsmasq

Bind DNS to the WireGuard interface IP by editing /etc/dnsmasq.conf and adding:

listen-address=10.44.0.1
bind-interfaces

Then point clients to DNS = 10.44.0.1 (as shown earlier). For blocking, you can add entries to /etc/dnsmasq.d/blocked.conf like:

address=/example-bad-domain.com/0.0.0.0

Restart dnsmasq:

sudo systemctl restart dnsmasq

Step 7: Troubleshooting Checklist

If a client connects but cannot reach anything, check these items: (1) confirm the server is listening on UDP 51820 and the cloud firewall allows it; (2) verify AllowedIPs on both server and client match the intended access; (3) ensure IP forwarding is enabled; (4) confirm your PostUp NAT rule uses the correct public interface; and (5) run sudo wg and look for the latest handshake time and transfer counters.

With these steps, you get a modern WireGuard VPN that follows a Zero-Trust mindset: per-user identities, minimal network exposure, and optional DNS filtering for safer browsing on remote devices.

Configure Windows Server 2022 as a Secure WireGuard VPN Gateway (with NAT and Firewall Rules)

Why WireGuard on Windows Server?

WireGuard is a modern VPN protocol known for strong cryptography, fast performance, and a simple configuration model. While it is often associated with Linux, it also works well on Windows Server 2022—especially for small and mid-sized organizations that need secure remote access to internal resources without deploying a complex VPN appliance.

In this tutorial, you will set up Windows Server 2022 as a WireGuard VPN gateway, enable NAT so VPN clients can reach your internal LAN, and lock down access with Windows Firewall. The end result is a clean, maintainable remote access VPN you can scale as needed.

Prerequisites

Server requirements: Windows Server 2022 (Desktop Experience is easier for first-time setup), local admin privileges, and a static internal IP address. If you want clients to connect from the internet, you also need a public IP or port-forwarding on your edge router.

Network plan: Choose a dedicated VPN subnet that does not overlap your LAN. Example used below: VPN subnet 10.30.0.0/24, WireGuard server VPN IP 10.30.0.1, LAN subnet 192.168.10.0/24.

Step 1: Install WireGuard for Windows

Download and install WireGuard for Windows from the official site (wireguard.com). After installation, open the WireGuard application. On Windows Server, it’s best to run it interactively first to confirm the tunnel comes up correctly, and later decide whether you want it to run at startup.

In WireGuard, click Add Tunnel and choose Add empty tunnel. WireGuard will generate a key pair automatically. Keep the generated PrivateKey on the server confidential.

Step 2: Create the Server Tunnel Configuration

Paste a server configuration similar to the following. Replace placeholders with your own values. If you don’t know your public endpoint yet, you can still configure it now and update later.

Example server config (wg0):

[Interface]
Address = 10.30.0.1/24
ListenPort = 51820
PrivateKey = SERVER_PRIVATE_KEY

At this stage, do not add peers yet. Save the tunnel as something recognizable like WG-RemoteAccess.

Step 3: Enable IP Forwarding on Windows Server

To route traffic between the VPN interface and the LAN, Windows must forward IP packets. On Windows Server, this is typically controlled via registry settings.

Open PowerShell as Administrator and run:

reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters /v IPEnableRouter /t REG_DWORD /d 1 /f

Reboot the server or restart the Routing service (a reboot is the simplest way to ensure it takes effect).

Step 4: Configure NAT (So VPN Clients Can Reach the LAN)

If your LAN routers do not have a route back to the VPN subnet, NAT is the quickest reliable approach: internal systems see the VPN traffic as coming from the server’s LAN IP, and replies return without adding static routes everywhere.

Open PowerShell as Administrator and identify the WireGuard adapter name:

Get-NetAdapter

Then configure NAT. This example NATs any VPN client traffic sourced from 10.30.0.0/24:

New-NetNat -Name "WG-NAT" -InternalIPInterfaceAddressPrefix 10.30.0.0/24

This is simple and effective for remote access. In larger environments, you may prefer proper routing instead of NAT, but NAT keeps the rollout fast and reduces dependencies.

Step 5: Open the WireGuard UDP Port in Windows Firewall

WireGuard uses UDP. If the server is internet-facing (or receiving port-forwarded traffic), allow inbound UDP on your chosen port (default 51820).

Run in an elevated PowerShell:

New-NetFirewallRule -DisplayName "WireGuard UDP 51820" -Direction Inbound -Protocol UDP -LocalPort 51820 -Action Allow

If your server has multiple network profiles, consider scoping the rule to the correct interface or remote IP ranges for extra security.

Step 6: Add a Client Peer (Laptop Example)

On the client device, install WireGuard and create a new tunnel. WireGuard will generate a public/private key pair for the client. You will copy the client’s PublicKey into the server config as a peer.

Server-side peer entry:

[Peer]
PublicKey = CLIENT_PUBLIC_KEY
AllowedIPs = 10.30.0.2/32

Now configure the client tunnel like this (replace values accordingly):

Client config:

[Interface]
Address = 10.30.0.2/24
PrivateKey = CLIENT_PRIVATE_KEY
DNS = 192.168.10.10

[Peer]
PublicKey = SERVER_PUBLIC_KEY
Endpoint = vpn.yourdomain.com:51820
AllowedIPs = 192.168.10.0/24, 10.30.0.0/24
PersistentKeepalive = 25

The AllowedIPs line determines what routes go through the tunnel. The example routes only your internal LAN and the VPN subnet, not all internet traffic. If you want a full-tunnel VPN, you would use 0.0.0.0/0 (and optionally ::/0 for IPv6), but that changes your security and bandwidth planning.

Step 7: Test Connectivity and Troubleshoot

Bring up the tunnel on the server and the client. On the client, confirm you have a 10.30.0.2 address and then test:

Ping 10.30.0.1 (WireGuard server VPN IP) and then ping 192.168.10.10 (an internal host). If the VPN connects but LAN access fails, verify NAT exists (Get-NetNat) and check that Windows Firewall on the target LAN host allows the traffic.

If the client can’t handshake at all, confirm UDP/51820 is reachable from the internet (router port-forwarding, upstream firewall rules, and correct endpoint DNS). Also ensure the server’s WireGuard tunnel is active and listening on the expected port.

Hardening Tips (Recommended)

For better security, limit inbound firewall rules to known remote IP ranges if possible, and keep peer definitions tight (use /32 for individual client addresses). Avoid reusing client IPs, and document which user/device owns each peer. Finally, keep Windows Server patched and consider running WireGuard on a dedicated VM if the server also hosts critical roles.

With these steps, you now have a lean WireGuard VPN gateway on Windows Server 2022 that supports secure remote access and can be expanded by adding more peers as your team grows.

How to Deploy a Zero‑Trust WireGuard VPN with Tailscale on Ubuntu Server (2025 Guide)

Overview

This step-by-step guide shows how to deploy a zero-trust VPN using Tailscale (built on WireGuard) on Ubuntu Server. You will install the Tailscale client, log in with SSO, enable secure SSH, configure access control lists (ACLs), expose a private subnet, and optionally offer an exit node. The result is a modern, fast, and secure VPN with minimal maintenance and strong identity-based access controls—perfect for homelabs and production servers in 2025.

Why Tailscale (WireGuard) for Zero Trust

Tailscale uses the WireGuard protocol for speed and strong cryptography while removing the operational pain of traditional VPNs. Devices authenticate using your identity provider (Google, Microsoft, Okta, GitHub, and others) and connect peer-to-peer where possible. You gain per-device keys, automatic NAT traversal, policy-based access (ACLs), MagicDNS, and optional Tailscale SSH that replaces inbound firewall holes.

Prerequisites

You need an Ubuntu Server 22.04 or 24.04 host with sudo access. Ensure outbound HTTPS (TCP 443) and UDP 41641 are allowed. No inbound ports are strictly required. Have a browser handy to authenticate to your identity provider or prepare a reusable auth key from the Tailscale admin console for headless systems.

Install Tailscale on Ubuntu

Run these commands to add the official repository and install Tailscale. The snippet auto-detects your Ubuntu codename:

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(. /etc/os-release; echo $VERSION_CODENAME).noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null

curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/$(. /etc/os-release; echo $VERSION_CODENAME).tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list

sudo apt-get update && sudo apt-get install -y tailscale

Authenticate and bring the node online

For interactive login, start Tailscale and enable Tailscale SSH. This avoids exposing port 22 to the internet and lets you restrict SSH via ACLs:

sudo tailscale up --ssh --operator=$USER --accept-dns=true --accept-routes=true --advertise-tags=tag:server

If the server is headless, generate an auth key in the Tailscale admin console (preferably tagged and reusable or ephemeral) and run:

sudo tailscale up --ssh --authkey=tskey-******** --advertise-tags=tag:server

Verify connectivity with tailscale status, view the device IPs via tailscale ip -4, and test pings to another node using tailscale ping <device-or-name>.

Enable zero-trust access controls (ACLs)

Open the Tailscale admin console and switch to the ACLs page. Keep policies simple and human-readable. Example: allow your admin group to SSH to servers and let developers access staging web ports:

{
  "groups": { "group:admins": ["[email protected]"], "group:devs": ["[email protected]"] },
  "tagOwners": { "tag:server": ["group:admins"] },
  "acls": [
    { "action": "accept", "users": ["group:admins"], "ports": ["tag:server:22,2222"] },
    { "action": "accept", "users": ["group:devs"], "ports": ["tag:server:80,443,8080"] }
  ],
  "ssh": [
    { "action": "check", "src": ["group:admins"], "dst": ["tag:server"], "users": ["root", "ubuntu"] }
  ]
}

This policy makes tag:server devices manageable by admins, grants controlled port access, and restricts Tailscale SSH to authorized identities. Commit and save to enforce instantly.

Expose your LAN with a Subnet Router (optional)

If the Ubuntu host can reach a private LAN (e.g., 192.168.10.0/24), you can advertise that subnet to Tailscale peers without opening your firewall. First, enable IP forwarding:

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

echo 'net.ipv6.conf.all.forwarding=1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf

sudo sysctl --system

Now advertise the subnet routes:

sudo tailscale up --advertise-routes=192.168.10.0/24 --accept-routes=true

Approve the routes in the admin console. For UFW, permit forwarding within the LAN and from the Tailscale interface (typically tailscale0):

sudo ufw route allow in on tailscale0 out on eth0 to 192.168.10.0/24

sudo ufw reload

Set up an Exit Node (optional)

An exit node lets approved devices send all internet traffic through your Ubuntu server. This is useful for securing devices on public Wi‑Fi or egressing from a fixed IP. Enable it on the server:

sudo tailscale up --advertise-exit-node=true

In the admin console, allow the exit node and then, from a client, choose “Use exit node”. Optionally permit local LAN access while using the exit node by enabling “Allow LAN access” on the client.

Security hardening and best practices

Restrict who can reach what by tags and groups; avoid broad *:* policies. Prefer Tailscale SSH over public SSH. Disable password auth in OpenSSH (sudoedit /etc/ssh/sshd_config, set PasswordAuthentication no) and restart SSH. Keep the system current and enable unattended upgrades:

sudo apt-get install -y unattended-upgrades

sudo dpkg-reconfigure --priority=low unattended-upgrades

If you use UFW, you generally do not need to open inbound ports for Tailscale. Traffic arrives over the encrypted tunnel and is handled by tailscaled. For large fleets, use ephemeral auth keys for CI/CD runners (--ephemeral) and shorter key lifetimes. Consider using device posture checks and auto-approvers in ACLs where appropriate.

Troubleshooting quick checks

Run sudo tailscale bugreport to gather diagnostics if needed. Use tailscale netcheck to verify NAT traversal, tailscale status to view peers, and tailscale ping to test reachability. If subnets are not reachable, confirm routes are approved and that IP forwarding and UFW rules allow routed traffic. If speeds seem low, ensure direct connections are established (not relayed) and verify that CPU scaling or virtualization offloads are not limiting WireGuard throughput on the server.

What you achieved

You installed a production-ready, zero-trust WireGuard VPN with Tailscale on Ubuntu, authenticated it with SSO, enabled Tailscale SSH, enforced fine-grained ACLs, and optionally provided subnet routing and exit-node functionality. This approach is simpler, faster, and safer than legacy site-to-site or username/password VPNs, and it scales from a single VPS to a multi-site enterprise network with minimal toil.

Popular Posts

Install Ollama and Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install Ollama + Open WebUI on Ubuntu 24.04 with NVIDIA GPU Acceleration (Step-by-Step)

Install a Local AI Chatbot on Ubuntu 24.04 with Ollama and Open WebUI (Step-by-Step)

Trending Now

Recovering from Btrfs Boot Failures Using GUI Tools on Fedora

By the end of this guide the reader will be able to identify a Btrfs‑based Fedora installation, boot from a live USB, list and restore snapshots using the graphical utilities btrfs‑assistant and snapper, and verify that the system returns to a functional state without resorting to the command line. Understanding the Btrfs Layout Used by Fedora Fedora Workstation and Fedora KDE install the root filesystem as a single Btrfs partition that contains two default sub‑volumes. One sub‑volume holds the traditional “/” hierarchy, while the second is dedicated to /var/lib/machines . The latter exists to keep container images out of snapshot operations; it remains empty on systems that do not run virtual machines. Because Btrfs stores data in sub‑volumes rather than separate partitions, a snapshot captures the state of an entire sub‑volume at a point in time. The installer (Anaconda) automatically registers these sub‑volumes with the snapper service. Snapper maintains a series of read‑only ...