Tailscale Stuck on a DERP Relay? Give Each Device Its Own Port

My office-to-NAS Tailscale path sat on a DERP relay for most of a month. The cause: five devices sharing the default port behind one router.

On this page
  1. A month of relay alerts
  2. What DERP is, and why it’s a fallback
  3. The pattern: one winner, four losers
  4. The fix: five devices, five ports
  5. How I know it worked
  6. The rule I keep now

If several Tailscale devices sit behind the same router and only one of them gets a direct connection, give each device its own tailscaled UDP port instead of the default 41641. My office router kept port 41641 for exactly one machine. The other four fell back to a relay, including the node that carries all my traffic home, whose path to my NAS was relayed for most of a month. Four new port numbers fixed it in an afternoon.


A month of relay alerts

My lab lives in two places. The Proxmox cluster (four nodes, pvelab01 to pvelab04) and my laptop sit at my office, behind a router that belongs to the building’s IT team, not me. My NAS sits at home. Tailscale joins the two sites, and one node, pvelab04, acts as the subnet router that every NAS-bound flow goes through: file shares, the nightly offsite backup sync, restores.

I’d built a small monitor for exactly this situation. A script on pvelab04 asks Tailscale every minute whether the path to the NAS is direct and publishes the answer to Prometheus as a 1 or a 0. A Grafana alert fires if it sits at 0 for 30 minutes. And I’d been getting its emails.

When I finally sat down with the history, this is what Prometheus had recorded for that path, one sample per day across the 29 days it held:

Share of each day the office-to-NAS path was direct Days
0% (relayed all day) 27
About 21% 1
About 61% 1

So for almost the entire retention window, every byte between my cluster and my NAS went through a relay. Nothing was broken, which is why I’d let the emails pile up. That’s the trap with relays.


What DERP is, and why it’s a fallback

Tailscale’s preferred path is a direct, encrypted WireGuard tunnel between two devices. To build one across two routers, each side has to learn the public address and port the other can be reached on, then both send packets at the same moment so each router lets the other’s packets in. Tailscale’s own write-up, How NAT traversal works, walks through it, and it’s honest about the limit: if one side can’t predict which outside port to send to, the direct path can’t be built.

When that happens, Tailscale uses a DERP relay server. The docs describe it as a last resort for when other connection types aren’t possible. Your traffic is still encrypted end to end; the relay just forwards packets it can’t read. It works, which is the problem. Everything keeps running, only slower and with more latency, and nothing tells you unless you go looking.

Same router, same default port, one winnerBEFORE: EVERYONE ON 41641AFTER: ONE PORT EACHpvelab01:41641directpvelab01:41641directpvelab02:41641via DERPpvelab02:41642directpvelab03:41641via DERPpvelab03:41643directpvelab04:41641via DERPpvelab04:41644directlaptop:41641via DERPlaptop:41645directThe router keeps 41641 for one device. The rest get other ports.Each port stays as it is, so every path goes direct.peira.dev

The pattern: one winner, four losers

The clue was in who was direct. When I compared the office devices, pvelab01 was reaching the outside world from the office’s public address on port 41641, and its path to the NAS was direct. Every other office device was showing up on some other, seemingly random port, and every one of them was relayed. The home side was fine: the NAS was reachable on its usual 41641 the whole time.

Tailscale listens on UDP port 41641 by default, and every one of my office devices was using that default. When a packet leaves through the office router, the router rewrites its source to the office’s public address and picks an outside port. In my office, the router kept a device’s own port number on the outside, but it can only hand any one outside port to one device at a time. pvelab01 had claimed 41641 first. Everyone else asking for 41641 got something else, and from home those other ports never worked for a direct connection.

I can’t see inside a router I don’t manage, so I’m describing what it did, not how it’s built. But the behaviour was consistent, and it pointed straight at the fix: stop making five devices fight over one port.

Why not just forward a port?

Tailscale’s firewall page notes that allowing inbound traffic to the UDP port one device listens on can help it go direct. That needs control of a router. I don’t have it at the office, and at home I couldn’t set up a forward for the NAS because it doesn’t show up in my ISP router’s device list. Unique ports needed nothing from either router.


The fix: five devices, five ports

First: make these values your own

The node names (pvelab01 to pvelab04), the port numbers 41642 to 41645 and the peer name nas are mine. Pick any free UDP ports you like, one per device behind the same router, and use your own device names. 203.0.113.10 below stands in for an office’s public address and 198.51.100.20 for a home one. If a value looks specific to one machine, it’s a placeholder to change, not a literal to copy.

I left pvelab01 on 41641, since it already held it, and gave everyone else the next number up:

Device Port
pvelab01 41641 (unchanged)
pvelab02 41642
pvelab03 41643
pvelab04 (the subnet router) 41644
laptop 41645

On a Debian or Ubuntu machine with Tailscale’s package installed, the port lives in /etc/default/tailscaled. The systemd service reads that file and starts tailscaled with --port=$PORT, which you can confirm on your own machine with systemctl cat tailscaled. Tailscale’s firewall page mentions the FLAGS variable in the same file as another place to set it; on my install, changing PORT is the direct route.

/etc/default/tailscaled on pvelab02 (only the PORT line changes)

PORT="41642"
FLAGS=""
Apply it and confirm the path

systemctl restart tailscaled
tailscale ping nas
Don't restart the tunnel you're standing on

Restarting tailscaled drops that device’s tunnel for a few seconds. If your SSH session reaches the device through that same tunnel, or through a subnet route it provides, you’ll cut yourself off mid-change. My laptop reaches the office LAN through pvelab04’s subnet route, so I restarted every node from pvelab01 over the office LAN instead of from the laptop.

For my laptop I wrote a small script that backs up the file, sets the port, restarts the service and pings the NAS. The first run printed unexpected state: NoState: the ping had raced tailscaled coming back up. The script now waits until tailscale status reports the backend as running before it pings. If you script this, add the same wait.


How I know it worked

tailscale ping is the quickest check. On a direct path it answers with an address and port; on a relayed one it answers via DERP(...) with the relay’s city code. Here’s pvelab01 today, with the addresses swapped for documentation ones:

Direct: an address and a port, not DERP

$ tailscale ping nas
pong from nas (100.64.0.0) via 198.51.100.20:41641 in 48ms

That 100.64.0.0 is a stand-in for the NAS’s tailnet address, which will be somewhere in that range on your tailnet. From the NAS’s side, every node now shows up from the office’s public address on its own port (203.0.113.10:41642, :41643 and so on), all direct, and the laptop is direct too.

The metric agreed. The office-to-NAS path flipped from 0 to 1 at about 10:00 that morning and read 1 in every 15-minute sample through the rest of the day. The relay alert has gone quiet.

What I haven’t done yet is compare the nightly backup and sync durations before and after, so I’m not going to claim a speed-up here. The point of this fix was getting off a detour I didn’t choose. Whether that detour was costing me minutes or hours each night is a separate measurement, and it’s one I’d rather make than guess.


The rule I keep now

Every Tailscale device behind the same router gets its own port. It’s written into my lab notes next to the port table above, so the next device I add at the office gets 41646 instead of quietly taking the default.

And if the relay alert ever fires again, the first thing I’ll check is whether two devices behind one router have ended up sharing a port. A direct-or-relayed metric with an alert on it is what turned this from “everything works, just a bit slow” into something I could actually see. If you run Tailscale between sites, it’s worth having.


Related posts:

Comments

Comments are powered by GitHub Discussions — sign in with a GitHub account to join the conversation.