How to Create a Zero-Trust VPN with Tailscale and WireGuard

A cybersecurity expert inspecting lines of code on multiple monitors in a dimly lit office.
A cybersecurity expert inspecting lines of code on multiple monitors in a dimly lit office.

How to Create a Zero-Trust VPN with Tailscale and WireGuard

The world of networking security is undergoing a seismic shift. For decades, the “castle-and-moat” model prevailed: build a strong perimeter, and anything inside is trusted. This worked, until it didn’t. Modern distributed systems, remote work, and the rise of cloud computing exposed the fundamental flaws of trusting anything “inside.”

Enter Zero Trust. The core principle is simple: “never trust, always verify.” Every user, device, and application attempting to access a resource must be authenticated and authorized, regardless of its location relative to the “network perimeter.”

This isn’t about ditching VPNs entirely; it’s about transforming them. Traditional VPNs often grant broad network access once authenticated, making them a juicy target for lateral movement if compromised. We need something smarter, more granular, and built for the distributed world.

This is where Tailscale comes in, leveraging the power and simplicity of WireGuard to deliver a zero-trust network overlay that’s a joy to set up and manage.

Why Zero Trust? (And Why Traditional VPNs Fall Short)

Imagine your company network as a house. A traditional VPN is like giving every authenticated employee a key to the front door, and once inside, they can wander anywhere. If a key is stolen (a credential compromised), the attacker has free rein.

Traditional VPNs:

  • Perimeter-focused: Once inside the “network,” trust is often implicit.
  • Single point of failure: The VPN server becomes a critical bottleneck and a high-value target.
  • Flat network access: Users often get access to entire subnets, not just the specific resources they need.
  • Complex firewall rules: Managing granular access at the network layer can become a nightmare.

Zero Trust flips this:

  • Identity-centric: Access is granted based on who and what (user, device, application), not just where (IP address).
  • Least Privilege: Users and devices are only granted access to the specific resources they absolutely need, for the shortest time necessary.
  • Continuous Verification: Trust is never assumed; it’s constantly re-evaluated based on context.
  • Micro-segmentation: Network segments are isolated, limiting lateral movement in case of a breach.

Why Tailscale and WireGuard are a Match Made in Heaven

You might be thinking, “Great, another security buzzword. How do I actually implement this without pulling my hair out?” This is where Tailscale shines.

WireGuard: The Foundation

WireGuard is a modern, fast, and cryptographically sound VPN protocol. Its brilliance lies in its simplicity:

  • Minimalistic: Very small codebase, making it easier to audit and less prone to bugs.
  • High Performance: Runs in the kernel, offering excellent throughput and low latency.
  • Modern Cryptography: Uses state-of-the-art algorithms (Curve25519, ChaCha20, Poly1305).
  • Simple Configuration: Compared to IPsec, it’s a breath of fresh air.

However, raw WireGuard is like a powerful engine without a chassis or steering wheel. You still need to manage keys, IP addresses, peer configurations, and NAT traversal for every single device. This is where Tailscale steps in.

Tailscale: The Zero-Trust Orchestrator

Tailscale takes the raw power of WireGuard and builds a fully managed, user-friendly control plane on top. It handles:

  • Key Exchange & Management: No more manual key generation and distribution. Tailscale handles all public/private key exchanges securely.
  • NAT Traversal: Connects devices even behind complex NATs and firewalls, using STUN/TURN-like techniques.
  • IP Address Management: Assigns unique, stable IP addresses (from the 100.64.0.0/10 range, known as “CGNAT range”) to every device in your “tailnet.”
  • DNS: Provides a built-in DNS resolver for all nodes, making it easy to address devices by name.
  • Identity-based Access Control (ACLs): This is the core of its zero-trust implementation. You define granular policies based on user identities, device tags, and services.
  • Authentication: Integrates with your existing identity providers (Google Workspace, Microsoft 365, Okta, GitHub, etc.) for seamless login.

In essence: WireGuard provides the secure, efficient tunnels. Tailscale provides the “zero-trust” intelligence, management, and ease of use to orchestrate those tunnels across all your devices, regardless of their location.

Setting Up Your First Tailscale Zero-Trust Network

Let’s get practical. We’ll start with a minimal setup to understand the core concepts, then expand into more advanced zero-trust features.

Step 1: Create a Tailscale Account

Go to tailscale.com and sign up. You’ll typically authenticate with an existing identity provider (e.g., your Google account, GitHub, etc.). This makes user management trivial later on.

Step 2: Install Tailscale on Your Devices

Tailscale supports virtually every major OS (Linux, Windows, macOS, Android, iOS, Synology, Raspberry Pi, Docker, etc.). We’ll focus on Linux CLI examples, as they’re foundational.

On a Linux Machine (e.g., Ubuntu/Debian):

Tailscale provides a convenient installation script.

curl -fsSL https://tailscale.com/install.sh | sudo sh
Expected Output
Executing Tailscale install script, v1.62.1.
...
Detected Debian Bullseye.
...
Running apt-get update...
Get:1 https://pkgs.tailscale.com/stable/debian bullseye InRelease [6768 B]
...
Setting up tailscale (1.62.1) ...
Setting up tailscale-archive-keyring (1.62.1) ...
Configuring /etc/apt/sources.list.d/tailscale.list
Setting up tailscale (1.62.1) ...

This script adds the Tailscale repository, installs the tailscale and tailscale-archive-keyring packages, and ensures necessary dependencies are met.

Step 3: Authenticate Your Device

Once installed, bring the Tailscale service up and authenticate.

sudo tailscale up
Expected Output
To authenticate, open your browser to:

https://login.tailscale.com/a/<some-long-id>

Copy the URL and paste it into your web browser. This will take you to the Tailscale authentication page, where you’ll log in with the identity you used to sign up. Once authenticated, the terminal command will complete.

Success.

Your device is now part of your “tailnet” (your private Tailscale network).

Step 4: Verify Your Tailnet Connection

You can check the status of your Tailscale connection and see other devices in your network.

tailscale status
Expected Output
100.x.y.z   my-laptop           user@example.com   linux   active; direct
100.a.b.c   my-server           user@example.com   linux   active; direct

You’ll see your device’s Tailscale IP address (e.g., 100.x.y.z), its hostname, the user it’s associated with, its OS, and its connection status.

Now, from my-laptop, you should be able to ping my-server using its Tailscale IP or even its hostname (if DNS is configured, which Tailscale does by default).

ping 100.a.b.c
Expected Output
PING 100.a.b.c (100.a.b.c) 56(84) bytes of data.
64 bytes from 100.a.b.c: icmp_seq=1 ttl=64 time=X.YZ ms
64 bytes from 100.a.b.c: icmp_seq=2 ttl=64 time=X.YZ ms
^C
--- 100.a.b.c ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = X.YZ/X.YZ/X.YZ/0.000 ms

Congratulations! You’ve just created your first WireGuard-powered, zero-trust network overlay. Every device added this way can communicate securely and directly with any other device in your tailnet, regardless of firewalls or NATs, as if they were on the same local network.

Advanced Tailscale Features for Zero Trust

The real power of Tailscale for zero trust comes from features like Subnet Routers and Access Control Lists.

Subnet Routers (aka Exit Nodes or Network Extensions)

A “Subnet Router” (formerly “Relaynode”) allows your Tailscale network to access devices on a physical subnet that don’t have Tailscale installed directly. This is crucial for accessing internal servers, IoT devices, or legacy systems without exposing them to the public internet.

A Tailscale node acts as a gateway for a specific local subnet. Any traffic from your Tailnet destined for that subnet will be routed through this gateway.

Use Case: You have a local server at 192.168.1.10 that doesn’t run Tailscale, and you want to access it from your laptop anywhere in the world.

Steps:

  1. Choose a Linux machine on the target subnet to act as the Subnet Router. This machine must have Tailscale installed.

  2. Enable IP Forwarding on the Subnet Router machine. This allows it to route traffic between its interfaces.

    echo 'net.ipv4.ip_forward = 1' | sudo tee /etc/sysctl.d/99-ip-forward.conf
    sudo sysctl -p /etc/sysctl.d/99-ip-forward.conf
    Expected Output
    net.ipv4.ip_forward = 1
  3. Advertise the Subnet from the Tailscale node on that network. Replace 192.168.1.0/24 with your actual subnet.

    sudo tailscale up --advertise-routes=192.168.1.0/24
    Expected Output
    ...
    Success.

    You can verify the advertised routes:

    sudo tailscale status --json | jq '.Self.AdvertisedRoutes'
    Expected Output
    [
      "192.168.1.0/24"
    ]
  4. Authorize the Subnet Route in the Tailscale admin console. Go to admin.tailscale.com/machines, find the machine you just configured, click the three dots next to it, and select “Edit route settings.” Check the box next to 192.168.1.0/24 and save.

  5. Connect from a Client Device: From another Tailscale device (e.g., your laptop), you can now access the 192.168.1.0/24 subnet. Ensure your client is configured to accept routes, which is often the default or can be enabled via tailscale up --accept-routes.

    Now, from your laptop (connected via Tailscale), you can ping or SSH into 192.168.1.10:

    ping 192.168.1.10
    Expected Output
    PING 192.168.1.10 (192.168.1.10) 56(84) bytes of data.
    64 bytes from 192.168.1.10: icmp_seq=1 ttl=63 time=X.YZ ms
    ...

    Note: Subnet Routers are powerful but also expand the scope of your Tailnet. Always apply the principle of least privilege: only advertise subnets that genuinely need to be accessed.

Access Control Lists (ACLs) - The Core of Zero Trust

ACLs are how Tailscale enforces your “never trust, always verify” policies. They define who can access what, on which ports. Tailscale’s ACLs are defined in a JSON file (often .hujson for human-friendly JSON with comments) and uploaded to your tailnet.

Key ACL Concepts:

  • Users: Referenced by their email address (e.g., user@example.com).
  • Groups: Define collections of users (e.g., group:sysadmins).
  • Tags: Apply labels to devices (e.g., tag:prod-servers). Devices can be tagged during tailscale up or from the admin UI.
  • Rules (acls block): Define src (source, who is allowed), dst (destination, what they can access, including ports), and action (accept or drop).
  • Tag Owners (tagOwners block): Specify which users or groups can apply specific tags to devices. This prevents unauthorized tagging.

Example policy.hujson:

Let’s imagine a scenario where:

  • sysadmins (user alice@example.com) have full access to everything.
  • devs (user bob@example.com) can SSH and access web services on dev-servers.
  • A monitoring-server needs to access specific ports on prod-servers.
{
  // ACLs define who can access what
  "acls": [
    // Rule 1: Allow sysadmins full access to any service on any device
    {
      "action": "accept",
      "src":    ["group:sysadmins"],
      "dst":    ["*:*"]
    },
    // Rule 2: Allow devs to SSH (port 22) and access HTTP/S (ports 80, 443) on devices tagged 'dev-servers'
    {
      "action": "accept",
      "src":    ["group:devs"],
      "dst":    ["tag:dev-servers:22", "tag:dev-servers:80", "tag:dev-servers:443"]
    },
    // Rule 3: Allow the monitoring server (a specific tagged device) to access Prometheus (9090) and Node Exporter (9100) on devices tagged 'prod-servers'
    {
      "action": "accept",
      "src":    ["tag:monitoring-server"],
      "dst":    ["tag:prod-servers:9090", "tag:prod-servers:9100"]
    }
  ],

  // Tag owners specify who can assign tags to devices
  "tagOwners": {
    "dev-servers": ["group:sysadmins"],
    "prod-servers": ["group:sysadmins"],
    "monitoring-server": ["group:sysadmins"]
  },

  // Groups define collections of users
  "groups": {
    "sysadmins": ["alice@example.com"],
    "devs": ["bob@example.com"]
  },

  // SSH rules (optional, for Tailscale's SSH server feature)
  "ssh": [
    {
      "action": "accept",
      "src": ["group:sysadmins"],
      "dst": ["tag:dev-servers", "tag:prod-servers"],
      "users": ["autouser"] // Allows sysadmins to SSH as 'autouser' on tagged servers
    }
  ]
}

Steps to Apply ACLs:

  1. Create the policy.hujson file with your desired rules.

  2. Test your ACLs (optional but highly recommended!): Tailscale provides a linter.

    tailscale set-acl --test < policy.hujson
    Expected Output (if valid)
    Policy file passed linting

    If there are errors, it will tell you.

  3. Apply the ACLs:

    tailscale set-acl policy.hujson
    Expected Output
    Success.

Now, the rules you’ve defined will be enforced across your tailnet. If bob@example.com tries to SSH into a prod-server, Tailscale will block the connection based on the ACL. This is the essence of Zero Trust: explicit authorization for every connection attempt.

Tailscale’s Use of WireGuard: Under the Hood

It’s important to reiterate: Tailscale is WireGuard. When you run tailscale up, it’s configuring a WireGuard interface on your system.

You can inspect the underlying WireGuard configuration for debugging or educational purposes, though you should never manually modify it as Tailscale manages it.

sudo tailscale debug wgconf
Expected Output (truncated)
#
# Generated by Tailscale.
# Do not edit.
#
[Interface]
PrivateKey = <your-private-key>
ListenPort = 41641
# fwmark 0x80000

[Peer]
# my-laptop
PublicKey = <public-key-of-laptop>
PresharedKey = <preshared-key>
AllowedIPs = 100.x.y.z/32
PersistentKeepalive = 25

[Peer]
# my-server
PublicKey = <public-key-of-server>
PresharedKey = <preshared-key>
AllowedIPs = 100.a.b.c/32, 192.168.1.0/24 # Includes subnet route if configured
PersistentKeepalive = 25
...

This output shows how Tailscale translates its higher-level concepts (devices, IPs, subnet routes) into standard WireGuard peer configurations, including unique public/private keys and pre-shared keys (which Tailscale rotates automatically for enhanced security).

Note: The pre-shared keys (PresharedKey) are a defense-in-depth measure specific to Tailscale, adding an extra layer of symmetric encryption on top of WireGuard’s standard asymmetric key exchange. This protects against potential future cryptographic breakthroughs that might compromise WireGuard’s public-key cryptography.

Security Considerations & Best Practices

Implementing a zero-trust network doesn’t mean you can ignore other security fundamentals.

  • Multi-Factor Authentication (MFA): Ensure your identity provider (Google, Microsoft, Okta) uses strong MFA. This is your primary defense against account compromise.
  • Least Privilege Principle: Constantly review your ACLs. Grant only the necessary access for the shortest duration.
  • Regular ACL Review: As your network evolves, so should your ACLs. Remove old rules, consolidate, and ensure they reflect current needs.
  • Keep Tailscale Updated: Tailscale is actively developed. Keep your clients and subnet routers updated to benefit from the latest features, performance improvements, and security patches.
  • Monitor Your Tailnet: Use the Tailscale admin console to monitor device activity, active connections, and audit logs.
  • Understand Subnet Router Scope: Be very deliberate about which subnets you advertise. A compromised subnet router could potentially expose your entire local network segment.
  • Node Sharing: Be cautious with sharing nodes outside your organization if enabled. Ensure the recipient’s identity is verified.
  • Tailnet Lock: For the highest security environments, enable Tailnet Lock. This feature prevents unauthorized devices from joining your tailnet even if your identity provider is compromised, by requiring approval from an existing key in your tailnet. It’s an advanced feature and adds operational overhead, but provides significant security benefits.

Conclusion

Building a zero-trust VPN network might sound daunting, but Tailscale, built on the rock-solid foundation of WireGuard, makes it remarkably straightforward. You get the security benefits of identity-aware access control, micro-segmentation, and continuous verification, without the traditional VPN complexities.

By moving beyond the outdated perimeter model and embracing a “never trust, always verify” approach, you can create a more resilient, flexible, and secure network for your distributed teams and infrastructure. Dive in, experiment with ACLs, and unlock the true power of modern network security.

Last updated on