Building a Bulletproof DNS Architecture for Your Tailscale-Enabled Home Lab
The Challenge
Setting up DNS for a modern home lab is surprisingly complex, especially when you’re running a mix of local services, remote access via Tailscale, and public-facing services. After wrestling with various approaches, I’ve developed a clean architecture that solves all the common pain points.
Requirements That Drove This Design
My home lab needed to handle:
- Remote access to internal services via Tailscale, including both Tailscale-connected and subnet-routed devices
- Valid HTTPS certificates everywhere (yes, even for internal services)
- Strict NFS requirements from my Unifi NAS Pro that demands proper forward and reverse DNS resolution
- Wildcard DNS support for Caddy reverse proxy
- Public services exposed only through Cloudflare Tunnels
- Zero leakage of internal infrastructure to public DNS
If you’re nodding along to these requirements, this solution is for you.
The Architecture: Two Domains, Clear Boundaries
After considering numerous approaches, I landed on a two-domain strategy that provides clean separation between public and private infrastructure:
snadboy.com- Public domain for external services (via Cloudflare Tunnels only)isnadboy.com- Internal domain for all home lab services
Why Two Domains?
You might wonder if this is overkill. Can’t we just use subdomains? Here’s why the two-domain approach wins:
- Security Through Obscurity: Certificate Transparency logs won’t expose your internal infrastructure when you request certs for
nas.isnadboy.comvsnas.internal.snadboy.com - Clean Mental Model: Public vs private is immediately obvious from the domain
- Simplified DNS Management: No complex split-horizon DNS or accidental public exposure
- Cost: At $10-15/year for the second domain, it’s cheaper than the time you’ll spend debugging DNS issues
The Implementation
Component Overview
snadboy.com] CT[Cloudflare Tunnels] end subgraph "Home Network" DHCP[DHCP Server
10.0.1.1] TECH[Technetium DNS
10.0.1.10] NAS[NAS
10.0.1.20] CADDY[Caddy
10.0.1.40] end subgraph "Tailscale Network" MAGIC[MagicDNS
100.100.100.100] REMOTE[Remote Client] end CF --> CT CT --> CADDY DHCP --> TECH MAGIC --> TECH REMOTE --> MAGIC TECH --> NAS
Step 1: Configure Your Internal DNS Server (Technetium)
First, set up your internal DNS server to be authoritative for isnadboy.com:
# /etc/bind/named.conf.local
zone "isnadboy.com" {
type master;
file "/etc/bind/zones/db.isnadboy.com";
};
# Reverse zones for PTR records (critical for NFS)
zone "10.in-addr.arpa" {
type master;
file "/etc/bind/zones/db.10";
};
zone "100.in-addr.arpa" {
type master;
file "/etc/bind/zones/db.100";
};
Create your zone file with all internal services:
# /etc/bind/zones/db.isnadboy.com
$TTL 604800
@ IN SOA technetium.isnadboy.com. admin.isnadboy.com. (
2024010101 ; Serial
604800 ; Refresh
86400 ; Retry
2419200 ; Expire
604800 ) ; Negative Cache TTL
@ IN NS technetium.isnadboy.com.
@ IN A 10.0.1.10
; Internal services
technetium IN A 10.0.1.10
nas IN A 10.0.1.20
proxmox IN A 10.0.1.30
caddy IN A 10.0.1.40
; Wildcards for Caddy
*.caddy IN A 10.0.1.40
*.apps IN A 10.0.1.40
; Aliases
files IN CNAME nas
pve IN CNAME proxmox
Don’t forget the reverse DNS zones (your NAS will thank you):
# /etc/bind/zones/db.10
$TTL 604800
@ IN SOA technetium.isnadboy.com. admin.isnadboy.com. (
2024010101
604800
86400
2419200
604800 )
@ IN NS technetium.isnadboy.com.
; PTR Records
10.1.0.10 IN PTR technetium.isnadboy.com.
20.1.0.10 IN PTR nas.isnadboy.com.
30.1.0.10 IN PTR proxmox.isnadboy.com.
40.1.0.10 IN PTR caddy.isnadboy.com.
Step 2: Configure DHCP
Keep your DHCP configuration simple and universal:
# DHCP Server Configuration
Primary DNS: 10.0.1.10 # Technetium
Secondary DNS: 1.1.1.1 # Cloudflare fallback
Search Domain: isnadboy.com
Important: Do NOT add 100.100.100.100 (Tailscale’s MagicDNS) here. Non-Tailscale devices can’t reach it, and Tailscale’s override feature handles this automatically for Tailscale clients.
Step 3: Configure Tailscale DNS
In the Tailscale Admin Console, configure split DNS to route internal queries to your DNS server:
DNS Settings:
Override local DNS: Yes # This is crucial!
Global nameservers: [empty] # Use Tailscale defaults
Split DNS:
isnadboy.com → 100.x.y.z # Technetium's Tailscale IP
10.in-addr.arpa → 100.x.y.z
100.in-addr.arpa → 100.x.y.z
Search domains: isnadboy.com
Step 4: Set Up SSL Certificates
Even though isnadboy.com is internal-only, you can still get valid Let’s Encrypt certificates using DNS challenges:
Using Certbot:
# Create Cloudflare credentials
cat > ~/.cloudflare.ini << EOF
dns_cloudflare_api_token = your-api-token-here
EOF
chmod 600 ~/.cloudflare.ini
# Request wildcard certificate
certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials ~/.cloudflare.ini \
-d "*.isnadboy.com" \
-d "isnadboy.com"
Using Caddy (Automatic):
*.isnadboy.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
@nas host nas.isnadboy.com
handle @nas {
reverse_proxy 10.0.1.20:443
}
@proxmox host proxmox.isnadboy.com
handle @proxmox {
reverse_proxy 10.0.1.30:8006
}
# Default handler
handle {
respond "Service not configured" 404
}
}
Step 5: Configure Public Domain
Keep snadboy.com strictly for public services via Cloudflare Tunnels:
# Cloudflare DNS for snadboy.com
A tunnel.snadboy.com → Cloudflare Tunnel IP
CNAME *.snadboy.com → tunnel.snadboy.com
# NO internal IPs ever appear here
How It All Works Together
Local Device Resolution:
laptop.local → DHCP DNS (10.0.1.10) → technetium
Query: nas.isnadboy.com
Response: 10.0.1.20 ✓
Remote Tailscale Client Resolution:
remote-laptop → Tailscale MagicDNS (100.100.100.100)
Query: nas.isnadboy.com
MagicDNS: Check split DNS rules
Match: *.isnadboy.com → forward to technetium
Response: 10.0.1.20 (accessible via Tailscale) ✓
Public Service Access:
internet-user → public-service.snadboy.com
Cloudflare DNS → Cloudflare Tunnel → Your Caddy instance
Never exposes internal IPs ✓
Testing Your Setup
Verify everything works correctly:
# From local non-Tailscale device
$ nslookup nas.isnadboy.com
Server: 10.0.1.10
Address: 10.0.1.10#53
Name: nas.isnadboy.com
Address: 10.0.1.20
# From Tailscale client (local or remote)
$ nslookup nas.isnadboy.com
Server: 100.100.100.100
Address: 100.100.100.100#53
Name: nas.isnadboy.com
Address: 10.0.1.20
# Test reverse DNS (critical for NFS)
$ nslookup 10.0.1.20
20.1.0.10.in-addr.arpa name = nas.isnadboy.com.
# Test MagicDNS still works
$ nslookup laptop.tail-scale.ts.net
Server: 100.100.100.100
Address: 100.100.100.100#53
Name: laptop.tail-scale.ts.net
Address: 100.64.0.10
# Verify certificates
$ openssl s_client -connect nas.isnadboy.com:443 -servername nas.isnadboy.com
# Should show valid Let's Encrypt certificate
Key Insights and Gotchas
1. Don’t Overthink Global Nameservers
Tailscale’s split DNS handles your internal domains. You don’t need to add your internal DNS server as a global nameserver unless it provides additional services (like ad blocking).
2. The Override Setting is Magic
Tailscale’s “Override local DNS” setting seamlessly replaces DHCP-provided DNS with MagicDNS for Tailscale clients only. This is why you don’t need conditional DHCP configurations.
3. Certificate Transparency is Real
Every certificate you request is publicly logged. Using a separate internal domain prevents exposing your infrastructure topology through CT logs.
4. PTR Records Matter
Many enterprise services (especially NAS devices) verify both forward and reverse DNS. Don’t skip setting up reverse zones.
5. Wildcard Certificates are Your Friend
Instead of managing dozens of certificates, use wildcards:
*.isnadboy.comfor general internal services*.apps.isnadboy.comfor containerized applications*.k8s.isnadboy.comfor Kubernetes ingresses
Conclusion
This architecture provides:
- ✅ Seamless remote access via Tailscale to all internal services
- ✅ Valid HTTPS everywhere without exposing internal infrastructure
- ✅ Clean separation between public and private services
- ✅ Full DNS compliance for enterprise gear like NAS devices
- ✅ Simple mental model that’s easy to troubleshoot
The two-domain approach might seem like overkill initially, but the clean separation and security benefits far outweigh the minimal additional cost. Your future self will thank you when you’re not debugging split-horizon DNS issues at 2 AM.
Quick Reference
Domains:
Public: snadboy.com (Cloudflare → Tunnels only)
Internal: isnadboy.com (All home lab services)
DHCP Config:
DNS: [10.0.1.10, 1.1.1.1]
Search: isnadboy.com
Tailscale Config:
Override DNS: Yes
Split DNS: isnadboy.com → technetium-tailscale-IP
Certificates:
Method: DNS-01 challenge via Cloudflare API
Coverage: *.isnadboy.com
Result:
Local devices: Direct to internal DNS
Tailscale devices: MagicDNS → Split DNS → Internal DNS
Public access: Cloudflare Tunnels only
Happy labbing! 🚀