Tailscale ping works but ports timeout: a Docker-hosted client that answers ICMP from your living room and goes completely dead from your office. Same tailnet IP, same commands. Here is what actually differs between those two paths, how to tell which one you are on, and the fix that does not require rebuilding the container.
The symptom
The failure this article is about, in one line: Tailscale ping works but ports timeout. The tunnel answers ICMP and nothing else gets through. You are away from home and need something off your NAS, so you reach for the cheap test first:
$ ping 100.91.120.97
64 bytes from 100.91.120.97: icmp_seq=0 ttl=64 time=31.4 ms
It answers. The tunnel is up, the peer is online, and Tailscale is doing its job. So you go for the thing you actually wanted:
$ curl -v --max-time 8 http://100.91.120.97:6806/
* Trying 100.91.120.97:6806...
* Operation timed out after 8002 milliseconds with 0 bytes received
No Connection refused. No reset. Just nothing, until your client gives up.
That distinction is the whole article. They are different failures with different causes:
| What you see | What it means |
|---|---|
Connection refused |
Something answered the TCP handshake and told you nothing is listening on that port. The network is fine. Your service is not running, or is bound to the wrong interface. |
| A hang, then timeout | Your SYN left the machine and nothing ever came back. From your side there is no service at that address — not because it is down, but because the packet never reached it. |
If you get a timeout, stop debugging your service. The service is probably fine. Debug the path.
Why Tailscale ping works but ports timeout on one network and not another
This is the observation that turns a vague “Tailscale is broken” into a diagnosable problem.
On the same NAS, queried from two different machines, tailscale status reports two different connection types:
# from the NAS itself
100.73.149.5 mac-mini active; direct 192.168.1.1:41641, tx 47.6MB rx 54.1MB
100.80.43.115 rtx5080-pc active; direct 192.168.1.43:41641, tx 38KB rx 43KB
100.115.222.113 office-pc active; relay "dbi"; offline, last seen 5m ago
mac-miniandrtx5080-pcsit on the same LAN as the NAS. Their traffic goes direct — a peer-to-peer WireGuard tunnel between the two machines.office-pcis on a different network. Its tunnel does not punch through, so it falls back torelay "dbi"— a Tailscale DERP server in Dubai forwarding the packets.
Those two paths do not go through the same code inside tailscaled. On the direct path, packets arrive at the host’s network stack more or less the way any local traffic would. On the relay path, they arrive at tailscaled itself, which then has to hand them to the host’s services — and in userspace-networking mode, that last hop is not automatic.
That is why the same curl to the same tailnet IP works from the couch and times out from the office.
Confirm which path you are on (do this first)
Do not start changing configuration until you know which of the two paths is failing. Both commands are read-only.
# On your client: which path am I using to reach the peer?
tailscale status | grep <peer-hostname>
# On the NAS (container-hosted Tailscale):
docker exec tailscale tailscale status | grep -E 'direct|relay'
Read the middle column:
direct 192.168.x.x:<port>— peer-to-peer. If ports work here and fail elsewhere, the problem is path-specific, not service-specific.relay "<region>"— the packets are going through a DERP server. This is the path where auserspace-networkingtailscaled will drop your inbound TCP unless you have explicitly wired it up.
A quick sanity check on the relay’s cost, from the client:
# macOS
/Applications/Tailscale.app/Contents/MacOS/Tailscale netcheck
* UDP: true
* IPv4: yes, 80.227.94.141:51431
* PortMapping: UPnP
* Nearest DERP: Dubai
* DERP latency:
- dbi: 79.6ms (Dubai)
- hkg: 124.6ms (Hong Kong)
- lhr: 130.0ms (London)
...
A relayed path adds at least a round trip through the nearest DERP region. In Dubai that is 79.6 ms one-way to the relay, before the relay talks to your NAS. If your service feels sluggish rather than dead, this table is where the latency came from.
Root cause: userspace-networking answers ICMP but does not forward inbound TCP
Here is the actual command line of the Tailscale process running inside the NAS container:
$ docker exec tailscale ps aux | grep tailscaled
tailscaled --socket=/tmp/tailscaled.sock --statedir=/var/lib/tailscale
--tun=userspace-networking
--tun=userspace-networking is the whole problem. The container also reports:
Privileged=true NetMode=host
Note what that does not mean: privileged and host networking do not give you a TUN device by themselves. In userspace mode tailscaled implements the WireGuard stack itself, entirely in userspace, with no kernel TUN interface. It can:
- originate outbound connections from anywhere in the container,
- answer ICMP, which is why
pingworks, - relay inbound connections that have been explicitly configured (see the fix below).
It cannot, by itself, take an inbound TCP packet arriving over the tunnel and deliver it to a service listening on the host. There is no TUN interface for the kernel to route it out of.
Anyone running a self-hosted model or agent stack behind this tunnel runs into the same class of problem — the access layer is where it usually breaks. The six engineering defenses around that stack cover the neighbouring decisions.
This is also why restarting the container fixes nothing. It is a mode setting, not a stuck process. If you have been rebooting your NAS to “clear” this, that is the time you have been losing.
Fix 1 (recommended): tailscale serve port relay — reversible, no rebuild
tailscale serve tells the running tailscaled to listen on the tailnet and forward to a local address. Because tailscaled is already inside the container and already able to reach 127.0.0.1, this works in userspace mode without touching the container definition.
# 0) Confirm current relay state (empty output = nothing is being forwarded)
docker exec tailscale tailscale serve status
# 1) Wire up the ports you actually need
docker exec tailscale tailscale serve --bg --tcp 445 tcp://127.0.0.1:445 # SMB
docker exec tailscale tailscale serve --bg --tcp 6806 tcp://127.0.0.1:6806 # SiYuan
docker exec tailscale tailscale serve --bg --tcp 8899 tcp://127.0.0.1:8899 # WordPress
# 2) Verify — this should now list forwarding rules
docker exec tailscale tailscale serve status
A correctly configured state looks like this:
|-- tcp://z4pro-wolv.taila72805.ts.net:445 (tailnet only)
|-- tcp://100.91.120.97:445
|--> tcp://127.0.0.1:445
|-- tcp://z4pro-wolv.taila72805.ts.net:6806 (tailnet only)
|-- tcp://100.91.120.97:6806
|--> tcp://127.0.0.1:6806
Two things worth knowing before you run this:
--bgkeeps it running in the background. Without it, the rule is tied to the foreground process and dies with your SSH session.- Rules are per port. There is no “forward everything” switch in this mode. If you later add a service on 8080, you add a rule for 8080.
Why this is the fix to reach for first: it changes nothing about the container. No rebuild, no downtime for everything else the NAS is doing, no risk of losing remote access while you are away from the machine. If the same box serves as the inference backend for a local model, you especially do not want to interrupt it mid-day — see how that stack is put together before you touch anything. It is also the only option that works if your NAS is a closed appliance (Z-space, Synology, QNAP) whose Docker UI will not let you hand-edit the Tailscale container’s environment.
Fix 2: switch to TUN mode (the root fix — read the warning first)
If you control the container definition, the clean solution is to stop using userspace mode:
# in the tailscale container definition
environment:
- TS_USERSPACE=false
Check the prerequisites before you do this:
docker exec tailscale ls -l /dev/net/tun # must exist
docker inspect tailscale --format '{{.HostConfig.Privileged}}' # must be true
Warning. This is not a change to make while you are away from the machine. Changing the mode tears down the tunnel, and if the new configuration does not come up cleanly you have just cut your own remote access to a box you cannot physically reach. Do it when you are on site, or when you have a second path in (a LAN connection, or physical access).
After the rebuild, tailscale serve status being empty is expected — with a TUN device the forwarding is handled by the kernel and you no longer need the relay rules.
The second wall: your PC firewall will block Tailscale again
If you fix the NAS and the problem moves to a Windows machine, this is why.
A Tailscale peer on the same tailnet may live on a completely different subnet from the one your firewall rules were written for. The tailnet space is 100.64.0.0/10 — it is not part of 192.168.1.0/24, and it is not part of whatever your home subnet is. A rule that allows only your home LAN will silently drop the tailnet address the moment the firewall profile turns on.
# Allow both the home LAN and the whole tailnet range
Set-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' `
-RemoteAddress 192.168.1.0/24,100.64.0.0/10
# Verify
Get-NetFirewallRule -DisplayName 'OpenSSH Server (sshd)' | Get-NetFirewallAddressFilter
One reading quirk: Windows normalises CIDR into a dotted netmask, so what you set as 192.168.1.0/24 reads back as 192.168.1.0/255.255.255.0. Both spellings mean the same thing. An assertion that compares against the literal CIDR string will report a failure on a change that actually succeeded.
Three-layer verification (skip a layer and you are guessing)
# L1 — the tunnel: peer is online and reachable
tailscale status | grep <peer-hostname>
tailscale ping <peer-tailnet-ip>
# L2 — the port: raw TCP, from the machine that was failing
nc -z -G 5 100.91.120.97 6806 && echo OPEN || echo no-answer
# L3 — the application: do one real thing with it
curl -s -o /dev/null -w '%{http_code} %{time_starttransfer}sn' http://100.91.120.97:6806/
Use nc and not curl for L2. Testing a non-HTTP port with curl produces a timeout that looks exactly like the bug you are chasing. SMB on 445 is the usual victim: curl http://host:445/ hangs whether or not SMB is healthy, while nc -z host 445 answers immediately.
And run the comparison from both addresses if you can. It localises the fault in one shot:
for h in 192.168.1.190 100.91.120.97; do
printf '%-16s ' "$h"
nc -z -G 5 "$h" 6806 && echo OPEN || echo "no-answer"
done
If the LAN address fails too, your service is down and Tailscale is innocent. If only the tailnet address fails, you are in this article.
Measured numbers (Dubai, on the direct path)
These were taken from a Mac mini on the same LAN as the NAS, five samples per row, using curl -w against the same two ports on both addresses.
Port 8899 (WordPress)
| Path | connect | TTFB (median) | total |
|---|---|---|---|
LAN 192.168.1.190 |
6–22 ms | 217 ms | 224–310 ms |
Tailnet 100.91.120.97 |
7–9 ms | 224 ms | 236–263 ms |
Port 6806 (SiYuan)
| Path | connect | TTFB (median) | total |
|---|---|---|---|
LAN 192.168.1.190 |
5–7 ms | 12.8 ms | 10.7–14.6 ms |
Tailnet 100.91.120.97 |
5–6 ms | 11.9 ms | 10.7–13.0 ms |
On the direct path, Tailscale costs you nothing worth measuring — the difference in both directions falls inside run-to-run noise. The 217 ms TTFB on the WordPress port is application render time (PHP, database, plugins), not tunnel overhead; you can see that clearly because the SiYuan port answers in 12 ms over the same tunnel.
The cost appears when the path becomes a relay. From this client, the Dubai DERP region measured 79.6 ms, versus 30 ms for the direct peer-to-peer path to the NAS.
direct peer-to-peer : ~30 ms
via DERP (dbi) : ~79.6 ms one way to the relay, plus relay-to-NAS
Decision table
| What you observe | Most likely cause | One command to confirm | Next step |
|---|---|---|---|
ping works, port times out, you are remote |
userspace mode + relay path | tailscale status | grep relay |
Fix 1 (tailscale serve) |
ping works, port times out, you are on the same LAN |
Service down or bound to the wrong interface | nc -z <LAN-IP> <port> |
This is not a Tailscale problem |
Connection refused (not a hang) |
Service not listening on that port | ss -lntp | grep <port> |
Start the service or fix its bind address |
| Ports work from home, fail from office | Path difference (direct vs relay) | Compare tailscale status on both clients |
Fix 1, then reconsider Fix 2 when on site |
| Everything is slow but nothing is dead | Traffic is being relayed | tailscale netcheck |
Fix 2 (TUN) if you can afford the rebuild |
| Windows peer unreachable after enabling firewall | Rule covers the LAN but not 100.64.0.0/10 |
Get-NetFirewallRule ... | Get-NetFirewallAddressFilter |
Add 100.64.0.0/10 to RemoteAddress |
Measurement notes
Being explicit about what is and is not measured here, because the difference matters:
Directly measured on this setup: the tailscaled invocation and container flags; tailscale serve configuration and its effect; LAN-vs-tailnet TTFB on two ports (5 samples each); DERP region latencies via netcheck; peer connection types (direct vs relay "dbi") as reported by tailscale status on the NAS.
Observed but not reproduced on demand: the failing path itself. The one peer in this tailnet that connects over a relay is not continuously online, so the relay-path failure was confirmed structurally (relay connection type + userspace mode + the reported symptom on that machine) rather than by a live side-by-side capture. If you are hitting this, the reproduction is four commands: on the remote machine, note the relay in tailscale status, nc -z the port, then apply Fix 1 and repeat. That before/after pair on your own network is worth more than any lab number.
Not measured: throughput on the relay path, and behaviour on other NAS vendors’ Docker implementations.
Scan with WeChat Pay
Scan with Alipay本文采用 CC BY 4.0 许可。欢迎转载与引用,请注明作者并附上原文链接。
Licensed under CC BY 4.0. Quoting and republishing are welcome with attribution and a link back to this article.