A local game cache is useful until DNS sends a room full of PCs to a cache that has gone offline.
There is another wrinkle when you have several VLANs: the best cache for one network may be a poor choice for another. A server with a fast connection to your gaming PCs might sit behind a much slower firewall path for everyone else.
I wanted those two decisions to live together: which cache should this client use, and is that cache available?
The result is LanCache Router, an open-source app for Technitium DNS Server. It routes supported download domains to a healthy cache based on the client's network, with an ordered list of standbys and normal DNS resolution when none is available.
Why I moved the decision into Technitium
LanCache stores supported game and update downloads locally, so multiple machines can reuse downloaded content. DNS helps clients find it by returning the cache's address for the relevant download hostnames.
My previous setup spread the routing and health decisions across several components: dnsdist chose a DNS backend by client subnet, lancache-dns supplied the cache answers, and a separate checker toggled availability. Technitium handled normal DNS resolution.
That left several services coordinating a decision I wanted to make inside the DNS server. It also tied the setup to one cache and a hard-coded subnet.
Technitium supports apps that participate in query processing, so I brought the routing, domain matching, and health checks into one app. LanCache still handles the actual downloads and storage.
DNS chooses the destination. The download connection runs directly between the client and the cache.
A route for each client network
The configuration describes cache servers and the networks allowed to use them. A setup could look like this:
| Client network | Preferred cache | If it is unavailable |
|---|---|---|
| Gaming PCs | Cache in the gaming room | Use the standby cache. |
| Another building | Cache in that building | Use its configured standby. |
| Guests | No cache route | Use normal DNS resolution. |
For a supported query reaching the app, the decision is straightforward:
- Find the first enabled route matching the client's source address.
- Select the first Healthy server in that route's preference list.
- Check whether the queried name belongs to the active cache-domain list.
- Return the cache address for an A lookup, or let Technitium continue if those checks do not pass.
A route can also explicitly pass queries through, which is useful for exempting a test PC from its subnet's cache rule.
Routes use explicit priorities: lower numbers run first, and the first match wins. Put a device exception before the broader subnet route. If the winning route has no healthy server, the app passes through rather than trying a later route.
One subnet, two caches
Here is a complete configuration for a preferred cache and a standby:
{ "enabled": true, "cacheServers": [ { "name": "cache-1", "address": "192.168.17.44" }, { "name": "cache-2", "address": "192.168.18.44" } ], "routes": [ { "priority": 10, "sourceCidr": "192.168.17.0/24", "servers": ["cache-1", "cache-2"] } ] } Replace the addresses and subnet with your own. Both caches must be reachable by the routed clients and by the DNS server.
For matching download domains, clients in 192.168.17.0/24 receive cache-1 while it is Healthy. If it becomes unavailable, new answers use cache-2, provided that server is Healthy. If neither qualifies, Technitium resolves the lookup normally under its existing rules.
Clients outside the subnet have no matching route, so their lookups follow normal DNS processing. You could add a second route for 192.168.18.0/24 and reverse the preference list, giving each network its nearby cache first.
This is ordered failover. The app selects one cache at a time for each route; it does not balance downloads across all healthy servers.
The route's preference order decides each new answer. Clients may retain an earlier answer until its TTL expires.
Health checks without probing on every lookup
The default check requests /lancache-heartbeat over HTTP on port 80 and accepts a 200 or 204 response. TCP and HTTPS checks are also available, with shared defaults and per-server overrides.
Probes run in the background. Query handling reads the latest health state and domain matcher without making a network request of its own.
By default, two consecutive failures mark a cache Unhealthy, and three consecutive successes make it Healthy. Newly started caches begin Unknown and use faster startup checks until their state settles. A preferred cache returns to selection automatically after it recovers.
There is a timing limit here that matters in practice: changing a DNS answer does not move an existing download.
The app first needs to detect the failure. Clients may then retain an earlier answer until its TTL expires; the default is 60 seconds. Open connections continue independently. Failover improves subsequent lookups, but it cannot make an interrupted connection switch servers instantly.
Keeping the domain list current
The app downloads the community uklans/cache-domains list, with a configurable refresh schedule. You can include or exclude services, add download hosts, and carve individual names out of broader wildcard entries.
A failed refresh keeps the last successful list active. Each DNS node also saves a snapshot for ordinary restarts, so a temporary source outage does not require discarding a list it already has. A first installation still needs a successful download before cache-domain matching becomes available.
Before trying it on your network
There are a few boundaries to account for:
- Technitium needs to see the client address. If NAT or a forwarding resolver makes every query appear to come from one gateway, subnet routes cannot distinguish the original clients.
- Health checks run from the DNS node. A successful probe cannot detect a firewall rule that blocks only the clients. In a cluster, each node probes independently.
- Cache addresses are currently IPv4 only. Eligible AAAA, HTTPS, and SVCB queries receive empty successful answers by default, keeping IPv6 addresses and service hints from bypassing the cache. Those modes are configurable.
- Existing DNS rules still matter. Local zones and other apps can take precedence. Passthrough follows Technitium's normal behavior, including its blocking and upstream settings.
There is also no per-service routing yet: a client cannot send Steam downloads to one cache and Epic downloads to another through separate service rules. IPv6 cache addresses, per-service routing, and a diagnostic TXT query are recorded in the backlog.
Try it
You need a working LanCache and a compatible Technitium installation. Install the app ZIP through Technitium's Apps page, open its configuration, and start with one client subnet. The packaged configuration is disabled until you supply your settings and enable it.
Once the cache is Healthy and the domain list has loaded, test from a client in that subnet. Replace the DNS address below with yours:
dig @192.168.17.53 lancache.steamcontent.com A +short With the default list and the example configuration, the answer should be 192.168.17.44 while the preferred cache is Healthy. The installation guide covers package compatibility, building from source, and checking fallback.
The project is MIT licensed, with a wiki covering configuration, routing examples, and troubleshooting.
If this fits your setup, take a look at the repository. Feedback from people running several VLANs or multiple DNS nodes would be especially useful. An issue with your intended topology, expected behavior, and relevant logs gives me something concrete to work with.
How are you handling cache availability across your networks today?


