Accessing Your Home LAN from Outside with Tailscale Subnet Router
I want to access my home NAS or server while away from home. But VPN setup is difficult... I do not have a VPN router either... Tailscale solves these problems.
In this article, I will explain how to build an environment using Docker that allows you to access your entire home LAN from outside using Tailscale Subnet Router functionality.
1. Requirements Overview
What We Want to Achieve
- VPN connection to home LAN (192.168.x.x) from smartphone/PC while away
- Name resolution for internal domains (*.example.local) using home DNS server
- Privacy protection so family members can use it but cannot see each other devices
Users
| User | Usage | Remarks |
|---|---|---|
| Admin | Remote management, all LAN access | Full admin privileges |
| Family | Simple LAN access (NAS, etc.) | Limited privileges, privacy protection between terminals |
Functional Requirements
| Requirement | Description |
|---|---|
| Access to home LAN from outside | Reachable to all devices in 192.168.x.0/24 |
| Inter-user communication blocking | For privacy protection, family devices cannot communicate with each other |
| Split DNS | Only specific domains resolved by home DNS when connected to Tailscale |
| Subnet Router only | Exit Node (all traffic forwarding) not required |
Technical Decisions
| Item | Decision | Reason |
|---|---|---|
| Container runtime | Docker | Easy management, reproducibility |
| Network mode | host | Required for Subnet Router |
| State persistence | Named Volume | Re-authentication unnecessary on restart |
| Health check | Built-in endpoint | Easy monitoring integration |
2. Architecture Overview
Overall Configuration Diagram
Communication Flow
Component Roles
| Component | Role | Remarks |
|---|---|---|
| Tailscale Container | Subnet Router, forwards traffic from Tailnet to LAN | Uses SNAT |
| DNS Server (Unbound) | Internal domain name resolution | Split DNS target |
| UFW/iptables | Firewall, controls forwarding | Important settings required |
Importance of SNAT (Source Network Address Translation)
Tailscale enables SNAT by default (--snat-subnet-routes=true). This is equivalent to MASQUERADE (IP masquerade / NAPT) in Linux iptables.
| Setting | Behavior | Notes |
|---|---|---|
| SNAT enabled (default) | Source IP becomes Subnet Router IP | No additional settings needed on LAN devices |
| SNAT disabled | Tailscale IP (100.x.x.x) is sent as-is | Static routes must be set on LAN routers and devices |
SNAT enabled is recommended. No additional settings needed on LAN devices.
3. Design Considerations
3.1 Understanding Split DNS
Split DNS is a mechanism to resolve only specific domains with specific DNS servers.
Benefits:
- Normal internet access uses fast local DNS
- Only home internal domains resolved by home DNS
- No impact on internet speed even when connected to VPN
3.2 Complex Relationship Between UFW and iptables
This is the biggest pitfall. In environments using Docker, multiple firewall layers exist:
Settings Requiring Attention
| Layer | Setting Location | What to Set |
|---|---|---|
| UFW INPUT | ufw allow | WireGuard port (41641/udp) |
| UFW FORWARD | /etc/default/ufw | DEFAULT_FORWARD_POLICY=ACCEPT |
| DOCKER-USER | /etc/ufw/after.rules | RETURN rules for Tailscale CGNAT |
3.3 DNS Server ACL Settings
If using DNS servers like Unbound, you need to allow queries from Tailscale CGNAT (100.64.0.0/10).
With Split DNS, Tailscale clients send queries directly to the DNS server. At this time, the source IP becomes a CGNAT address (100.x.x.x).
3.4 Auth Key Management
Important: If state is persisted with Docker Named Volume, re-authentication is not required on container restart. Auth Key is only needed in the following cases:
- Initial deployment
- When Named Volume is deleted
- When device is deleted from Tailscale Admin Console
4. Configuration Details
From here, I will explain specific configuration methods.
4.1 Tailscale Admin Console Settings
4.1.1 Issuing Auth Key
- Access Tailscale Admin Console
- Settings → Keys → Generate auth key
- Issue with the following settings:
| Item | Setting |
|---|---|
| Description | home-router |
| Expiration | 90 days (or as needed) |
| Reusable | Off (for security) |
| Ephemeral | Off (persistent device) |
| Pre-approved | On |
| Tags | tag:subnet-router |
Save the issued key securely (format: tskey-auth-xxxxx).
4.1.2 ACL Settings
Set the following in Admin Console → Access Controls:
{ "groups": { "group:admin": ["your-admin-email@example.com"], "group:family": ["family-email@example.com"] }, "tagOwners": { "tag:subnet-router": ["group:admin"] }, "hosts": { "home_network": "192.168.x.0/24" }, "acls": [ { "action": "accept", "src": ["group:admin", "group:family"], "dst": ["home_network:*"] }, { "action": "accept", "src": ["tag:subnet-router"], "dst": ["*:*"] } ] } Points:
- group:admin and group:family can access home LAN
- By not defining inter-terminal communication rules, default deny is applied and mutual communication is blocked
4.1.3 DNS Settings (After Deployment)
Set the following in Admin Console → DNS:
| Item | Setting |
|---|---|
| Add Nameserver | Custom: 192.168.x.53 (home DNS server IP) |
| Restrict to domain | example.local |
| Override local DNS | Off |
4.2 Server-Side Preparation
4.2.1 Enabling IP Forwarding
# Create configuration file sudo tee /etc/sysctl.d/99-tailscale.conf << 'EOF' net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 EOF # Apply immediately sudo sysctl -p /etc/sysctl.d/99-tailscale.conf # Verify sysctl net.ipv4.ip_forward # Output: net.ipv4.ip_forward = 1 4.2.2 TUN Device Verification
# Verify TUN device exists ls -la /dev/net/tun # Create if it does not exist sudo mkdir -p /dev/net sudo mknod /dev/net/tun c 10 200 sudo chmod 666 /dev/net/tun 4.3 Docker Configuration
4.3.1 Directory Creation
sudo mkdir -p /opt/tailscale cd /opt/tailscale 4.3.2 Creating docker-compose.yml
sudo tee /opt/tailscale/docker-compose.yml << 'EOF' services: tailscale: image: tailscale/tailscale:v1.94.1 container_name: tailscale hostname: home-router restart: unless-stopped cap_add: - NET_ADMIN - NET_RAW network_mode: host volumes: - tailscale_data:/var/lib/tailscale - /dev/net/tun:/dev/net/tun environment: - TS_AUTHKEY=tskey-auth-xxxxxxx # Set Auth Key here - TS_STATE_DIR=/var/lib/tailscale - TS_ROUTES=192.168.x.0/24 - TS_USERSPACE=false - TS_ACCEPT_DNS=false - TS_EXTRA_ARGS=--port=41641 - TS_ENABLE_HEALTH_CHECK=true - TS_ENABLE_METRICS=true - TS_LOCAL_ADDR_PORT=[::]:9002 volumes: tailscale_data: EOF Configuration Item Descriptions:
| Item | Value | Description |
|---|---|---|
| image | tailscale/tailscale:v1.94.1 | Use specific version |
| network_mode | host | Required for Subnet Router |
| cap_add | NET_ADMIN, NET_RAW | Network operation privileges |
| TS_ROUTES | 192.168.x.0/24 | Subnet to advertise |
| TS_USERSPACE | false | Use kernel mode (recommended) |
| TS_ACCEPT_DNS | false | Do not use Tailscale DNS on server side |
Important Settings:
| Environment Variable | Description | Notes |
|---|---|---|
| TS_AUTHKEY | Authentication key | Needed only for initial deployment |
| TS_STATE_DIR | State storage location | Authentication state is persisted |
| TS_ROUTES | Advertised routes | LAN subnet to be accessible |
4.3.3 Starting the Container
cd /opt/tailscale sudo docker compose up -d # Check logs sudo docker compose logs -f 4.3.4 Operation Verification
# Check container status sudo docker ps | grep tailscale # Check Tailscale status sudo docker exec tailscale tailscale status # Expected output example: # 100.x.x.x home-router your-tailnet.ts.net linux - # 100.x.x.x iphone your-tailnet.ts.net iOS idle 4.3.5 Route Approval
After starting the container, enable 192.168.x.0/24 in Tailscale Admin Console → Machines → home-router → Edit route settings.
4.4 iptables/UFW Configuration
4.4.1 Changing UFW Routed Policy
Since Subnet Router forwards packets, UFW routed policy needs to be changed to allow.
# Check current settings sudo ufw status verbose # Default: deny (incoming), allow (outgoing), deny (routed) ← routed is deny # Edit /etc/default/ufw sudo sed -i 's/DEFAULT_FORWARD_POLICY="DROP"/DEFAULT_FORWARD_POLICY="ACCEPT"/' /etc/default/ufw # Reload UFW sudo ufw reload # Verify sudo ufw status verbose # Default: deny (incoming), allow (outgoing), allow (routed) ← OK if allow 4.4.2 Allowing WireGuard Port
sudo ufw allow 41641/udp comment "Tailscale WireGuard" 4.4.3 DOCKER-USER Chain Configuration (Important)
When using Docker, the DOCKER-USER chain filters traffic from outside. Traffic from Tailscale CGNAT (100.64.0.0/10) needs to be allowed.
# Edit /etc/ufw/after.rules sudo tee -a /etc/ufw/after.rules << 'EOF' # BEGIN TAILSCALE DOCKER INTEGRATION *filter :DOCKER-USER - [0:0] -A DOCKER-USER -j RETURN -s 10.0.0.0/8 -A DOCKER-USER -j RETURN -s 172.16.0.0/12 -A DOCKER-USER -j RETURN -s 192.168.0.0/16 -A DOCKER-USER -j RETURN -s 100.64.0.0/10 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p tcp -m tcp --tcp-flags FIN,SYN,RST,ACK SYN -d 172.16.0.0/12 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 192.168.0.0/16 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 10.0.0.0/8 -A DOCKER-USER -j DROP -p udp -m udp --dport 0:32767 -d 172.16.0.0/12 -A DOCKER-USER -j RETURN COMMIT # END TAILSCALE DOCKER INTEGRATION EOF # Reload UFW sudo ufw reload # Verify sudo iptables -L DOCKER-USER -v -n Important: Without -A DOCKER-USER -j RETURN -s 100.64.0.0/10, UDP packets (including DNS) from Tailscale clients to LAN will be dropped.
4.5 Unbound (DNS Server) Configuration
When using Split DNS, the DNS server needs to allow queries from Tailscale CGNAT (100.64.0.0/10).
4.5.1 Unbound ACL Configuration
# Edit /etc/unbound/unbound.conf.d/access-control.conf sudo tee /etc/unbound/unbound.conf.d/access-control.conf << 'EOF' server: # Localhost access-control: 127.0.0.0/8 allow # LAN access-control: 192.168.x.0/24 allow # Docker networks access-control: 172.16.0.0/12 allow # Tailscale CGNAT (for Split DNS) access-control: 100.64.0.0/10 allow # Deny all others access-control: 0.0.0.0/0 refuse EOF # Restart Unbound sudo systemctl restart unbound # Verify sudo systemctl status unbound 4.5.2 DNS Server UFW Configuration
If the DNS server is on a separate machine, UFW on that machine also needs settings to allow DNS queries from Tailscale CGNAT.
# Run on DNS server side sudo ufw allow from 100.64.0.0/10 to any port 53 proto tcp comment "DNS TCP from Tailscale" sudo ufw allow from 100.64.0.0/10 to any port 53 proto udp comment "DNS UDP from Tailscale" 5. Verification Procedures
5.1 Server-Side Verification
# 1. Container startup verification sudo docker ps | grep tailscale # Expected: tailscale container in Up state # 2. Tailscale authentication verification sudo docker exec tailscale tailscale status # Expected: home-router displayed with 192.168.x.0/24 in Routes # 3. IP forwarding verification sysctl net.ipv4.ip_forward # Expected: net.ipv4.ip_forward = 1 # 4. Health check curl http://localhost:9002/healthz # Expected: 200 OK 5.2 Client-Side Verification
# 1. Tailscale connection verification tailscale status # Expected: Connection successful, 100.x.x.x IP obtained # 2. LAN connectivity verification ping 192.168.x.10 # Expected: Response received # 3. DNS verification (Split DNS) dig grafana.example.local # Expected: Returns internal IP like 192.168.x.10 # 4. Service access curl https://grafana.example.local/ # Expected: Normal response 5.3 Security Verification
# Inter-terminal communication blocking verification (from Admin device to Family device) ping 100.x.x.x # Tailscale IP of Family device # Expected: Timeout (communication not possible) 6. Additional Notes
6.1 Important Settings Not Documented in Design Documents
Important configuration items discovered during actual construction that were missing from documentation:
6.1.1 DNS Server UFW Rules
- Problem: DNS queries timeout even with Split DNS configured
- Cause: DNS server UFW was blocking queries from Tailscale CGNAT (100.64.0.0/10)
- Solution: Add the following to DNS server UFW
sudo ufw allow from 100.64.0.0/10 to any port 53 proto tcp sudo ufw allow from 100.64.0.0/10 to any port 53 proto udp 6.1.2 DOCKER-USER Chain Configuration
- Problem: ping works but DNS (UDP port 53) times out
- Cause: DROP rules in DOCKER-USER chain were dropping UDP packets from Tailscale
-A DOCKER-USER -j DROP -p udp --dport 0:32767 -d 192.168.0.0/16 - Solution: Add the following to /etc/ufw/after.rules
-A DOCKER-USER -j RETURN -s 100.64.0.0/10 6.1.3 UFW Routed Policy
- Problem: Tailscale connects but all communication to LAN times out
- Cause: UFW routed policy was deny by default, blocking packet forwarding
- Solution: Change to
DEFAULT_FORWARD_POLICY="ACCEPT"in /etc/default/ufw
6.2 Troubleshooting
When Split DNS Does Not Work
-
Check client settings
- iOS/Android: Check if Use Tailscale DNS is enabled in Tailscale app settings
- macOS/Windows: Check for conflicts between system DNS settings and Tailscale settings
-
Check Admin Console settings
- Is Restrict to domain correctly configured?
- Is Override local DNS Off?
Check with packet capture
# Run on Subnet Router side sudo tcpdump -i any -n 'port 53' When ping Works But Cannot Access Specific Services
- Check DOCKER-USER chain
sudo iptables -L DOCKER-USER -v -n - Check if RETURN rule for 100.64.0.0/10 exists
Auth Key Expiration
Since state is maintained with Named Volume, this is usually not a problem. However, a new Auth Key is required in the following cases:
- When Named Volume is deleted
- When device is deleted from Tailscale Admin Console
6.3 Operational Notes
Auth Key Rotation
| Scenario | Required Action |
|---|---|
| Container restart | None (state is persisted) |
| Named Volume deletion | Issue new Auth Key, restart container |
| Device deletion from Admin Console | Issue new Auth Key, restart container |
Version Update Procedure
- Check Tailscale Release Notes
- Verify new version in test environment
- Update image tag in docker-compose.yml
docker compose pull && docker compose up -d
6.4 Reference Links
This completes the Tailscale Subnet Router construction. You can now securely access your home LAN from outside.
![[Tailscale] Complete Guide to VPN Connection to Home LAN with Subnet Router](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fstatic.shoz.in%2F2025%2FJul%2Ftailscale-arch.png)





