Joining Tailscale with Surge Mac

A minimal config you can use as-is

As of Surge Mac 6.7.0, Tailscale is built in as a proxy policy. You no longer run a separate Tailscale client — Surge itself joins the tailnet as a node (and coexists with the official Tailscale client too — they don't interfere, and no extra config is needed), then routes matched traffic to machines inside it. Reaching internal devices, the NAS back home, a dev box — all go through this policy. Here's a minimal config you can copy verbatim.

Requires Surge Mac 6.7.0+. Note that 6.7.0 (11310) currently ships on the beta channel — switch Surge's update channel to beta to receive it. iOS has supported it since Surge iOS 5.102.0 (3730), though there the connection is lazy — it only dials the tailnet on your first request to a tailnet domain (and stays connected afterward), so until then the node shows as not connected (not green) in the Tailscale console, turning green after a request; that's expected. It's still an early beta — only TCP-based control / DERP paths are supported, UDP direct paths are not.

Minimal config

[General]
dns-server = 223.5.5.5, 119.29.29.29
 
[Proxy]
ts-home = tailscale, section-name=Home
 
[Rule]
DOMAIN-SUFFIX,tailXXXXXX.ts.net,ts-home # the full-domain suffix
IP-CIDR,100.64.0.0/10,ts-home,no-resolve
IP-CIDR6,fd7a:115c:a1e0::/48,ts-home,no-resolve
IP-CIDR,192.168.50.0/24,ts-home,no-resolve # subnet, if you have one
FINAL,DIRECT
 
[Tailscale Home]
auth-key = tskey-auth-....
hostname = surge-mac
dns-server = 8.8.8.8, 1.1.1.1
prefer-ipv6 = false

Walking through it

ts-home under [Proxy] is a Tailscale policy:

  • section-name=Home points at the [Tailscale Home] section below — the names have to match.

[Rule] decides what enters the tailnet:

  • DOMAIN-SUFFIX,tailXXXXXX.ts.net matches MagicDNS names. Surge can resolve MagicDNS names, but you still need an explicit rule to route them into the Tailscale policy.
  • IP-CIDR,100.64.0.0/10 is the 100.x range (CGNAT) Tailscale assigns to nodes — fixed for everyone.
  • IP-CIDR6,fd7a:115c:a1e0::/48 is Tailscale's IPv6 range, identical across all tailnets — copy it as-is.
  • IP-CIDR,192.168.50.0/24 is an example subnet route: once a machine in the tailnet advertises your home LAN with tailscale set --advertise-routes=192.168.50.0/24 and you approve that route on the console's Machines page, adding a rule for that range lets you reach devices that don't run Tailscale at all. Drop this line if you have no subnet.
  • no-resolve skips a pointless DNS lookup for pure-IP rules.
  • FINAL,DIRECT is the catch-all; everything else goes direct.

Don't agonize over whether more rules are needed: 100.64.0.0/10 and fd7a:115c:a1e0::/48 are Tailscale's two supersets. Node addresses, the MagicDNS resolver 100.100.100.100, the IPv6 service address fd7a:115c:a1e0::53, the 4via6 tunnel range — they all fall inside these two, already covered. The only thing outside them that you'd add an IP-CIDR for is the subnet route above.

[Tailscale Home] is the node itself:

  • auth-key registers this Surge instance into your tailnet. It's only used on the first connection; afterward Surge caches the node state. Changing this value clears that state and re-registers the device.
  • hostname is the device name shown in the tailnet; omit it and one is generated.
  • dns-server / prefer-ipv6 set the DNS this policy uses and whether to prefer IPv6.

Five easy things to miss

  1. Swap the MagicDNS suffix for your own. tailXXXXXX.ts.net is just the example tailnet name; every account's is different. In any device's Tailscale addresses (in the console or the Mac app), the second half of the MagicDNS name hostname.tailXXXXXX.ts.net is your suffix. Replace tailXXXXXX.ts.net in the rule with it. 100.64.0.0/10 and fd7a:115c:a1e0::/48 are fixed ranges — leave them alone.

    A device's Tailscale addresses — the second half of the MagicDNS name, tailXXXXXX.ts.net, is the suffix

  2. Generate your own auth-key. In the Tailscale console, Settings → Keys → Generate auth key (Reusable is handy), and drop the tskey-auth-... into auth-key.

    Tailscale console Settings → Keys, click Generate auth key

  3. Add underlying-proxy only when you need it. It usually connects fine without one, so it isn't required. The first connection may need a cold start, so give it a moment; only if it really won't connect, add underlying-proxy to ts-home and point it at an outbound policy:

    ts-home = tailscale, section-name=Home, underlying-proxy=proxy
    proxy = ss, your-server.com, 8388, encrypt-method=chacha20-ietf-poly1305, password=YOUR_PASSWORD

    Replace proxy with your own node (Surge's comma-separated fields, not a raw ss:// URL; any protocol works).

    Networks that block Tailscale's control plane / DERP (e.g. mainland China) are the exception: the embedded node's cold-start handshake can't reach Tailscale's servers, so underlying-proxy is usually required — the tell-tale sign is traffic already matching Policy: ts-home yet timing out. Point it at a policy with reliable egress and reload.

  4. prefer-ipv6 depends on your service. Set true only if the target service listens on IPv6; otherwise keep it false and stay on v4. With true, Surge resolves MagicDNS names to fd7a:..., and many self-hosted services only listen on v4 — so the connection fails.

  5. Don't leave tailnet ranges in skip-proxy. If [General]'s skip-proxy contains *.ts.net or 100.64.0.0/10 (some configs add these so the official client owns the tailnet), that traffic bypasses Surge entirely and the ts-home rules above never fire — you'll see requests go direct, matching no policy at all. When switching to Surge's built-in Tailscale, remove both from skip-proxy.

Let Claude set it up

Rather than editing line by line, hand the prompt below to Claude Code (or any other LLM agent that can run commands on your Mac) and let it wire things up following the config above:

You're helping me configure Surge Mac. The goal: add Tailscale to my Surge config so I can reach devices inside my tailnet by rule. Treat this article as the source of truth for the config: https://www.jizhiovo.com/posts/surge-mac-tailscale
 
Always follow these:
- Never touch the original profile I pick until I explicitly approve the merge — make all edits on a backup copy.
- Don't read the bodies of sections like [General] or [Proxy] — they can hold auth-keys, passwords, and other secrets. Locating a section needs only a grep of its header line; put new content on the line below the header.
- Re-check the conf syntax after every edit; only have me reload once it passes.
- Wherever it says "pause for confirmation," wait for my reply before continuing — don't run ahead.
- If you're missing something (a value to fill in, an address to hit), ask me — don't guess.
 
Steps:
1. Preflight the version: confirm this Surge Mac supports Tailscale (needs 6.7.0+). If not, tell me to switch the update channel to beta and install the latest build, then stop.
2. Install the skill Surge ships: symlink the skill in /Applications/Surge.app/Contents/Resources/Skills/ into my skills directory so it updates along with Surge. If that directory doesn't exist, the version most likely doesn't support this — go back to step 1.
3. List the profiles you can find (the .conf files in the Surge profile directory) and have me pick which one to work on — the CLI can't tell which profile is currently active, so don't guess. Pause for confirmation: wait until I've chosen.
4. Once I confirm, copy the conf to a backup and only edit the backup from here on. Following the article: use grep to find the [Proxy] and [Rule] header lines, insert the ts-home policy on the line below [Proxy] and the matching routing rules on the line below [Rule] (put them first, so they match before other rules), then append the [Tailscale ...] section at the end of the file — without reading the existing bodies of those sections. Also grep [General]'s skip-proxy line and, if it contains *.ts.net or 100.64.0.0/10, remove those from the backup — otherwise tailnet traffic bypasses Surge and the rules never fire. Note: if the [Rule] body is just an #!include of another file (the rules live in the included file), inserting below the header leaves a rule block that doesn't end in FINAL and the syntax check fails — in that case add the rules at the top of the included file's rule list, and copy that file too, repointing the include at the copy. Then check the syntax.
5. If the syntax is clean, open the backup in my default editor. I need to fill in two things: the auth-key and the MagicDNS (full-domain) suffix. The suffix you can read via the tailscale CLI and pre-fill if I have Tailscale installed locally; the auth-key can't be pulled locally — I have to generate it in the Tailscale console (Settings → Keys), so just remind me to paste it in.
6. Have me switch Surge to this backup and reload. Then verify by reaching a service on a remote tailnet device; if you don't have an address, ask me for a URL.
7. If Tailscale is installed locally, also confirm this Surge node's hostname registered into the tailnet and shows as connected.
8. Verify with a real request. The first cold start is slow, so try at least twice. If it still won't connect, ask whether I have a usable proxy to forward through, have me give its policy name, add underlying-proxy to the matching Tailscale policy, then re-verify.
9. Once it works, pause for confirmation: ask whether to merge the changes back into the original profile — if yes, mv the backup over the original conf; if no, keep both.

Limitations

  • Only TCP-based control and DERP paths are supported — no UDP direct path.
  • It only acts as a client reaching into the tailnet. The policy supports just auth-key / hostname / control-url / mtu / dns-server / prefer-ipv6 — no advertise-routes / exit-node and the like — so it can't be an exit node or subnet router, and other devices can't route out through this Surge instance or reach the LAN behind it.
  • It's still beta. It's enough as a lightweight, rule-driven entry into your internal network; for the full Tailscale experience, install the official client.