Tailscale ACLs and Split DNS for Local Services

Tailscale ACLs and Split DNS for Local Services

Tailscale split DNS and ACLs matter when local services need to stay reachable without being wide open. Split DNS can return local IPs for a name inside the tailnet while leaving normal external resolution alone outside it.

In Tailscale, split DNS is set in the admin console. Point it at a local nameserver for the domain you want handled inside your network. If you use something like dnsmasq, that resolver can answer for the local names you care about instead of sending every query out to public DNS.

ACLs sit alongside that. They decide which users and devices can reach which services. If you get the ACLs wrong, the DNS setup still works, but you have just made a service easier to find rather than safer to use.

An ACL example might look like this:

{
  "ACLs": [
    {
      "action": "accept",
      "users": ["user@example.com"],
      "ports": ["service:80", "service:443"]
    },
    {
      "action": "deny",
      "users": ["*"],
      "ports": ["service:80", "service:443"]
    }
  ]
}

That allows one user to reach the service and blocks everyone else. It is only useful if the rules match the way you actually use the service, so they need checking whenever access changes.

dnsmasq can make local DNS handling simpler. It is a small DNS forwarder and DHCP server that can answer for local names. In a Tailscale setup, the basic shape is straightforward:

  1. Install dnsmasq on a device in the tailnet.
  2. Configure the local names and IP addresses it should return.
  3. Point Tailscale DNS at that dnsmasq instance.

That keeps internal queries local and avoids pushing everything through an external resolver.

wg-quick is useful for managing WireGuard configuration files. It keeps tunnel setup in one place and makes it easy to bring an interface up when you need it.

  1. Create a configuration file in /etc/wireguard/, usually wg0.conf.
  2. Set the interface details, including private keys and allowed IPs.

An example looks like this:

[Interface]
PrivateKey = <your_private_key>
Address = 10.0.0.1/24

[Peer]
PublicKey = <peer_public_key>
AllowedIPs = 10.0.0.2/32

Bring it up with:

wg-quick up wg0

Firewall allowlists are the other half of it. ACLs control access in Tailscale, but the firewall still needs to block what should not be exposed.

A basic iptables example might look like this:

iptables -A INPUT -p tcp --dport 80 -s <trusted_ip_range> -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j DROP

Review the rules regularly. Old allowlists have a habit of staying in place long after the reason for them has gone.

The common failure modes are the dull ones: ACLs too broad, firewall rules too open, and DNS pointing at the wrong place. Keep the access list tight, check the resolver path, and do not assume the service is hidden just because it sits behind Tailscale.

A sane setup comes down to a few checks: know which devices need access, point split DNS at the right resolver, write ACLs that match those devices, and keep the firewall rules strict. If any one of those is vague, the rest starts to wobble.

Related posts

Vector | vdev-v0.3.3

Vector vdev v0 3 3: patch release with crash, leak and parsing fixes, connector and tooling improvements, upgrade notes on prechecks, rolling updates, compat

Loki | v3.7.2

Loki v3 7 2: security and CVE fixes, updated S3 client to aws sdk v1 97 3, ruler panic fix for unset validation scheme, S3 Object Lock sends SHA256 checksum

Loki | v3.7.2

Loki v3 7 2: Patch release with CVE fixes, AWS S3 SDK update, ruler panic fix, S3 Object Lock SHA256 checksum support