← Back to projects
Infrastructure In Progress

Homelab Network: Pi-hole & VPN

Dedicated Raspberry Pi outside the cluster: Pi-hole as DNS/DHCP server, PiVPN OpenVPN for secure remote access to self-hosted services.

2024
Infrastructure
In Progress
Pi-hole, OpenVPN, PiVPN, Raspberry Pi, DNS, DHCP, iptables

Context

The Kubernetes cluster exposes several services (Immich, Gitea, Docker Registry, Joplin, Radicale) that I wanted to reach securely from the internet, without exposing them directly. Rather than managing a public reverse proxy, I chose a VPN: a dedicated Raspberry Pi outside the cluster acts as a network gateway.

That same machine runs Pi-hole, which filters ads and trackers for the entire local network.

Pi-hole — DNS and DHCP

The Problem with the ISP Router

Pi-hole works as a DNS resolver: by pointing devices’ DNS to it, it can block ad domains before the request even leaves the network. But my ISP router (Livebox) does not allow specifying a custom DNS server to distribute via DHCP. Result: it’s impossible to force devices to use Pi-hole through the router.

Solution: Hand DHCP Over to Pi-hole

The solution is to disable the router’s DHCP server and enable Pi-hole’s built-in DHCP server. Pi-hole now assigns IP addresses to every device on the network, and simultaneously pushes its own address as the DNS server.

An added benefit: Pi-hole knows the name and IP of every device. Cluster nodes, phones, computers — all declared with their hostnames in Pi-hole, which simplifies local name resolution without touching /etc/hosts on every machine.

PiVPN — Remote Access with OpenVPN

Goal

Once connected to the VPN from the internet, traffic is tunnelled to the local network. This lets me reach all cluster services as if I were physically present, without opening any ports other than the VPN port on the router.

PiVPN simplifies OpenVPN installation on Raspberry Pi and client certificate management.

Server Configuration

The OpenVPN configuration is as follows (sensitive information anonymised):

dev tun
proto udp
port <VPN-port>

ca   /etc/openvpn/easy-rsa/pki/ca.crt
cert /etc/openvpn/easy-rsa/pki/issued/<hostname>.crt
key  /etc/openvpn/easy-rsa/pki/private/<hostname>.key

dh none
ecdh-curve prime256v1

topology subnet
server 10.18.193.0 255.255.255.0

# Pi-hole DNS pushed to VPN clients
push "dhcp-option DNS <IP-pihole>"
push "dhcp-option DOMAIN cluster"

# Prevent DNS leaks on Windows
push "block-outside-dns"

# Full tunnel: all client traffic goes through the VPN
push "redirect-gateway def1 bypass-dhcp"

# Route to local network
push "route 192.168.1.0 255.255.255.0"

client-to-client
client-config-dir /etc/openvpn/ccd
keepalive 15 120
remote-cert-tls client

tls-version-min 1.2
tls-crypt /etc/openvpn/easy-rsa/pki/ta.key

cipher AES-256-CBC
auth SHA256

# Drop privileges after startup
user openvpn
group openvpn
persist-key
persist-tun

crl-verify /etc/openvpn/crl.pem
status /var/log/openvpn-status.log 20
status-version 3
verb 3

Notable points in this configuration:

  • ECDH instead of static Diffie-Hellman — faster, no dh.pem file to generate
  • tls-crypt — encrypts and authenticates TLS packets before the handshake even begins: protects against port scanning and DoS attacks on the handshake
  • TLS 1.2 minimum — older versions are rejected
  • AES-256-CBC / SHA256 — strong encryption and integrity
  • user openvpn / group openvpn — the daemon drops root privileges after startup
  • redirect-gateway def1 — all client traffic goes through the tunnel, preventing leaks

Firewall — iptables

An iptables filtering layer is set up to limit incoming connections on the Raspberry Pi:

  • Only the VPN port (UDP) and SSH are open from the outside
  • Forwarding is enabled only for VPN → local network traffic
  • Established and related connections are accepted, everything else is rejected by default

Rules are persisted via iptables-persistent to survive reboots.

Challenge: Android and DNS over VPN

The Problem

On Android, even when connected to the VPN in full tunnel mode (redirect-gateway def1), DNS requests can bypass the server pushed by the VPN. Android has a Private DNS feature (DNS-over-TLS) that takes priority over DNS distributed by DHCP or VPN, effectively short-circuiting Pi-hole.

Solution

Disable Private DNS in Android network settings (Network → Private DNS → Off). Once disabled, Android uses the DNS pushed by OpenVPN — Pi-hole’s IP — and ad blocking works correctly, including on mobile when roaming.

Accessing Cluster Services

Once connected, all services become reachable via their internal domain names:

ServiceInternal Domain
Immichimmich.cluster
Giteagitea.cluster
Docker Registryregistry.cluster
Joplinjoplin.cluster
Radicaleradicale.cluster

Traefik, the cluster’s ingress controller, handles routing and internal TLS certificates. Pi-hole resolves .cluster names to the control plane IP, and Traefik dispatches to the correct service.

Planned Improvements

OpenVPN works, but WireGuard offers better performance (lower latency, faster handshake) and a smaller attack surface (roughly 4,000 lines of code vs ~100,000 for OpenVPN). Migration to WireGuard via PiVPN is planned.