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:

  1. Security Through Obscurity: Certificate Transparency logs won’t expose your internal infrastructure when you request certs for nas.isnadboy.com vs nas.internal.snadboy.com
  2. Clean Mental Model: Public vs private is immediately obvious from the domain
  3. Simplified DNS Management: No complex split-horizon DNS or accidental public exposure
  4. 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

graph TB subgraph "Public Internet" CF[Cloudflare DNS
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.com for general internal services
  • *.apps.isnadboy.com for containerized applications
  • *.k8s.isnadboy.com for 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! 🚀