Why a Homelab Router
Most home routers are black boxes: you plug them in, they work, and you never think about them again. Mine is the opposite. It's the most opinionated device in my house — a small OpenWrt box running Nikki, a Mihomo-based transparent proxy, that decides where every packet goes.
Why? Because I wanted routing decisions at the network level, not per device. When you share a home with several people carrying several phones each, installing and maintaining per-device VPN apps is a losing battle. One router, one policy engine, every device covered. I built this for my own household, and it runs 24/7 without anyone having to think about it.
The router does transparent proxying: clients don't know it exists. Their traffic just... goes the right way.
Architecture
Here's the shape of the thing:
┌──────────────┐
│ Internet │
└──────┬───────┬─┘
│ │
┌──────▼──┐ ┌──▼──────────┐
│ VPS │ │ VPN profiles │
│ (Xray) │ │ (fallbacks) │
└──────┬──┘ └──┬──────────┘
│ │
┌──────▼───────▼────────┐
│ OpenWrt + Nikki │
│ (Mihomo transparent │
│ proxy, rule engine) │
└──────┬───────┬────────┘
│ │
┌──────────▼─┐ ┌───▼──────────┐
│ Household │ │ IoT / Smart │
│ devices │ │ home devices │
└────────────┘ └──────────────┘
All outbound traffic passes through Nikki's rule engine. Rules match on domain, IP range, ASN, port, and process heuristics, then steer each connection to a proxy group. DNS goes over HTTPS so the ISP's port-53 hijacking can't poison or log lookups. A YACD dashboard gives me a live view of connections, rule hits, and proxy latency. Tailscale on the router means I can reach the whole LAN — and the dashboard — from anywhere.
Key Features
Per-App Selectors: Telegram and WhatsApp
The heart of the setup is per-app routing. Two messaging apps get their own dedicated selectors — Telegram and Whatsapp — each backed by a curated rule provider. Telegram's rules cover its DC IP ranges, ports, and domains. WhatsApp's cover its domains, media CDN ranges, messaging ports, and push-notification endpoints. Everything else falls through to the default policy.
The key design decision: both selectors include GLOBAL as their first option. That means one tap on the GLOBAL selector cascades everywhere — flip the main mode and both messaging apps follow it. Per-app independence when I want it, one-switch coherence when I don't.
The GLOBAL Group
GLOBAL is a select-type proxy group — the master switch. Its options:
- VPN MANUAL — my primary tunnel, hand-picked and monitored.
- VPN-FALLBACK — automatic fallback chain if the primary degrades.
- ID-LB / SG-LB — load-balanced regional pools for latency-sensitive traffic.
- DIRECT — straight out the ISP, no proxy.
- REJECT — drop it.
Because Telegram and WhatsApp both list GLOBAL first, changing one dropdown re-routes the household's two most-used apps at once. It's the kind of small UX decision that pays off every single day.
Scheduled Quiet Hours
The most human feature: at night, WhatsApp goes quiet. A scheduler flips the Whatsapp selector to a dead-end Blackhole proxy around 23:00. In the early morning, a cron job flips it back to GLOBAL and closes stale connections, restoring normal service before anyone wakes up.
The Blackhole Trick (Worth Bragging About)
My first attempt was obvious: route WhatsApp to REJECT during quiet hours. It didn't work. Messages still got through.
Here's why: a REJECT sends an instant TCP RST. Modern chat apps interpret a fast refusal as "try another path" — they aggressively retry, fall back to different endpoints, different ports, different protocols. The harder you slam the door, the harder they knock.
The fix was counterintuitive: don't refuse. Just never answer.
The Blackhole is a SOCKS5 proxy pointing at an unroutable address. Connections go in and... hang. No RST, no ICMP unreachable, no response at all. The app waits. And waits. Eventually it gives up gracefully — no retry storm, no fallback frenzy, no battery drain from a thousand failed attempts.
The internet itself keeps working — streaming, browsing, everything else is untouched. Only the messaging noise stops. The household sleeps undisturbed.
Sometimes the best firewall rule is no answer at all.
DNS-over-HTTPS
My ISP hijacks port 53 — plain DNS queries get intercepted. So the proxy stack doesn't use plain DNS at all: resolution goes over HTTPS to a public resolver, and fake-IP mode handles the rest. Rule matching happens against resolved IPs without ever trusting the ISP's answers. It's a small change with an outsized privacy payoff, and it also defeats the kind of transparent DNS tampering that breaks rule-based routing.
Challenges & Solutions
Rule providers don't hot-reload
I keep the WhatsApp rules in a local file provider. Editing it and hitting "update providers" in the dashboard does nothing — file providers are only re-read on a full restart. I lost an hour to that before reading the fine print. Now: edit, restart, verify. Established connections keep their old routing until they close, so I also flush connections after a rule change to make the new policy take effect immediately.
IPv6 leaks around the rules
Some apps happily use IPv6 when it's available, bypassing IPv4-only rules entirely. The fix was blunt but effective: disable IPv6 on the proxy stack (ipv6: false) so everything is forced through the IPv4 rule engine where my policies actually apply. Less elegant than dual-stack rules, far more predictable.
Chat apps fight back
Messaging apps are adversarial by design — multiple ports, fallback domains, QUIC, push-notification side channels. Blocking WhatsApp at night took several iterations: domain rules alone weren't enough, port rules alone weren't enough. The working set combines domain suffixes, keyword matches, ASN-based IP rules, and port rules, all in one provider. When one vector leaks, the others catch it. Defense in depth isn't just for firewalls.
Don't break the household
The scariest deploys are the ones where your family is the QA team.
Every rule change goes live for everyone at once. My discipline: change one thing, watch the YACD connection log, keep a known-good config snapshot, and never experiment during the hours people actually need the internet. The router has a revert path, and I keep it tested.
Lessons
1. Route at the network, not the device
Per-device VPN apps don't scale across a household. One transparent proxy on the router covers every phone, laptop, TV, and IoT gadget — including the ones that can't run a VPN client at all.
2. Master switches beat micro-management
The GLOBAL-first design in both app selectors means 90% of daily operation is one dropdown. Granular control exists for the 10% that needs it. Defaults should be one tap; details can be five.
3. Hanging beats rejecting
For quiet hours, a blackhole outperforms a reject. Apps handle silence better than refusal — fewer retries, less battery drain, no error spam. Sometimes the best firewall rule is no answer at all.
4. Don't trust the ISP's DNS
If your provider hijacks port 53, every DNS-based rule you write is built on sand. DoH isn't paranoia; it's a prerequisite for the rule engine to mean anything.
5. Observability is a feature
YACD's live connection view is how I debug everything: which rule matched, which proxy carried it, how long it took. A proxy without a dashboard is a black box with opinions. I check rule hits the way other people check the weather.
What's Next
The setup is stable and largely self-running. On the horizon:
- Per-device policies — guests get a simpler profile than residents.
- Latency-based auto-switching between regional pools instead of manual selection.
- More scheduled profiles — maybe a "focus hours" mode for the work laptop.
- Ad-blocking at the rule layer for the whole LAN.
But honestly? The best homelab is the one you stop thinking about. Most days, I don't. It just works — quietly, in a closet, blinking.
Built for myself and my household. One router, zero per-device apps, and finally — quiet nights.
← Back to home