Skip to content
Menu

Networking9 min read

Tailscale Features

MagicDNS, ACLs, subnet routers, and the full feature set

MagicDNS

Every device on a tailnet gets a stable DNS name automatically. No manual DNS records, no editing /etc/hosts, no running your own DNS server. This is one of Tailscale's most practically useful features for day-to-day operations.

DNS Name Format

Each device is reachable at: <hostname>.<tailnet-name>.ts.net

console
# Full qualified name
workstation.tail1234.ts.net

# Short name (within the same tailnet, MagicDNS resolves this)
workstation

100.x.y.z Addressing

CGNAT Address Space (100.64.0.0/10)

Tailscale assigns each device an IP from the 100.64.0.0/10 range (RFC 6598 — Shared Address Space, originally allocated for CGNAT). This is a deliberate choice:

  • Won't conflict with your LAN: Your office uses 10.0.0.0/8? Home uses 192.168.0.0/16? Neither overlaps with 100.64.0.0/10.
  • Won't conflict with cloud VPCs: AWS, GCP, Azure VPCs typically use RFC 1918 space. No overlap.
  • Stable across networks: Your device keeps its 100.x.y.z address whether you're on WiFi, cellular, ethernet, or roaming between them.

Note

The one edge case: If your ISP uses CGNAT, you might see 100.64.x.x addresses on your WAN interface too. In practice this rarely causes issues because Tailscale's WireGuard interface has higher routing priority for Tailscale-specific IPs, but it's worth knowing if you're debugging routing.

Split DNS

Route Specific Domains to Specific Nameservers

Split DNS lets you configure the tailnet so that queries for specific domains are resolved by specific nameservers — routed through the tailnet.

bash
// In Tailscale DNS settings:
{
  "dns": {
    "nameservers": ["100.100.100.100"],  // MagicDNS
    "routes": {
      "corp.example.com": ["10.0.0.53"],     // Corporate DNS for internal domains
      "staging.internal": ["10.1.0.53"]      // Staging environment DNS
    }
  }
}

Use case: You're at a coffee shop, but api.corp.example.com still resolves correctly because the query is routed through your tailnet to the corporate DNS server.

ACLs (Access Control Lists)

Tailscale ACLs are policy-as-code, defined in HuJSON (JSON with comments and trailing commas). They control which devices and users can communicate with which other devices and services. ACLs are evaluated on each node — there's no central chokepoint.

Warning

Default deny: If you define any ACL rules, the default policy becomes deny-all. Only explicitly allowed traffic passes. This is the opposite of the default (without ACLs) where all nodes in a tailnet can reach all other nodes.

Building Blocks

Concept Purpose Example
Groups Named collections of users "group:engineering": ["alice@", "bob@"]
Tags Labels for devices (replace user ownership) "tag:server", "tag:k8s-node"
Tag Owners Who can apply a tag to a device "tag:server": ["group:ops"]
Hosts Named aliases for specific IPs/CIDRs "prod-db": "100.64.0.50"
autoApprovers Auto-approve routes/exit nodes by tag "routes": {"10.0.0.0/8": ["tag:router"]}

ACL Example

bash
{
  // Groups: named user collections
  "groups": {
    "group:engineering": ["alice@example.com", "bob@example.com"],
    "group:ops":         ["carol@example.com", "dave@example.com"],
  },

  // Tag owners: who can assign which tags
  "tagOwners": {
    "tag:server":    ["group:ops"],
    "tag:k8s-node":  ["group:ops"],
    "tag:monitoring": ["group:ops"],
  },

  // ACL rules: who can reach what
  "acls": [
    // Ops can reach everything
    {
      "action": "accept",
      "src":    ["group:ops"],
      "dst":    ["*:*"],
    },

    // Engineering can reach servers on specific ports
    {
      "action": "accept",
      "src":    ["group:engineering"],
      "dst":    ["tag:server:22,80,443"],
    },

    // Monitoring can reach all nodes on prometheus port
    {
      "action": "accept",
      "src":    ["tag:monitoring"],
      "dst":    ["*:9090,9100"],
    },

    // K8s nodes can talk to each other on all ports
    {
      "action": "accept",
      "src":    ["tag:k8s-node"],
      "dst":    ["tag:k8s-node:*"],
    },
  ],

  // Auto-approve subnet routes and exit nodes
  "autoApprovers": {
    "routes": {
      "10.0.0.0/8":      ["tag:server"],
      "192.168.0.0/16":   ["tag:server"],
    },
    "exitNode": ["tag:server"],
  },
}

Tip

Tags vs Users: Tags are particularly important for server infrastructure. A tagged device (tag:server) is no longer owned by a specific user — it's "owned" by the tag. This means ACLs don't break when an employee leaves. If Carol tagged the server and then leaves the company, the server's ACL access is unchanged because it's governed by the tag, not Carol's account.

Where ACLs Are Evaluated

Tailscale distributes ACL rules to every node. Each node enforces the policy locally using packet filtering in the WireGuard layer. This means:

  • No central bottleneck: ACL enforcement is distributed, not a single firewall in the path.
  • Offline-capable: If the coordination server is temporarily unreachable, the last-pushed ACLs remain in effect.
  • Fast updates: ACL changes are pushed to all nodes via the control plane connection (long-polling/WebSocket). Typical propagation: seconds.

Subnet Routers

Not every device can run the Tailscale agent. Subnet routers let you bridge between Tailscale and non-Tailscale networks, making entire subnets reachable through the tailnet.

Your Laptop
100.64.0.1 (Tailscale)
Subnet Router
100.64.0.10 + 192.168.1.0/24
Office Printer
192.168.1.50 (no Tailscale)

Setting Up a Subnet Router

  1. Advertise routes on the machine that will act as the subnet router:
    console
    console
    
              $ sudo tailscale up --advertise-routes=192.168.1.0/24,10.0.0.0/24
    
            
  2. Enable IP forwarding on the subnet router (Linux):
    console
    console
    
              $ echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
    
              $ echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
    
              $ sudo sysctl -p /etc/sysctl.d/99-tailscale.conf
    
            
  3. Approve routes in the Tailscale admin console (or via autoApprovers in ACLs). Unapproved routes are ignored by other nodes.
  4. Done: Other tailnet nodes can now reach 192.168.1.50 as if they were on the same LAN. Traffic flows: Laptop → WireGuard → Subnet Router → LAN → Printer.

Note

Use case — accessing on-prem from anywhere: Put a subnet router in your office or datacenter. Now every tailnet member (working from home, a coffee shop, or traveling) can reach the entire office network. No traditional VPN concentrator, no port forwarding, no exposing services to the internet.

Tip

High availability: Run two subnet routers advertising the same routes. Tailscale will use the one with lower latency and failover to the other if the primary becomes unreachable. No special configuration needed — just advertise the same routes from both.

Exit Nodes

Subnet routers expose specific subnets. Exit nodes route all traffic (the default route, 0.0.0.0/0) through a specific node. This is the traditional "VPN" behavior — all your internet traffic goes through the exit node.

Subnet Router
  • Routes specific subnets (e.g., 192.168.1.0/24)
  • Internet traffic goes out your local connection
  • Use case: access internal networks
Exit Node
  • Routes ALL traffic (0.0.0.0/0)
  • Internet traffic exits via the exit node's connection
  • Use case: secure untrusted networks, geo-location

Setting Up an Exit Node

Server Side (the exit node)

console
console

        $ sudo tailscale up --advertise-exit-node

      

Then approve in the admin console or via autoApprovers.

Client Side (use the exit node)

console
console

        $ sudo tailscale up --exit-node=myserver

        

        # To stop using the exit node:

        $ sudo tailscale up --exit-node=

      

Warning

Bandwidth consideration: All your traffic flows through the exit node, which means the exit node's bandwidth is your bandwidth limit. A Raspberry Pi on a residential connection is a poor exit node for heavy usage. A cloud VM with 1 Gbps+ is ideal.

Taildrop

Peer-to-peer file transfer between tailnet devices. No cloud storage intermediary, no size limits, no file type restrictions. Files transfer directly over the WireGuard connection.

console
console

    $ tailscale file cp ./backup.tar.gz myserver:

    Sending backup.tar.gz to myserver... 100% (2.4 GB)

    

    # On the receiving end:

    $ tailscale file get ~/received/

    Received backup.tar.gz (2.4 GB) from workstation

  

Tip

Like AirDrop, but cross-platform and anywhere: Works between Linux, macOS, Windows, iOS, Android. Files go peer-to-peer (or through DERP if direct fails), encrypted end-to-end by WireGuard. No file size limits.

Tailscale Funnel

Funnel is the reverse of everything else Tailscale does. Instead of making private services reachable within the tailnet, Funnel exposes a local port to the public internet via Tailscale's infrastructure.

Public Internet
Anyone
Tailscale Funnel
hostname.ts.net:443
Your Device
localhost:3000
console
console

    $ tailscale funnel 3000

    Available on the internet:

      https://mydevice.tail1234.ts.net/

      |-- proxy http://127.0.0.1:3000

  

Funnel Details

  • HTTPS only: Tailscale terminates TLS with a Let's Encrypt certificate for *.ts.net
  • Port limitations: Only ports 443, 8443, and 10000 are available for Funnel
  • Use cases: Webhook receivers, demo deployments, sharing local dev server, CI/CD callbacks
  • Not a CDN: Traffic goes through Tailscale's infrastructure to your device. Latency and bandwidth depend on your connection.
Tailscale Serve (tailnet only)
  • Exposes a local port to your tailnet
  • Only reachable by tailnet members
  • No public DNS/TLS needed
  • tailscale serve 3000
Tailscale Funnel (public internet)
  • Exposes a local port to the public internet
  • Anyone with the URL can reach it
  • TLS terminated by Tailscale (Let's Encrypt)
  • tailscale funnel 3000

Tailscale SSH

Replaces SSH key management entirely. Authentication is handled by Tailscale identity — no authorized_keys, no key rotation, no SSH CA infrastructure.

console
console

    $ tailscale ssh user@myserver

    Welcome to myserver (Ubuntu 24.04)

    user@myserver:~$

  

How Tailscale SSH Works

  • Server side: Tailscale runs an SSH server embedded in the Tailscale daemon. When enabled, it intercepts SSH connections on the Tailscale interface.
  • Authentication: Instead of SSH keys, the server verifies the connecting peer's Tailscale identity (which user, which device, which tags). This is enforced via ACLs.
  • Authorization: SSH ACLs in the Tailscale policy define who can SSH into what, with what user:
bash
{
  "ssh": [
    {
      "action": "accept",
      "src":    ["group:ops"],
      "dst":    ["tag:server"],
      "users":  ["root", "deploy"],
    },
    {
      "action": "accept",
      "src":    ["group:engineering"],
      "dst":    ["tag:server"],
      "users":  ["deploy"],  // No root for eng
    },
  ],
}

Tip

Session recording: Tailscale SSH can record SSH sessions (audit logs) and require re-authentication for sensitive commands. This gives you compliance-friendly audit trails without deploying a bastion host or SSH CA.

Key Expiry & Node Authorization

Key Expiry

  • Each node's WireGuard key has an expiration (default: 180 days)
  • Expired nodes must re-authenticate (OAuth login again)
  • Configurable per node or by tag
  • Can disable key expiry for servers (--auth-key with no expiry flag)
  • Prevents stale/compromised devices from maintaining access indefinitely

Node Authorization

  • New devices need admin approval before joining the tailnet
  • Pre-authorized keys: tailscale up --auth-key=tskey-... for headless servers
  • Auto-approve by tag via autoApprovers in ACLs
  • Prevents unauthorized devices from joining even if they have valid credentials
console
console

    $ # Generate a pre-auth key for automated server provisioning

    $ tailscale up --auth-key=tskey-auth-kAbCdEf123-ABCdef123456789

    

    # Reusable keys (for provisioning multiple servers):

    # Created in admin console with "reusable" flag

    # Can also be tagged: --auth-key auto-assigns tag:server

  

Device Posture & Compliance

Tailscale can restrict access based on device state — not just identity. This is useful for compliance-heavy environments (SOC 2, HIPAA, etc.).

Posture Check What It Verifies Use Case
OS Version Minimum OS version (e.g., macOS 14+) Ensure devices have recent security patches
Tailscale Version Minimum client version Ensure latest security fixes are deployed
Disk Encryption FileVault (macOS), BitLocker (Windows) Compliance: data at rest must be encrypted
Screen Lock Screen lock is enabled Prevent physical access to unlocked devices
Identity Provider Valid, active IDP session Revoked IDP accounts lose tailnet access immediately

Note

Integration with MDM: Device posture works best alongside mobile device management (MDM). Tailscale checks what the MDM reports about the device, and ACLs can reference posture attributes. This bridges the gap between network access control and endpoint management.

Feature Overview

Feature What It Does Key Command / Config
MagicDNS Automatic DNS for all tailnet devices hostname.tailnet.ts.net
ACLs Fine-grained access control (tags, groups, ports) HuJSON policy file
Subnet Routers Bridge tailnet to non-Tailscale networks --advertise-routes=10.0.0.0/24
Exit Nodes Route all traffic through a node --advertise-exit-node / --exit-node=host
Taildrop P2P file transfer between tailnet devices tailscale file cp ./f host:
Funnel Expose local port to public internet tailscale funnel 3000
Serve Expose local port within tailnet tailscale serve 3000
Tailscale SSH Identity-based SSH (no keys needed) tailscale ssh user@host
Key Expiry Force periodic re-authentication Configurable per node/tag (default 180 days)
Device Posture Restrict access based on device state Admin console posture policies
Solidnines — solidnines.com