A zero-configuration mesh VPN built on WireGuard that connects devices across networks with automatic NAT traversal and centralized access control.
Addresses below are RFC 5737 documentation ranges or placeholders - swap in your own.
Table of Contents#
- Overview
- Key Features
- Architecture
- Installation
- Basic Usage
- Subnet Routing
- Exit Nodes
- MagicDNS
- Tailscale SSH
- Tailscale Funnel
- Access Control Lists (ACLs)
- Tailscale Lock (Tailnet Lock)
- Admin Console
- Headscale (Self-Hosted)
- Troubleshooting
- See Also
- Sources
1. Overview#
Tailscale is a mesh VPN that creates secure, peer-to-peer connections between devices using the WireGuard protocol. Instead of routing traffic through a central server, Tailscale establishes direct encrypted tunnels between nodes wherever possible. A coordination server handles authentication, key distribution, and ACL enforcement, but never sees user traffic.
Tailscale is available as a managed SaaS product (free tier supports up to 100 devices) and as an open-source coordination server alternative called Headscale.
2. Key Features#
- Zero configuration - no manual port forwarding, firewall rules, or key management needed
- WireGuard-based - uses the WireGuard protocol for fast, modern encryption
- Automatic NAT traversal - uses DERP relay servers and NAT hole-punching to connect devices behind firewalls
- MagicDNS - automatic DNS for all devices in the network using stable hostnames
- ACL-based access control - fine-grained, centrally managed policies define who can access what
- SSO integration - authenticates via existing identity providers (Google, Microsoft, GitHub, Okta, OIDC)
- Subnet routing - expose entire LAN subnets to the tailnet without installing Tailscale on every device
- Exit nodes - route all internet traffic through a specific node
- Tailscale SSH - SSH access managed through Tailscale ACLs, replacing SSH keys
- Funnel - expose local services to the public internet through Tailscale's infrastructure
3. Architecture#
3.1. Components#
- Tailscale client (tailscaled) - daemon running on each device that manages WireGuard tunnels
- Coordination server - distributes public keys and ACL policies; never handles user traffic
- DERP relays - encrypted relay servers used when direct peer-to-peer connections fail; traffic remains end-to-end encrypted
- Identity provider - handles authentication (Google, Microsoft, GitHub, Okta, or custom OIDC)
3.2. Connection Flow#
1. Device authenticates via identity provider
2. Coordination server distributes peer keys and ACL policies
3. Devices attempt direct WireGuard connection (NAT hole-punching)
4. If direct connection fails, traffic relays through DERP servers
5. DERP relay sees only encrypted WireGuard packets3.3. Network Model#
Each device receives a stable IP address in the 100.64.0.0/10 (CGNAT) range. IPv6 addresses are assigned from the fd7a:115c:a1e0::/48 prefix. These addresses persist across network changes and device reboots.
4. Installation#
4.1. Arch Linux#
sudo pacman -S tailscale
sudo systemctl enable --now tailscaled4.2. Debian/Ubuntu#
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.noarmor.gpg | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/noble.tailscale-keyring.list | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt update
sudo apt install tailscale
sudo systemctl enable --now tailscaledReplace
noblewith your Ubuntu release codename. For Debian, use the Debian-specific URL from pkgs.tailscale.com.
4.3. Fedora/RHEL#
sudo dnf config-manager --add-repo https://pkgs.tailscale.com/stable/fedora/tailscale.repo
sudo dnf install tailscale
sudo systemctl enable --now tailscaled4.4. Docker#
docker run -d \
--name=tailscale \
--hostname=<container-hostname> \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
-v /dev/net/tun:/dev/net/tun \
-v tailscale-state:/var/lib/tailscale \
-e TS_AUTHKEY=<auth-key> \
tailscale/tailscale:latest5. Basic Usage#
5.1. Authenticate and Connect#
# Start Tailscale and authenticate
sudo tailscale up
# Use an auth key for headless/automated setups
sudo tailscale up --authkey=<auth-key>
# Disconnect
sudo tailscale down
# Check status
tailscale status
# Show detailed peer information
tailscale status --peers --active5.2. Useful Commands#
# Ping a peer (via Tailscale, shows direct vs relayed)
tailscale ping <peer-name-or-ip>
# Show current Tailscale IP addresses
tailscale ip
# Show network diagnostics
tailscale netcheck
# Show debug information
tailscale debug metrics
tailscale debug prefs5.3. File Transfer#
# Send a file to another device
tailscale file cp <file> <peer-name>:
# Receive files (waits for incoming transfers)
tailscale file get <output-directory>6. Subnet Routing#
Subnet routers expose local network subnets to the tailnet, allowing devices without Tailscale installed to be accessed through the VPN.
6.1. Enable Subnet Router#
On the machine that will act as the subnet router:
# 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
# Advertise the subnet
sudo tailscale up --advertise-routes=192.0.2.0/24,198.51.100.0/246.2. Approve the Route#
Routes must be approved in the admin console:
- Go to login.tailscale.com/admin/machines
- Click the subnet router machine
- Under "Subnets", approve the advertised routes
Or approve via CLI with tailscale set:
# On the admin's machine (if using ACL auto-approvers)
# Add to ACL file under "autoApprovers":
{
"autoApprovers": {
"routes": {
"192.0.2.0/24": ["tag:server"]
}
}
}6.3. Accept Routes on Clients#
Clients must explicitly accept subnet routes:
sudo tailscale up --accept-routes7. Exit Nodes#
An exit node routes all internet traffic from other tailnet devices through itself, similar to a traditional VPN gateway.
7.1. Advertise as Exit Node#
# On the exit node
sudo tailscale up --advertise-exit-nodeApprove the exit node in the admin console (same process as subnet routes).
7.2. Use an Exit Node#
# Route all traffic through a specific exit node
sudo tailscale up --exit-node=<exit-node-ip-or-name>
# Allow LAN access while using an exit node
sudo tailscale up --exit-node=<exit-node-ip-or-name> --exit-node-allow-lan-access
# Stop using the exit node
sudo tailscale up --exit-node=8. MagicDNS#
MagicDNS provides automatic DNS resolution for all devices in a tailnet. Each device is reachable by its hostname.
8.1. Enable MagicDNS#
Enable in the admin console under DNS settings, or configure in the ACL file:
{
"dns": {
"magic": true,
"nameservers": ["1.1.1.1", "8.8.8.8"],
"routes": {
"corp.example.com": ["192.0.2.53"]
},
"domains": ["example.com"]
}
}8.2. DNS Resolution#
# Devices are reachable by hostname
ping my-laptop
# Full domain format
ping my-laptop.<tailnet-name>.ts.net
# Check DNS configuration
tailscale dns status8.3. Split DNS#
Route specific domains to internal DNS servers while using Tailscale DNS for everything else:
{
"dns": {
"routes": {
"internal.corp.com": ["192.0.2.53"],
"staging.corp.com": ["198.51.100.53"]
}
}
}9. Tailscale SSH#
Tailscale SSH allows SSH connections managed entirely through Tailscale ACLs, eliminating the need for SSH key distribution or password management.
9.1. Enable Tailscale SSH#
# On the target machine
sudo tailscale up --ssh9.2. Configure ACLs for SSH#
{
"ssh": [
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self"],
"users": ["autogroup:nonroot", "root"]
},
{
"action": "check",
"src": ["autogroup:member"],
"dst": ["tag:production"],
"users": ["root"],
"checkPeriod": "12h"
}
]
}Actions:
- accept - allow SSH access immediately
- check - require re-authentication via the identity provider at the specified interval
9.3. Connect via Tailscale SSH#
# SSH using Tailscale identity (no keys needed)
ssh <user>@<tailscale-hostname>
# Tailscale handles authentication and authorization
# No SSH keys are exchanged; identity comes from the IdP10. Tailscale Funnel#
Funnel exposes local services to the public internet through Tailscale's infrastructure, with automatic HTTPS certificates.
10.1. Enable Funnel#
# Serve a local service publicly
tailscale funnel 8080
# Serve with a specific path
tailscale funnel --set-path=/api localhost:3000
# Serve a local directory
tailscale funnel /path/to/files
# Disable funnel
tailscale funnel --remove 808010.2. Tailscale Serve (Private, Tailnet-Only)#
tailscale serve is similar to Funnel but only exposes services within the tailnet:
# Expose a local port within the tailnet
tailscale serve 8080
# Expose with HTTPS (auto-certs within tailnet)
tailscale serve https / localhost:8080
# Show current serve configuration
tailscale serve status10.3. Funnel ACL Requirement#
Funnel must be enabled in the ACL policy:
{
"nodeAttrs": [
{
"target": ["autogroup:member"],
"attr": ["funnel"]
}
]
}11. Access Control Lists (ACLs)#
ACLs are defined as a JSON (or HuJSON) policy file in the admin console. They control all traffic within the tailnet.
11.1. Basic Structure#
{
"acls": [
{
"action": "accept",
"src": ["group:engineering"],
"dst": ["tag:webserver:80,443"]
},
{
"action": "accept",
"src": ["autogroup:member"],
"dst": ["autogroup:self:*"]
}
],
"groups": {
"group:engineering": ["user1@example.com", "user2@example.com"],
"group:devops": ["user3@example.com"]
},
"tagOwners": {
"tag:webserver": ["group:devops"],
"tag:database": ["group:devops"]
},
"hosts": {
"monitoring": "100.64.0.5"
}
}11.2. Common ACL Patterns#
Allow all members to access all devices (default):
{
"acls": [
{"action": "accept", "src": ["*"], "dst": ["*:*"]}
]
}Role-based access:
{
"acls": [
{"action": "accept", "src": ["group:devops"], "dst": ["*:*"]},
{"action": "accept", "src": ["group:engineering"], "dst": ["tag:webserver:80,443", "tag:webserver:22"]},
{"action": "accept", "src": ["group:engineering"], "dst": ["tag:database:5432"]}
]
}Deny by default with explicit allows:
{
"acls": [
{"action": "accept", "src": ["group:admins"], "dst": ["*:*"]},
{"action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self:*"]},
{"action": "accept", "src": ["tag:monitoring"], "dst": ["*:9090,9100"]}
]
}11.3. Tests#
ACL policies support inline tests to verify correctness:
{
"tests": [
{
"src": "user1@example.com",
"accept": ["tag:webserver:80"],
"deny": ["tag:database:5432"]
}
]
}12. Tailscale Lock (Tailnet Lock)#
Tailnet Lock adds a second layer of verification: even if the coordination server is compromised, devices can only join the network if signed by a trusted node.
12.1. Enable Tailnet Lock#
# Initialize tailnet lock (generates a signing key)
tailscale lock init
# Sign a new node
tailscale lock sign <node-key>
# View lock status
tailscale lock status
# List trusted signing keys
tailscale lock list12.2. Key Concepts#
- Tailnet Lock Key (TLK) - a signing key held by trusted nodes
- Nodes must be signed by a TLK holder before they can join the network
- Protects against coordination server compromise
- Requires at least one TLK holder to be online to sign new devices
12.3. Disable Tailnet Lock#
# Requires signatures from a majority of TLK holders
tailscale lock disable13. Admin Console#
The admin console at login.tailscale.com provides:
- Machines - view all devices, approve subnet routes and exit nodes, manage tags
- Users - manage users and their devices
- ACLs - edit access control policies with syntax validation and testing
- DNS - configure MagicDNS, nameservers, and split DNS routes
- Keys - generate auth keys, API keys, and manage OAuth clients
- Logs - network flow logs showing source, destination, and action (accept/deny)
- Settings - identity provider configuration, tailnet lock, feature toggles
13.1. Auth Keys#
Generate pre-authentication keys for headless or automated device registration:
# In the admin console: Settings > Keys > Generate auth key
# Options:
# - Reusable: allow multiple devices to use the same key
# - Ephemeral: device is removed when it goes offline
# - Pre-approved: skip manual approval
# - Tags: automatically apply tags on registration13.2. API Access#
# Generate an API key or OAuth client in the admin console
# Use with the Tailscale API
curl -s -H "Authorization: Bearer <api-key>" \
https://api.tailscale.com/api/v2/tailnet/-/devices | jq .14. Headscale (Self-Hosted)#
Headscale is an open-source, self-hosted implementation of the Tailscale coordination server.
# Install headscale (example for Debian)
wget https://github.com/juanfont/headscale/releases/latest/download/headscale_<version>_linux_amd64.deb
sudo dpkg -i headscale_<version>_linux_amd64.deb
# Configure /etc/headscale/config.yaml
# Start the server
sudo systemctl enable --now headscale
# Create a user
headscale users create myuser
# Generate a pre-auth key
headscale preauthkeys create --user myuser
# Connect a client to headscale
sudo tailscale up --login-server=https://headscale.example.comKey differences from managed Tailscale:
- No MagicDNS (requires manual DNS configuration)
- No Funnel support
- No Tailscale SSH (use standard SSH)
- ACLs configured in a YAML file on the server
- DERP servers must be self-hosted or use Tailscale's public ones
Troubleshooting#
| Issue | Cause | Solution |
|---|---|---|
"Unable to connect" after tailscale up | tailscaled service not running | Run sudo systemctl start tailscaled then sudo tailscale up |
| All traffic relayed (no direct connections) | Firewall blocking UDP hole-punching | Run tailscale netcheck to diagnose; ensure UDP port 41641 is not blocked |
| Subnet routes not working | Routes not approved or client not accepting routes | Approve routes in admin console; run sudo tailscale up --accept-routes on clients |
| DNS resolution fails for tailnet names | MagicDNS not enabled or systemd-resolved conflict | Enable MagicDNS in admin console; check resolvectl status for DNS routing |
| Exit node not appearing | Exit node not approved in admin console | Approve the exit node in the Machines tab of the admin console |
| "not logged in, last login error" | Auth key expired or revoked | Generate a new auth key and re-authenticate with sudo tailscale up |
| High latency between peers | Traffic routing through DERP relay instead of direct | Run tailscale ping <peer> to check; firewalls or strict NAT may prevent direct connections |
| Tailscale SSH "connection refused" | SSH not enabled on the target or ACL denying access | Verify --ssh flag is set on the target; check SSH ACL rules in the policy |
| Cannot reach devices after OS update | tailscaled service not started after reboot | Run sudo systemctl enable --now tailscaled |