Configure Split DNS with Tailscale and Local DNS

Tailscale is a fantastic tool for securely accessing all your systems and applications remotely. However, it does come with some trade-offs — especially when it comes to DNS. By default, Tailscale takes over your system’s DNS settings. While non-Tailscale domains are generally forwarded to your local resolver, that behavior depends on Tailscale functioning correctly.

Recently, I ran into an issue where one of my nodes had an expired key and couldn’t connect to the tailnet anymore. As a result, all DNS queries on that system failed, even for unrelated domains.

While this might have been a rare edge case, it made me wonder: Can I configure my system to only use Tailscale’s DNS (100.100.100.100) for .ts.net domains, while keeping my normal DNS resolver for everything else?

The answer is yes — by using systemd-resolved, you can set up true split DNS and direct specific domains like *.ts.net to Tailscale’s DNS while keeping everything else on your local DNS.

Install systemd-resolved

First, ensure that systemd-resolved is installed and enabled on your system:

BASH
1sudo apt update
2sudo apt install systemd-resolved
3sudo systemctl enable systemd-resolved
4sudo systemctl start systemd-resolved
Click to expand and view more

Also, make sure /etc/resolv.conf is pointing to the systemd stub resolver:

BASH
1sudo ln -sf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
Click to expand and view more

Create Tailscale DNS Split Configuration

To configure systemd-resolved to use Tailscale’s DNS server only for .ts.net domains, we’ll create a network configuration file that is applied at boot.

Open the file:

BASH
1sudo nano /etc/systemd/network/99-tailscale.network
Click to expand and view more

Paste the following:

BASH
1[Match]
2Name=tailscale0
3
4[Network]
5DNS=100.100.100.100
6Domains=~ts.net
Click to expand and view more

This tells systemd-resolved to use 100.100.100.100 (Tailscale’s MagicDNS) only for .ts.net queries on the tailscale0 interface.

Apply the changes by restarting the systemd network daemon:

BASH
1sudo systemctl restart systemd-networkd
Click to expand and view more

After setting up split DNS with systemd-resolved, you can use the resolvectl status command to confirm that your system is routing DNS queries correctly.

In the example below, you can see that:

output
 1Global
 2       Protocols: +LLMNR +mDNS -DNSOverTLS DNSSEC=no/unsupported
 3resolv.conf mode: uplink
 4
 5Link 2 (eth0)
 6    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
 7         Protocols: +DefaultRoute +LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
 8Current DNS Server: 192.168.1.1
 9       DNS Servers: 192.168.1.1
10        DNS Domain: home
11
12Link 3 (tailscale0)
13    Current Scopes: DNS
14         Protocols: -DefaultRoute -LLMNR -mDNS -DNSOverTLS DNSSEC=no/unsupported
15Current DNS Server: 100.100.100.100
16       DNS Servers: 100.100.100.100
17        DNS Domain: tail43c135.ts.net ~0.e.1.a.c.5.1.1.a.7.d.f.ip6.arpa ~100.100.in-addr.arpa ~101.100.in-addr.arpa
18                    ~102.100.in-addr.arpa ~103.100.in-addr.arpa ~104.100.in-addr.arpa ~105.100.in-addr.arpa ~106.100.in-addr.arpa
19                    ~107.100.in-addr.arpa ~108.100.in-addr.arpa ~109.100.in-addr.arpa ~110.100.in-addr.arpa ~111.100.in-addr.arpa
20                    ~112.100.in-addr.arpa ~113.100.in-addr.arpa ~114.100.in-addr.arpa ~115.100.in-addr.arpa ~116.100.in-addr.arpa
21                    ~117.100.in-addr.arpa ~118.100.in-addr.arpa ~119.100.in-addr.arpa ~120.100.in-addr.arpa ~121.100.in-addr.arpa
22                    ~122.100.in-addr.arpa ~123.100.in-addr.arpa ~124.100.in-addr.arpa ~125.100.in-addr.arpa ~126.100.in-addr.arpa
23                    ~127.100.in-addr.arpa ~64.100.in-addr.arpa ~65.100.in-addr.arpa ~66.100.in-addr.arpa ~67.100.in-addr.arpa
24                    ~68.100.in-addr.arpa ~69.100.in-addr.arpa ~70.100.in-addr.arpa ~71.100.in-addr.arpa ~72.100.in-addr.arpa
25                    ~73.100.in-addr.arpa ~74.100.in-addr.arpa ~75.100.in-addr.arpa ~76.100.in-addr.arpa ~77.100.in-addr.arpa
26                    ~78.100.in-addr.arpa ~79.100.in-addr.arpa ~80.100.in-addr.arpa ~81.100.in-addr.arpa ~82.100.in-addr.arpa
27                    ~83.100.in-addr.arpa ~84.100.in-addr.arpa ~85.100.in-addr.arpa ~86.100.in-addr.arpa ~87.100.in-addr.arpa
28                    ~88.100.in-addr.arpa ~89.100.in-addr.arpa ~90.100.in-addr.arpa ~91.100.in-addr.arpa ~92.100.in-addr.arpa
29                    ~93.100.in-addr.arpa ~94.100.in-addr.arpa ~95.100.in-addr.arpa ~96.100.in-addr.arpa ~97.100.in-addr.arpa
30                    ~98.100.in-addr.arpa ~99.100.in-addr.arpa ~ts.net
Click to expand and view more

The long list of ~100.in-addr.arpa entries is used for reverse DNS (PTR) lookups in Tailscale’s subnet.

Testing Resolution

Use resolvectl query to check which DNS server is used per domain:

Query a public domain (uses local DNS):

BASH
1resolvectl query google.com
Click to expand and view more
output
1google.com: 172.217.23.206                     -- link: eth0
2            2a00:1450:400e:802::200e           -- link: eth0
3
4-- Information acquired via protocol DNS in 8.3ms.
5-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
6-- Data from: network
Click to expand and view more

Query a Tailscale domain (uses Tailscale DNS):

BASH
1resolvectl query  beszel.tail43c135.ts.net
Click to expand and view more
output
1beszel.tail43c135.ts.net: 100.80.20.240        -- link: tailscale0
2
3-- Information acquired via protocol DNS in 4.2ms.
4-- Data is authenticated: no; Data was acquired via local or encrypted transport: no
5-- Data from: network
Click to expand and view more

Copyright Notice

Author: Sven van Ginkel

Link: https://svenvg.com/posts/configure-split-dns-with-tailscale-and-local-dns/

License: CC BY-NC-SA 4.0

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. Please attribute the source, use non-commercially, and maintain the same license.

Start searching

Enter keywords to search articles

↑↓
ESC
⌘K Shortcut