Status: CURRENT
Last reviewed: 31 August 2026
Applies to: Tailscale Split DNS, subnet routers, AdGuard Home and a private reverse proxyThis setup lets you keep selected services publicly reachable while resolving your private services to internal IP addresses only when you’re at home or connected to Tailscale. Basically: public where I actually want public, private everywhere else. Much nicer than throwing the whole bloody homelab onto the internet.
What this setup does
I use the same domain for both public and private services. Public services can still be reachable normally, while private subdomains resolve through my own DNS server and point at my internal reverse proxy when I’m connected to Tailscale. One domain, two completely different paths depending on where I am. Neat little trick 🙂
For this example:
- Domain:
richay.au - AdGuard Home:
10.10.10.10— local DNS / ad blocker - Traefik v3:
10.10.10.20— internal reverse proxy - AdGuard DNS rewrite:
*.richay.au → 10.10.10.20 - Cloudflare Tunnel: only for services I deliberately expose publicly, such as this blog
- Tailscale: remote private access, subnet routing and Split DNS
The important distinction is that DNS and routing are separate things. AdGuard can correctly answer private.richay.au → 10.10.10.20, but your remote device still needs a Tailscale route to 10.10.10.20 or the browser won’t be able to reach it. DNS can tell you exactly where the house is; it doesn’t magically build the bloody road there.
Split DNS is not an access-control system. Split DNS decides which DNS server answers a query. Subnet routes decide how packets reach the private IP. Tailscale Grants/ACLs decide whether that traffic is actually allowed. Three different jobs, even though they’re all holding hands by the end of this guide.
1. Install Tailscale on the subnet router
Install Tailscale on a machine that can reach both your DNS server and reverse proxy over the LAN. It doesn’t need to be anything fancy — it just needs to sit in the right spot and not randomly disappear when you need it.
In this example I’m installing Tailscale on the Traefik host at 10.10.10.20. That machine will also act as a Tailscale subnet router.
You can install Tailscale using the current Linux installation instructions.
2. Enable IP forwarding
Tailscale requires IP forwarding on a Linux subnet router. For most modern Linux distributions with /etc/sysctl.d:
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf
sudo sysctl -p /etc/sysctl.d/99-tailscale.confIf you’re only using IPv4 you technically only need IPv4 forwarding, but I’ve left Tailscale’s current IPv4 + IPv6 example intact. Future me can deal with IPv6 properly one day instead of pretending it doesn’t exist 😅.
3. Advertise the private routes
This is the bit I had slightly wrong in the original version of this guide. DNS was working, so naturally I stared at DNS for too long even though the missing bit was actually routing. Classic.
The remote Tailscale client needs to reach both the DNS server at 10.10.10.10 and the reverse proxy at 10.10.10.20. Advertising only the AdGuard address lets DNS queries work, but doesn’t create a route to the private IP returned by AdGuard. So you get a perfectly correct DNS answer followed immediately by absolutely fuck-all happening in the browser.
For the tightest routing, advertise only those two hosts:
sudo tailscale set --advertise-routes=10.10.10.10/32,10.10.10.20/32If you deliberately want your Tailscale devices to access the whole LAN instead, you could advertise the subnet:
sudo tailscale set --advertise-routes=10.10.10.0/24I prefer the two /32 routes for this guide because they expose only the DNS and reverse-proxy hosts rather than the whole subnet. No need to hand Tailscale the keys to the whole neighbourhood when two houses will do.
4. Approve the routes in Tailscale
Open the Tailscale Machines page, locate the subnet-router machine, open its menu and choose Edit route settings.
Approve 10.10.10.10/32 for AdGuard and 10.10.10.20/32 for Traefik.
Route approval and Tailscale access policy are separate. The route makes those addresses reachable through the tailnet; your Grants/ACLs decide who is actually allowed to connect to them. Reachable does not automatically mean invited in.
Linux client note: Windows, macOS, iOS and Android accept advertised subnet routes by default. Linux clients do not. On a Linux client that needs to use these routes, run:
sudo tailscale set --accept-routes=true5. Configure Tailscale Split DNS
Now open the Tailscale DNS page.
- Select Add nameserver → Custom.
- Enter your internal DNS server:
10.10.10.10. - Enable Restrict to search domain.
- Enter
richay.auas the search domain. - Save the nameserver.
Tailscale calls this a restricted nameserver. It is also commonly called Split DNS. Same idea, slightly more corporate name.
With that configured, DNS queries for names under richay.au are sent to AdGuard while you’re connected to Tailscale. Normal DNS queries can continue using the device’s normal resolver.
You do not need to enable Override DNS servers just to use this Split DNS setup. Override DNS servers is for forcing global tailnet DNS behaviour, whereas here we only want richay.au queries sent to AdGuard. No point hijacking every DNS query when we’re only interested in our own little corner of the internet.

6. Configure AdGuard Home
AdGuard now needs to return the internal Traefik address for your private services. This is where the whole thing starts feeling suspiciously clever for something that’s actually pretty simple.
In my setup I use a wildcard DNS rewrite equivalent to:
*.richay.au → 10.10.10.20This means names such as:
code-server.richay.au
proxmox.richay.au
homeassistant.richay.aucan all resolve to Traefik’s private IP without creating public DNS records for every private service. Which is exactly what I want — nice names internally, no giant public sign saying “hey look, here’s all my homelab shit”.
The original article called this a CNAME record. That’s not quite right when you’re directly rewriting a wildcard name to an IP address — this is better described as an AdGuard wildcard DNS rewrite. Tiny terminology fix, but if I’m going back through these articles I may as well stop lying to future me 😄.
7. Let Traefik route the hostname
Traefik can continue routing each hostname to the correct application exactly as it does on your LAN. It doesn’t care that the request came in through Tailscale; as far as Traefik is concerned, business as usual.
The connection path is:
Remote device
↓
Tailscale
↓
Split DNS query for app.richay.au
↓
AdGuard 10.10.10.10
↓
Returns 10.10.10.20
↓
Tailscale subnet route
↓
Traefik 10.10.10.20
↓
Private web appPublic applications can remain completely separate. For example, this blog can still have a public Cloudflare Tunnel while code-server.richay.au exists only in your private DNS. Public stuff stays public, private stuff stays private, and the two don’t need to become one giant networking casserole.
Access control
Don’t confuse private DNS with permission. Hiding the sign doesn’t lock the door.
A device that knows the private IP does not automatically gain access unless your Tailscale policy allows the connection. Tailscale now recommends Grants for new access-control policies; legacy ACLs still work but aren’t receiving new features.
If your tailnet is just your own trusted devices, the default policy may be enough. For a larger tailnet, use Grants to limit which users/devices can reach the advertised private addresses and ports.
Test the setup
Connect a phone or laptop to Tailscale while away from your home network. Mobile data is handy here so you know you’re genuinely testing the remote path and not accidentally proving that your own Wi-Fi still works. First confirm the private DNS server and reverse proxy are reachable:
ping 10.10.10.10
ping 10.10.10.20On Windows, Tailscale recommends using Resolve-DnsName rather than relying on nslookup when testing advanced DNS behaviour such as Split DNS:
Resolve-DnsName -Name code-server.richay.auThe result should return 10.10.10.20. Then open the private hostname in your browser:
https://code-server.richay.auTroubleshooting
If DNS resolution fails, check that Tailscale can reach AdGuard and that the restricted nameserver is configured for the correct domain. Don’t start kicking Traefik yet — it hasn’t even been invited to this part of the problem.
If DNS resolves correctly to 10.10.10.20 but the website won’t open, that’s usually a routing or access-policy problem, not DNS. Confirm the Traefik address is included in the advertised and approved routes. This is the point where DNS has done its job and gets to sit back while routing takes the blame.
Useful checks:
tailscale status
tailscale status --routesIf a Linux client can reach normal Tailscale 100.x addresses but not the advertised LAN addresses, enable subnet route acceptance:
sudo tailscale set --accept-routes=trueIf Linux DNS itself is behaving strangely, make sure your resolver configuration is compatible with Tailscale. On distributions that use it, Tailscale recommends systemd-resolved rather than a manually overwritten or locked /etc/resolv.conf.
The end result
With this setup I can keep genuinely public services public, while the rest of my homelab uses the same nice *.richay.au hostnames without exposing those applications to the internet.
At home, AdGuard resolves the private names directly. Away from home, Tailscale Split DNS sends only richay.au lookups back to AdGuard, and the subnet routes carry the connection to Traefik.
Much nicer than remembering a pile of IP addresses and ports 🙂
Once this is working it’s one of those setups that feels almost suspiciously tidy: same domain everywhere, no public DNS records for the private stuff, and remote access behaves like you’re sitting at home. Took a few moving parts to get there, but bloody hell it’s nice when it all clicks 🙂
