| name | deploying-tailscale-for-zero-trust-vpn |
| description | Deploy and configure Tailscale as a WireGuard-based zero trust mesh VPN with identity-aware access controls, ACLs, and exit nodes for secure peer-to-peer connectivity. |
| domain | cybersecurity |
| subdomain | zero-trust-architecture |
| tags | ["zero-trust","tailscale","wireguard","mesh-vpn","ztna","peer-to-peer","acl","identity-aware","headscale"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
Deploying Tailscale for Zero Trust VPN
Overview
Tailscale is a zero trust mesh VPN built on WireGuard that creates encrypted peer-to-peer connections between devices without requiring traditional VPN servers or complex network configuration. Every connection in a Tailscale network (tailnet) is end-to-end encrypted using WireGuard's Noise protocol framework with Curve25519 key exchange. Tailscale implements zero trust networking by authenticating every connection request through identity providers, enforcing granular Access Control Lists (ACLs), and supporting features like exit nodes, subnet routers, MagicDNS, and Tailscale SSH. For organizations preferring self-hosted infrastructure, Headscale provides an open-source implementation of the Tailscale control server.
Prerequisites
- Identity provider (Okta, Azure AD, Google Workspace, GitHub, or OIDC-compatible)
- Devices running supported OS (Linux, Windows, macOS, iOS, Android, FreeBSD)
- Administrative access to configure DNS and firewall rules
- Understanding of WireGuard protocol fundamentals
- Network planning documentation for subnet routing requirements
Architecture
Tailscale Coordination Server
(or self-hosted Headscale)
|
Key Distribution
& NAT Traversal
|
+-----------------+-----------------+
| | |
+----+----+ +----+----+ +----+----+
| Node A |<---->| Node B |<---->| Node C |
| (Linux) | | (macOS) | |(Windows)|
+---------+ +---------+ +---------+
WireGuard WireGuard WireGuard
Encrypted Encrypted Encrypted
P2P Tunnel P2P Tunnel P2P Tunnel
Each node connects directly to every other node.
DERP relay servers used only when direct P2P fails.
Installation and Setup
Linux Installation
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
tailscale status
tailscale ip -4
tailscale ip -6
Windows / macOS Installation
brew install --cask tailscale
Docker Deployment
version: '3.8'
services:
tailscale:
image: tailscale/tailscale:latest
container_name: tailscale
hostname: my-service
environment:
- TS_AUTHKEY=tskey-auth-xxxxx
- TS_STATE_DIR=/var/lib/tailscale
- TS_EXTRA_ARGS=--advertise-tags=tag:container
volumes:
- tailscale-state:/var/lib/tailscale
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
- sys_module
restart: unless-stopped
volumes:
tailscale-state:
Kubernetes Deployment
apiVersion: v1
kind: Secret
metadata:
name: tailscale-auth
namespace: tailscale
type: Opaque
stringData:
TS_AUTHKEY: "tskey-auth-xxxxx"
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: tailscale
namespace: tailscale
spec:
selector:
matchLabels:
app: tailscale
template:
metadata:
labels:
app: tailscale
spec:
containers:
- name: tailscale
image: tailscale/tailscale:latest
env:
- name: TS_AUTHKEY
valueFrom:
secretKeyRef:
name: tailscale-auth
key: TS_AUTHKEY
- name: TS_KUBE_SECRET
value: tailscale-state
- name: TS_USERSPACE
value: "true"
securityContext:
capabilities:
add: ["NET_ADMIN"]
Access Control Lists (ACLs)
Tailscale ACLs define who can access what within your tailnet using a declarative JSON format. The default policy is deny-all, making it zero trust by design.
{
"acls": [
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:dev-server:*"]
},
{
"action": "accept",
"src": ["group:sre"],
"dst": ["tag:production:22,443,8080"]
},
{
"action": "accept",
"src": ["tag:backend"],
"dst": ["tag:database:5432,3306,27017"]
},
{
"action": "accept",
"src": ["group:employees"],
"dst": ["tag:internal-tools:443"]
}
],
"groups": {
"group:engineering": ["user@company.com", "dev@company.com"],
"group:sre": ["sre@company.com", "oncall@company.com"],
"group:employees": ["autogroup:members"]
},
"tagOwners": {
"tag:dev-server": ["group:engineering"],
"tag:production": ["group:sre"],
"tag:backend": ["group:sre"],
"tag:database": ["group:sre"],
"tag:internal-tools": ["group:sre"],
"tag:container": ["group:sre"]
},
"ssh": [
{
"action": "check",
"src": ["group:sre"],
"dst": ["tag:production"],
"users": ["root", "admin"]
},
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:dev-server"],
"users": ["autogroup:nonroot"]
}
],
"nodeAttrs": [
{
"target": ["autogroup:members"],
"attr": ["funnel:deny"]
}
]
}
Exit Nodes and Subnet Routing
Configure Exit Node
sudo tailscale up --advertise-exit-node
sudo tailscale up --exit-node=<exit-node-ip>
curl ifconfig.me
Subnet Router Configuration
sudo tailscale up --advertise-routes=10.0.0.0/24,192.168.1.0/24
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
sudo tailscale up --accept-routes
Tailscale SSH (Zero Trust SSH)
Tailscale SSH replaces traditional SSH key management with identity-based access.
sudo tailscale up --ssh
ssh user@hostname
MagicDNS Configuration
ping my-server
Self-Hosted with Headscale
wget https://github.com/juanfont/headscale/releases/latest/download/headscale_linux_amd64
chmod +x headscale_linux_amd64
sudo mv headscale_linux_amd64 /usr/local/bin/headscale
sudo mkdir -p /etc/headscale
sudo headscale generate config > /etc/headscale/config.yaml
sudo headscale serve
headscale users create myorg
headscale preauthkeys create --user myorg --reusable --expiration 24h
tailscale up --login-server https://headscale.example.com
Security Hardening
Key Expiry and Rotation
sudo tailscale up --authkey=tskey-auth-xxxxx
Device Authorization
{
"nodeAttrs": [
{
"target": ["autogroup:members"],
"attr": [
"mullvad:deny",
"funnel:deny"
]
}
],
"autoApprovers": {
"routes": {
"10.0.0.0/24": ["group:sre"],
"192.168.0.0/16": ["group:sre"]
},
"exitNode": ["group:sre"]
}
}
Network Lock (Tailnet Lock)
tailscale lock init
tailscale lock add nodekey:xxxxx
Monitoring and Observability
tailscale status --json | jq '.Peer | to_entries[] | {name: .value.HostName, online: .value.Online, os: .value.OS}'
tailscale ping <peer-ip>
tailscale netcheck
Integration Patterns
Service Mesh Integration
CI/CD Pipeline Integration
export TS_AUTHKEY=tskey-auth-xxxxx-ephemeral
tailscale up --authkey=$TS_AUTHKEY --hostname=ci-runner-$CI_JOB_ID
References