What you get
A vendor that only accepts connections from an approved list of source addresses sees exactly one address from your whole organization, permanently, regardless of who is connecting or which coffee shop, hotel, or home network they are connecting from. You add one address to the vendor’s allowlist and never touch it again.
Everything else on the device is untouched. Video calls, package downloads, and the rest of the internet still leave from wherever the device actually is, at full speed, over the shortest path. Only the domains you name take the detour. That is the difference between this and every full tunnel VPN anyone has ever resented: the scope of the redirect is a list of domain names you wrote down, not the entire default route.
This is what Tailscale calls an app connector, and the vendor facing version of the pitch is direct. The addresses of the node running the app connector can be added to the allowlist, and all nodes on the tailnet will use that address for their traffic egress to that application.
How it works
Start with what an app connector is not, because two neighboring features look similar and behave completely differently.
An exit node takes everything. It offers to be the path for all internet traffic for a device, so the selection is per device and total. A subnet router advertises a fixed range of addresses, so it routes by CIDR, and it only works when the destination has stable addresses you can write down in advance. An app connector sits between the two. The documentation describes it as being like a subnet router with the added benefit of routing your users and devices to applications by domain names instead of IP addresses, which gives more reliable connectivity. You configure example.com, not 198.51.100.0/24, and the connector figures out the addresses at runtime.
That runtime discovery is the part people get wrong, so slow down here.
The connector does not resolve your domain list on a schedule and cache the answers. Discovery is driven by actual client demand. App connectors use the PeerAPI to perform DNS discovery through DoH, DNS over HTTPS. When a device on your tailnet wants a configured domain, the resolution goes to the connector, the connector resolves it, and the addresses it learns are automatically advertised as routes to the tailnet. Those routes are what pull subsequent traffic through the connector.
Three consequences fall directly out of that design, and every one of them shows up in the field.
First, there is a learning period. Without preconfigured routes, a connector that has just been stood up knows the domains but not yet the addresses. It learns them by proxying real DNS requests from real users, and routes appear as demand arrives. A connector nobody has used yet is not routing anything, and that is not a fault.
Second, the client must be able to see the connector as a peer at all. Client devices must have access to the app connector tag for it to appear as a peer, and minimal access such as ICMP is sufficient for discovery. Full application access is not required for the discovery step. If your policy gives nobody any access to the connector tag, discovery silently does not happen, and the symptom is that nothing routes rather than that something errors.
Third, because the routes come from DNS answers, anything that changes the DNS answer changes your routing. Geographic load balancing, CDN fronting, and CNAME chains all land on this mechanism.
Build it
-
Pick the host, and pick it for its address. You need a Linux device with a public address and IP forwarding enabled. The address is the entire product here, so it must be one you control and can keep: a static or reserved address on a cloud instance, not whatever ephemeral address the provider hands out on reboot. If that address changes, the vendor’s allowlist stops matching and your access breaks in a way that looks like a vendor problem.
-
Create the tag and its owners in the tailnet policy file, so the connector has a machine identity separate from any person.
"tagOwners": { "tag:vendor-connector": ["group:network-admins"], }, -
Auto approve the routes it will discover. This step is what makes discovery actually work unattended. Discovered addresses arrive as route advertisements, and unapproved routes do not carry traffic. The setup documentation approves the default routes for the connector tag.
"autoApprovers": { "routes": { "0.0.0.0/0": ["tag:vendor-connector"], "::/0": ["tag:vendor-connector"], }, }, -
Grant clients at least DNS access to the connector tag, because discovery happens through the connector and a client that cannot reach it cannot trigger it.
"grants": [ { "src": ["autogroup:member"], "dst": ["tag:vendor-connector"], "ip": ["tcp:53", "udp:53"], }, ], -
Define the connector itself in
nodeAttrs, naming the tag that serves it and the domains it serves."nodeAttrs": [ { "target": ["*"], "app": { "tailscale.com/app-connectors": [ { "name": "vendor-portal", "connectors": ["tag:vendor-connector"], "domains": ["example.com", "*.example.com"], }, ], }, }, ],Wildcards work for subdomains but never for top level domains.
*.example.comand*.example.co.ukare valid,*.comand*.co.ukare not. Note also that the wildcard does not include the parent, which is why both entries appear above. -
Advertise the connector on the node.
tailscale up --advertise-connector --advertise-tags=tag:vendor-connectorThe flag offers the device as an app connector for domain specific internet traffic for the tailnet. It is also available on
tailscale set, which changes only the settings you name rather than requiring the complete desired configuration, which is what you want when reconfiguring a connector that is already carrying traffic. -
Register the application in the admin console. On the Apps page, give the application a name, choose a preset or supply your own domains, and select the connector tags that serve it. Preset applications exist for common platforms, and for those Tailscale updates and manages the routes.
-
Give the vendor the connector’s public address and confirm it is in place on their side before you tell anyone the path is live.
-
Build the second connector before you need it. Multiple app connector devices for a single application are recommended for performance and reliability. Same tag, same policy, different host, ideally different failure domain.
Verify it
-
Ask the connector what it has learned. On the connector node,
tailscale appc-routesprints the current app connector route status: the domains from the configuration and how many routes have been learned for each.--allprints learned domains and routes plus extra routes configured by policy,--mapprints the map of learned domains, and--nprints the total number of routes the node advertises. If a domain shows zero routes, nobody has resolved it through this connector yet. -
Check that the routes are approved, in the admin console or with
tailscale status --jsonon the connector. Advertised is not the same as approved, and an unapproved route carries nothing. -
Prove the egress address from a client, not from the connector. Fetch a service that reports the source address it sees, from a device that is not the connector, and confirm the answer is the connector’s public address. Then fetch the same service over a domain that is not in your configuration and confirm the answer is the device’s own local address. Both halves matter. The second one is what proves you built an app connector and not an accidental full tunnel.
-
Follow the CNAME chain if the domain has one. Use
digto check the CNAME configuration, and expect the resolved target rather than the alias to show up in the route advertisements. A configuration that names only the alias can look correct and route nothing. -
Know what failure looks like. If an app connector becomes unavailable while in use and no other connector is available, resolution to the domain begins to fail until the connector is back online. So the failure presents as name resolution breaking for one specific vendor while the rest of the internet is fine. That is a very recognizable signature once you have seen it, and a very confusing one if you have not.
Gotchas
-
The address is the deliverable, so protect it like one. A host whose public address is not reserved will eventually reboot into a new address, and the outage will look like the vendor blocking you rather than like your own infrastructure moving.
-
Discovery needs peer visibility. If policy gives clients no access at all to the connector tag, the connector never appears as a peer and discovery never fires. Minimal access such as ICMP is enough for that step, so this is cheap to satisfy and easy to forget.
-
Cold start looks like breakage. Fresh connector, correct configuration, zero routes. Nothing is wrong. Preconfigure routes if you cannot tolerate the learning window, particularly when an upstream firewall has to be programmed before traffic flows.
-
Do not point a connector at a CDN. Publishing CDN routes can force unrelated traffic through the connector, because CDNs frequently share addresses across many unrelated properties. You will pay for that in bandwidth and in confusing incidents. Serve that content outside the connector.
-
Wildcards do not include the parent. To cover a domain and its subdomains you list both. Half your users hitting the bare domain and being routed differently from the half hitting the subdomain is a genuinely miserable diagnosis.
-
Regional routing is not the same as failover. Failover is the default behavior for overlapping connectors and is available on all plans. Regional routing, which sends devices to the nearest connector and spreads load within a region, is a Premium and Enterprise feature. Design for the one you actually have.
-
Within a region, load balancing is sticky and pseudorandom. When regional routing spreads load across overlapping connectors in a region, clients tend to stay on one connector unless it becomes unavailable. Do not expect even distribution when you look at a single client.
Where to take it next
- Place a connector near each concentration of people, then let regional routing pick the closest one, so the address stability you gained does not cost you a transatlantic round trip on every request.
- Preconfigure route ranges for the vendors whose address space is published and stable, and reserve pure discovery for the ones that move. Smaller route tables, faster cold starts, and an upstream firewall you can program in advance.
- Bring the connector definition into the policy as code pipeline, so adding a domain to a connector is a reviewed pull request with an assertion attached rather than a live edit to a text box.
- Extend the same pattern to internal services with unstable addresses, since the argument for routing by domain name rather than by CIDR is not limited to third party vendors.