Summary
After the upstream tsnet session goes away (auth-key expiry/revocation, control-plane drop,
network change), ts-bridge connect keeps its local TCP listener up and accepting, but every
forwarded connection now dies immediately at the remote handshake. There is no signal that the
tunnel is functionally dead — the port still listens, so any health check that only probes "is
something listening on the port?" passes while the bridge is useless.
Evidence
A bridge that had been forwarding fine (an SSH pull completed through it) later produced, with the
local listener still LISTENING, the same reset on every attempt:
kex_exchange_identification: read: Connection reset by peer
Connection reset by 127.0.0.1 port <port>
netstat confirmed 127.0.0.1:<port> was still LISTENING (same PID) at the moment of the resets.
A fresh connect then failed at startup with:
auth key rejected by control plane (likely expired, revoked, or single-use already consumed)
tailscale init failed (...): tsnet.Up: backend: invalid key
— i.e. the upstream identity had lapsed, but the already-running bridge never reflected that on the
listener side; it just kept resetting forwarded connections.
Impact
- Silent, hard-to-diagnose failures: clients see "connection reset" / "kex" errors with no hint
that the bridge's upstream is dead.
- Port-based liveness checks (and wrappers/automation that wait for the listener to come up) get a
false "healthy" reading and proceed against a dead tunnel.
Expected
The local listener's health should track the upstream tsnet session. On upstream loss the bridge
should do at least one of:
- stop accepting new local connections (callers fail fast with connection-refused, which is
detectable), and/or
- exit non-zero with the upstream reason, and/or
- surface the dead state via
status / --health-addr so a supervisor can restart it.
Notes
Related but distinct from #203 (READY line at startup) and #204 (structured exit reason when
connect fails at startup): those cover startup signaling. This is runtime upstream death on an
already-running bridge whose local listener stays up.
Summary
After the upstream tsnet session goes away (auth-key expiry/revocation, control-plane drop,
network change),
ts-bridge connectkeeps its local TCP listener up and accepting, but everyforwarded connection now dies immediately at the remote handshake. There is no signal that the
tunnel is functionally dead — the port still listens, so any health check that only probes "is
something listening on the port?" passes while the bridge is useless.
Evidence
A bridge that had been forwarding fine (an SSH pull completed through it) later produced, with the
local listener still LISTENING, the same reset on every attempt:
netstatconfirmed127.0.0.1:<port>was still LISTENING (same PID) at the moment of the resets.A fresh
connectthen failed at startup with:— i.e. the upstream identity had lapsed, but the already-running bridge never reflected that on the
listener side; it just kept resetting forwarded connections.
Impact
that the bridge's upstream is dead.
false "healthy" reading and proceed against a dead tunnel.
Expected
The local listener's health should track the upstream tsnet session. On upstream loss the bridge
should do at least one of:
detectable), and/or
status/--health-addrso a supervisor can restart it.Notes
Related but distinct from #203 (READY line at startup) and #204 (structured exit reason when
connectfails at startup): those cover startup signaling. This is runtime upstream death on analready-running bridge whose local listener stays up.