Post

Homelab Part 2 - DNS Stack

Homelab Part 2 - DNS Stack

DNS is one of the most critical components of the homelab. If DNS stops working, everything else starts behaving like it is broken even when it is not.

Because most services in the homelab are accessed through domain names and reverse proxies, DNS needed to satisfy a few requirements:

  • Local recursive resolution
  • Ad / tracker filtering
  • Configuration consistency across sites
  • Independence from public DNS providers

This resulted in a two-node DNS architecture.

DNS Stack

The DNS stack consists of three components:

  • Pi-hole
  • Unbound
  • Pi-hole-Updatelists
  • Nebula Sync

They utilize the selfhosted headscale network to keep in sync.

Both On-Prem and OCI environments run the exact same stack.

1
2
3
4
5
DNS Stack
├── Pi-hole
├── Unbound
└── Pi-hole-Updatelists

Each node performs:

  • DNS filtering
  • Recursive resolution
  • Query caching

However, only one node is considered the master.

LocationRoleDeployment
On-PremMasterProxmox LXC
OCISecondaryOCI LXC

The on-prem node acts as the configuration source, while the OCI node acts as a replicated resolver.

Both nodes are fully capable of resolving DNS queries independently.

Why Two DNS Nodes

Running only a single resolver introduces an obvious single point of failure.

Two DNS nodes provide:

  • Geographic redundancy
  • Failover capability
  • Resilience if one tenancy goes offline

The OCI instance is particularly useful because it remains reachable even if the home internet connection is unavailable.

Recursive DNS With Unbound

Both Pi-hole instances use Unbound as their upstream resolver.

Unbound performs true recursive DNS resolution using root hints instead of forwarding queries to public resolvers like Cloudflare or Google.

The lookup process works like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Client
  │
  ▼
Pi-hole
  │
  ▼
Unbound
  │
  ▼
Root DNS Servers
  │
  ▼
TLD Servers
  │
  ▼
Authoritative DNS

Advantages of this approach:

  • No dependency on third-party DNS providers
  • DNSSEC validation
  • Local caching
  • Greater privacy

Root hints are updated periodically to ensure the resolver always knows how to reach the root DNS servers.

Blocklist Management

Pi-hole uses community-maintained blocklists for filtering advertisements, trackers and known malicious domains.

Blocklists are updated automatically using Pi-hole-Updatelists, which runs weekly.

This ensures the resolver stays updated without requiring manual intervention.

Configuration Synchronization

Since two Pi-hole nodes exist, configuration must remain consistent.

This includes:

  • Blocklists
  • Regex filters
  • Whitelist entries
  • DNS records
  • Settings

To solve this problem I use Nebula Sync.

Nebula Sync runs inside a Docker container and synchronizes configuration between the two Pi-hole instances.

Private Network Connectivity

Synchronization traffic flows over the Headscale network.

Headscale provides a self-hosted control plane for the Tailscale protocol and connects all infrastructure nodes into a private overlay network.

1
2
3
4
5
6
7
8
On-Prem Pi-hole (Master)
        │
        │ Headscale Network
        ▼
Nebula Sync Container
        │
        ▼
OCI Pi-hole (Replica)

This allows configuration synchronization without exposing the DNS API publicly.

Configuration Flow

The on-prem Pi-hole acts as the authoritative configuration source.

The flow works like this:

1
2
3
4
5
6
7
8
9
10
Admin Changes Settings
        │
        ▼
On-Prem Pi-hole (Master)
        │
        ▼
Nebula Sync
        │
        ▼
OCI Pi-hole (Replica)

This ensures both DNS nodes behave identically.

Deployment Architecture

Both DNS nodes run inside Proxmox LXC containers.

LXC was chosen instead of Docker for the DNS stack because DNS services often require:

  • Low-level networking
  • Predictable networking behaviour
  • Minimal abstraction

The architecture looks like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
On-Prem Proxmox
└── DNS LXC
    ├── Pi-hole
    ├── Unbound
    └── Pi-hole-Updatelists

OCI Instance
└── DNS LXC
    ├── Pi-hole
    ├── Unbound
    └── Pi-hole-Updatelists

Docker Host
└── Nebula Sync

Result

This setup provides:

  • Recursive DNS resolution
  • Network-wide ad blocking
  • Configuration synchronization
  • Geographic redundancy

While it is not yet fully highly available, it provides a good balance between simplicity and reliability for a homelab environment.

Future Improvements

Some improvements planned for the DNS layer include:

  • Automatic DNS failover
  • Health checks between resolvers
  • Metrics collection for Unbound
  • Prometheus integration

DNS outages are subtle but extremely disruptive, so improving reliability here remains a priority.

Thanks!

Thank you for going through this DNS saga for my homelab.

If you have some suggestins for me, do drop them over any of socials at links.hudater.dev

Do leave your comment, react and share this with your Tech Ninja Friends!

This post is licensed under CC BY 4.0 by the author.