Tailscale & OPNsense Configuration¶
This covers running Tailscale on OPNsense: the firewall as the tailnet's DNS server (ad-blocking everywhere), an exit node, and subnet routes, plus the firewall rules that make it work. The client side (installing on Debian servers, ACLs, key expiry) is on the Tailscale page.
How it's set up here¶
Tailscale runs in two places, for different jobs:
| Where | Job |
|---|---|
OPNsense (os-tailscale plugin) |
The tailnet's DNS server, so phones get AdGuard Home filtering on mobile data; an exit node; a subnet router for the VLANs remote devices actually need |
| The Debian servers (Tailscale client) | Direct, independent admin access (SSH) when the Cloudflare tunnel or the reverse proxy is the thing that's broken |
On the servers, Tailscale's DNS is off (tailscale set --accept-dns=false). Why: with it on, tailscaled rewrites
/etc/resolv.conf at runtime to point at MagicDNS, and the host then depends on the tailnet path for every lookup.
When that path broke (see the firewall rule pitfall below), the hosts couldn't resolve anything while their containers
still worked, which is a confusing failure. The servers use the router's LAN resolver directly and nothing on them
needs *.ts.net names.
On the tailnet side, remote devices may reach only the VLANs they need (servers and trusted clients). The management and IoT networks and the firewall's own GUI/SSH stay blocked from the tailnet.
1. Install the plugin¶
- System → Firmware → Plugins, tick Show community plugins, install
os-tailscale. - VPN → Tailscale → Authentication: log in (an auth key or the login URL).
- Interfaces → Assignments: add
tailscale0as a new interface, enable it (no IP config), apply. It then gets its own tab under the firewall rules.
2. Advertise routes and the exit node (OPNsense)¶
- Navigate to VPN > Tailscale > Settings.
- Check Advertise Routes and enter the subnets remote devices should reach (e.g.
<SERVERS_SUBNET>). - Enable Advertise Exit Node if you want phones to send all traffic home.
- Click Save.
CLI Alternative
Without the plugin GUI:
Approve them (Tailscale Console)¶
- Open the Tailscale Admin Console.
- Select the OPNsense machine → Edit route settings.
- Toggle on the advertised subnet routes and Use as exit node, then Save.
3. Firewall rules on the Tailscale interface¶
Don't use Tailscale net as the source
The obvious rule, source = Tailscale net, doesn't work on OPNsense. The interface "net" for tailscale0
resolves to the firewall's own tailnet address as a /32, not the tailnet range. No rule ever matches a
peer, and pf silently drops everything: DNS, subnet routes, the exit node. Symptoms look like a broken
Tailscale link even though tailscale ping works.
Create an alias instead: Firewall → Aliases, tailnet_v4 = 100.64.0.0/10 (Tailscale's CGNAT range), and
use it as the source of every rule below. More on this trap:
Tailscale peers are blocked by the firewall although the rules say "pass".
Rules on the Tailscale interface, in this order (first match wins):
| # | Action | Source | Destination | Port | Why |
|---|---|---|---|---|---|
| 1 | Pass | tailnet_v4 |
the firewall's tailnet IP | 53 TCP/UDP | DNS for remote devices |
| 2 | Block (log) | tailnet_v4 |
This Firewall (self) | any | no GUI/SSH on the firewall from the tailnet |
| 3 | Block (log) | tailnet_v4 |
MANAGEMENT net, IOT net | any | keep sensitive VLANs off the tailnet |
| 4 | Pass | tailnet_v4 |
the VLANs remote devices need (e.g. SERVERS) | any | subnet routes |
| 5 | Pass | tailnet_v4 |
! PrivateNetworks (inverted) |
any | exit node: internet only |
Rule 2 matters because of rule 5: the built-in private-networks alias doesn't include 100.64.0.0/10, so without an
explicit block the "internet only" rule would also open the firewall's own tailnet address.
4. Source NAT for the exit node¶
Exit-node traffic leaves OPNsense with a 100.x source address. The FreeBSD Tailscale client doesn't masquerade it
the way the Linux client does, so it needs an outbound NAT rule:
- Firewall → NAT → Source NAT (Outbound): set the mode to Hybrid (automatic rules stay, yours are added).
- Add a rule: Interface WAN, Source
tailnet_v4, Destination any, Translation Interface address. - Save, apply, then test from a phone on mobile data with the exit node on (check your public IP is the home one).
While you're on that page: the Tailscale docs also recommend a Static Port outbound rule for UDP from the firewall so peers can make direct connections instead of relaying (see the sources).
5. DNS for the tailnet (Tailscale Console)¶
Make the firewall the tailnet's resolver, so every device on the tailnet gets AdGuard Home filtering and your local names.
- In OPNsense, Interfaces → Overview: copy the Tailscale IP (
100.x.y.z). - Tailscale Admin Console → DNS → Nameservers → Add nameserver → Custom: paste it.
- Choose one:
- Global nameserver (with Override DNS servers on): all queries from tailnet devices go to OPNsense. Ad-blocking works everywhere. This is what I use for phones.
- Restrict to domain =
<YOUR_LOCAL_DOMAIN>: split DNS, only local names go to OPNsense.
AdGuard Home and Unbound must accept queries from 100.64.0.0/10: AdGuard listening on all interfaces (or
tailscale0), and Unbound's access list allowing the range (check Services → Unbound DNS → Access Lists
if queries are refused).
Local names over the tailnet
<SERVICE>.<YOUR_LOCAL_DOMAIN> resolves to NPM's LAN IP through the AdGuard rewrite
(AdGuard Home → DNS rewrites), so a remote device also needs a
subnet route to that IP (rule 4) and has to trust your private CA.
5A. Unbound only (no AdGuard)¶
If Unbound answers DNS directly, add the local names under Services > Unbound DNS > Overrides (one host override per name, pointing at NPM's IP). Unbound overrides have no wildcard field, which is one reason the AdGuard rewrite is easier.
Interfaces & Rebinds¶
If it still isn't working, check these common configuration errors:
- Interface binding: AdGuard Home must be listening on the Tailscale interface (or all interfaces).
- Bootstrap DNS: in AdGuard Home (Settings > DNS Settings), bootstrap servers resolve upstream names only;
127.0.0.1plus a public resolver is fine. - DNS rebind check: System > Settings > Administration → DNS Rebind Check. If enabled, the GUI may refuse to load via a name that resolves to a private IP; add your local domain to the alternate hostnames instead of turning the check off.
- Boot order: the plugin can start after the interfaces are configured, so
tailscale0doesn't exist yet when rules and DNS listeners load. After a reboot, check that tailnet DNS answers; restarting AdGuard Home or Unbound fixes a missed bind.
Verification¶
- Disconnect your phone from Wi-Fi (mobile data).
- Enable Tailscale (and the exit node, if used).
- DNS: a known ad domain is blocked, and
<SERVICE>.<YOUR_LOCAL_DOMAIN>resolves. - A service on an allowed VLAN loads by IP and by name.
- A management IP and the firewall GUI do not load (rules 2 and 3).
- With the exit node on, your public IP is your home one.
- From a server:
dig @<OPNSENSE_TAILNET_IP> example.comanswers.