Dedicated Raspberry Pi outside the cluster: Pi-hole as DNS/DHCP server, PiVPN OpenVPN for secure remote access to self-hosted services.
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 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.
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.
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.
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:
dh.pem file to generatetls-crypt — encrypts and authenticates TLS packets before the handshake even begins: protects against port scanning and DoS attacks on the handshakeuser openvpn / group openvpn — the daemon drops root privileges after startupredirect-gateway def1 — all client traffic goes through the tunnel, preventing leaksAn iptables filtering layer is set up to limit incoming connections on the Raspberry Pi:
Rules are persisted via iptables-persistent to survive reboots.
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.
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.
Once connected, all services become reachable via their internal domain names:
| Service | Internal Domain |
|---|---|
| Immich | immich.cluster |
| Gitea | gitea.cluster |
| Docker Registry | registry.cluster |
| Joplin | joplin.cluster |
| Radicale | radicale.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.
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.