← All writing
blog

WireGuard on Kubernetes with Adblocking

The Internet is simply unusable with all the ads floating around. While browser extensions like uBlock Origin work for tech-savvy users, most people lack ad protection. I wanted a DNS-level solution combined with VPN capabilities to serve my family.

Why This Approach?

Three key use cases motivated this setup:

  • Preventing ISP data collection on browsing patterns
  • Circumventing internet censorship
  • Remote access to home network

I evaluated mobile solutions like Blokada and DNS66 but found limitations. Android’s VPN service restriction prevents simultaneous VPN connections, meaning custom VPN solutions expose browsing to ISPs. A publicly accessible DNS and VPN server resolves this constraint.

Technology Selection

AdGuard Home was chosen over Pi-Hole because it offers:

  • Native DNS-over-TLS support
  • Single configuration file
  • Golang implementation using fewer resources

WireGuard was selected for VPNs due to its speed, auditable codebase, cross-platform support, and Linux kernel integration. Kilo, a WireGuard-based network overlay for Kubernetes, enabled the complete setup while securing inter-pod communication and allowing cluster debugging.

AdGuard Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: adguardhome
spec:
selector:
matchLabels:
app: adguardhome
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
template:
metadata:
labels:
app: adguardhome
spec:
volumes:
- name: tls-cert-secret
secret:
secretName: production-tls-cert
- name: adguard-config
hostPath:
path: "/path/to/store/conf"
type: DirectoryOrCreate
- name: adguard-logs
hostPath:
path: "/path/to/store/work"
type: DirectoryOrCreate
containers:
- name: adguardhome
image: adguard/adguardhome:v0.102.0
ports:
- containerPort: 53
hostPort: 53
protocol: UDP
- containerPort: 53
hostPort: 53
protocol: TCP
- containerPort: 853
hostPort: 853
protocol: TCP
volumeMounts:
- name: tls-cert-secret
mountPath: /certs
- name: adguard-config
mountPath: /opt/adguardhome/conf
- name: adguard-logs
mountPath: /opt/adguardhome/work
terminationGracePeriodSeconds: 20
---
apiVersion: v1
kind: Service
metadata:
name: adguardhome
labels:
app: adguardhome
spec:
type: ClusterIP
selector:
app: adguardhome
ports:
- port: 80
targetPort: 80
protocol: TCP

The rolling update strategy intentionally terminates existing pods without waiting for new ones. This is necessary because hostPort binding to port 53 prevents concurrent pod scheduling.

Initial setup requires accessing the admin UI on port 3000. After configuration, revert the service targetPort to 80.

Optional enhancements include:

  • DNSSEC for DNS response authenticity verification
  • DNS-over-TLS using certificate paths /certs/tls.crt and /certs/tls.key

Mobile DNS Configuration

Android Pie and later: Navigate to Settings > WiFi and Internet > Private DNS, select “Private DNS Hostname Provider,” and enter your configured domain. Configure routers at the network level for device-wide protection.

WireGuard Setup

Prerequisites

Install WireGuard on both server (k3s cluster) and client machines. Install kgctl on the client:

Terminal window
go get github.com/squat/kilo/cmd/kgctl

Kilo Installation

Download and modify the k3s manifest:

Terminal window
curl -LO https://raw.githubusercontent.com/squat/kilo/master/manifests/kilo-k3s.yaml

Add --mesh-granularity=full to the kilo container args in the DaemonSet:

containers:
- name: kilo
image: squat/kilo
args:
- --kubeconfig=/etc/kubernetes/kubeconfig
- --hostname=$(NODE_NAME)
- --mesh-granularity=full
env:
- name: NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName

Apply the manifest:

Terminal window
kubectl apply -f kilo-k3s.yaml

Client Key Generation

Terminal window
wg genkey | tee privatekey | wg pubkey > publickey

Server-Side Peer Authorization

Create archie.yaml:

apiVersion: kilo.squat.ai/v1alpha1
kind: Peer
metadata:
name: archie
spec:
allowedIPs:
- 10.120.120.1/32
publicKey: CLIENT_PUBLIC_KEY
persistentKeepalive: 10
Terminal window
kubectl apply -f archie.yaml

Generate Client Configuration

Terminal window
kgctl showconf peer archie

Output:

[Peer]
AllowedIPs = 10.42.0.0/24, 10.42.0.0/32, 10.4.0.1/32
Endpoint = YOUR_SERVER_IP:51820
PersistentKeepalive = 10
PublicKey = SERVER_PUBLIC_KEY

Client Configuration

Create /etc/wireguard/adguard.conf:

Terminal window
[Interface]
Address = 10.120.120.1/32
PrivateKey = CLIENT_PRIVATE_KEY
DNS = YOUR_SERVER_IP
[Peer]
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = YOUR_SERVER_IP:51820
PersistentKeepalive = 10
PublicKey = SERVER_PUBLIC_KEY

The 0.0.0.0/0, ::/0 configuration routes all traffic through the VPN interface.

Connection Management

Terminal window
wg-quick up adguard

Verify connectivity at https://ifconfig.io - your IP should match the server’s IP.

Terminal window
wg-quick down adguard

Mobile Integration

Generate a QR code for Android app scanning:

Terminal window
qrencode -t ansiutf8 < /etc/wireguard/adguard.conf

Install the WireGuard Android app from F-Droid and scan the code to import the configuration.

Results

This setup provides:

  • Complete ISP traffic isolation
  • DNS-level ad filtering across all connected devices
  • Network malware protection
  • Cross-network device communication regardless of physical location
  • Centralized infrastructure management via Kubernetes