Skip to main content
Headscale supports subnet routing and exit nodes, allowing you to access devices not running Tailscale and route internet traffic through specific nodes.

Subnet Routing

Subnet routing lets you access an entire network through a single Tailscale-connected node. For example, access your home network (192.168.1.0/24) from anywhere.

Setting Up a Subnet Router

1

Enable IP forwarding on the router node

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
2

Advertise routes when connecting

sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-routes=192.168.1.0/24,192.168.3.0/24 \
  --authkey YOUR_PREAUTH_KEY
You can advertise multiple subnets by separating them with commas.
3

View advertised routes

docker exec headscale headscale routes list
Output:
ID | Node          | Prefix           | Advertised | Enabled | Primary
1  | home-router   | 192.168.1.0/24  | true       | false   | -
2  | home-router   | 192.168.3.0/24  | true       | false   | -
4

Enable the routes

docker exec headscale headscale routes enable --route-id 1
docker exec headscale headscale routes enable --route-id 2

Configure Firewall (Linux)

For the subnet router to work, configure the firewall:
# Allow forwarding on tailscale interface
sudo iptables -A FORWARD -i tailscale0 -j ACCEPT
sudo iptables -A FORWARD -o tailscale0 -j ACCEPT

# Save rules (Ubuntu/Debian)
sudo netfilter-persistent save

Using Subnet Routes

On client devices, accept routes:
sudo tailscale up \
  --login-server http://localhost:8000 \
  --accept-routes \
  --authkey YOUR_KEY
Now you can access devices on the advertised networks:
# Ping device on subnet
ping 192.168.1.100

# SSH to device on subnet
ssh [email protected]

# Access web interface
curl http://192.168.1.1

Auto-Approval of Routes

Automate route approval using ACL policies in config/policy.json:
config/policy.json
{
  "tagOwners": {
    "tag:servers": ["group:admins"]
  },
  "autoApprovers": {
    "routes": {
      "192.168.0.0/16": ["tag:servers"],
      "10.0.0.0/8": ["tag:servers"]
    }
  }
}
Create pre-auth keys with the tag:
docker exec headscale headscale preauthkeys create \
  --user myuser \
  --reusable \
  --expiration 24h \
  --tags tag:servers
Routes advertised by nodes with tag:servers will be automatically approved.

Exit Nodes

Exit nodes route all your internet traffic through a specific node in your network.

Setting Up an Exit Node

1

Enable IP forwarding

sudo sysctl -w net.ipv4.ip_forward=1
sudo sysctl -w net.ipv6.conf.all.forwarding=1
2

Configure NAT (Network Address Translation)

# Replace eth0 with your internet-facing interface
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

# Save rules
sudo netfilter-persistent save
3

Advertise as exit node

sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-exit-node \
  --authkey YOUR_PREAUTH_KEY
4

Approve the exit node

  1. Open http://localhost:3001/admin/
  2. Go to Routes page
  3. Find the exit node route (0.0.0.0/0 or ::/0)
  4. Click Approve

Using an Exit Node

On a client device:
tailscale exit-node list

Exit Node Use Cases

Secure Public WiFi

Route traffic through your home network when using untrusted public WiFi.

Access Region-Specific Content

Route traffic through a node in a different geographic location.

Bypass Network Restrictions

Access services blocked on your current network.

Privacy Protection

Hide your IP address from websites you visit.

Combining Exit Node and Subnet Routes

sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-exit-node \
  --advertise-routes=192.168.1.0/24 \
  --authkey YOUR_KEY
This node serves as both:
  • An exit node for internet traffic
  • A subnet router for local network access

Auto-Approval for Exit Nodes

config/policy.json
{
  "tagOwners": {
    "tag:servers": ["group:admins"]
  },
  "autoApprovers": {
    "exitNode": ["tag:servers", "tag:personal"]
  }
}
Exit nodes with these tags will be automatically approved.

Advanced Routing Scenarios

Multiple Subnet Routers

Advertise different subnets from different nodes:
# Home router
sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-routes=192.168.1.0/24 \
  --authkey YOUR_KEY

# Office router
sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-routes=10.0.1.0/24 \
  --authkey YOUR_KEY

Route-Specific Access Control

Control which devices can access specific routes:
config/policy.json
{
  "acls": [
    {
      "action": "accept",
      "src": ["tag:personal"],
      "dst": ["192.168.1.0/24:*"],
      "comment": "Personal devices can access home network"
    },
    {
      "action": "accept",
      "src": ["tag:work"],
      "dst": ["10.0.1.0/24:*"],
      "comment": "Work devices can access office network"
    }
  ]
}

Docker Container as Subnet Router

docker-compose.yml
services:
  tailscale-router:
    image: tailscale/tailscale:latest
    hostname: docker-router
    environment:
      - TS_AUTHKEY=YOUR_KEY
      - TS_LOGIN_SERVER=http://host.docker.internal:8000
      - TS_ROUTES=192.168.1.0/24
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - tailscale-router:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    sysctls:
      - net.ipv4.ip_forward=1
      - net.ipv6.conf.all.forwarding=1
    restart: unless-stopped

volumes:
  tailscale-router:

Monitoring Routes

List All Routes

docker exec headscale headscale routes list

Using Headplane GUI

  1. Open http://localhost:3001/admin/
  2. Go to Routes page
  3. View:
    • All advertised routes
    • Which routes are enabled
    • Which node advertises each route
    • Route IDs for management

Check Route Status on Device

# View accepted routes
tailscale status --peers

# Check routing table
ip route | grep tailscale

# Test connectivity
ping 192.168.1.1
traceroute 192.168.1.100

Troubleshooting

Subnet Route Not Working

1

Verify IP forwarding is enabled

cat /proc/sys/net/ipv4/ip_forward
# Should return: 1
2

Check route is enabled

docker exec headscale headscale routes list
# Verify "Enabled" column shows "true"
3

Verify firewall allows forwarding

sudo iptables -L FORWARD -v
# Should show ACCEPT rules for tailscale0
4

Check client accepts routes

tailscale status
# Should show "accept-routes: true"

Exit Node Not Working

1

Verify NAT is configured

sudo iptables -t nat -L POSTROUTING -v
# Should show MASQUERADE rule
2

Test internet connectivity on exit node

ping 8.8.8.8
curl https://ifconfig.me
3

Check exit node is advertised and approved

docker exec headscale headscale routes list
# Look for 0.0.0.0/0 route with Enabled: true
4

Verify client is using exit node

tailscale status
# Should show "exit node: <node-name>"

# Check your public IP
curl https://ifconfig.me
# Should match exit node's public IP

Route Conflicts

Error: Route overlaps with existing route Solution: Be specific with your subnet masks:
# Instead of advertising entire 192.168.0.0/16
# Advertise specific subnets
sudo tailscale up \
  --login-server http://localhost:8000 \
  --advertise-routes=192.168.1.0/24 \
  --authkey YOUR_KEY

Best Practices

Use Specific Subnets

Advertise only the specific networks you need:
--advertise-routes=192.168.1.0/24
Instead of broad ranges like 192.168.0.0/16

Tag Routers

Apply tags to subnet routers and exit nodes:
--tags tag:servers,tag:router
Use with ACL auto-approval

Monitor Route Usage

Regularly check which routes are active:
docker exec headscale headscale routes list

Document Your Network

Keep a record of:
  • Which nodes advertise which routes
  • Subnet ranges and their purposes
  • Exit node locations and capabilities

Next Steps

Build docs developers (and LLMs) love