Skip to content
tsrelay

Errors

Errors produced by the relay itself are RFC 9457 application/problem+json documents, and carry their code twice: in the Tsrelay-Error header, and as the code field of the body. The type field links to the code's entry on this page, such as https://tsrelay.mazetti.me/errors/tunnel_unavailable; detail says what exactly went wrong.

{
  "type": "https://tsrelay.mazetti.me/errors/tunnel_unavailable",
  "title": "Tunnel Unavailable",
  "status": 561,
  "detail": "no tunnel is attached to this hook",
  "code": "tunnel_unavailable"
}

Responses from your app are never marked, including its own 5xx. Tunnel and transport failures have statuses of their own, 561 to 565, so they can't be mistaken for your app's 502, 503 or 504; clients that don't know them treat them as 500.

Calling a hook

Senders see these when they call a hook.

sender_forbidden

403. The caller has no sender grant: calling a hook needs the sender role on the relay. Ask a relay admin to grant the calling device the sender role; see Access control.

hook_not_found

404. No hook answers at this path, the hook is in a channel the caller can't use, or it was deleted or expired while a call was held for it. Check the URL; its owner can list their hooks with tsrelay hooks list.

body_stalled

408. The sender paused the request body for longer than the hook's idleTimeout. The hook's owner can raise it with tsrelay hooks update <hook> --idle-timeout 1m.

body_too_large

413. The request body is larger than the hook's maxBodyBytes, or, for a call from the internet, larger than the relay's --funnel-max-body-bytes. The hook's owner can raise the limit with tsrelay hooks update <hook> --max-body-bytes 10485760, up to the relay's maximum.

path_too_long

414. The call's path, percent-encoded, is longer than 8 KiB. The relay refuses it before routing, like a server refusing an overlong request line, so it doesn't reach the inspector or the audit log. Shorten the path; send long data in the body.

shutting_down

503. The relay is shutting down, usually for a restart, while the call waited for a tunnel; one arriving then gets it too. Calls already delivered to a tunnel finish first. Retry-After is 5: retry then, and the restarted relay holds the call until the tunnel is back.

rate_limited

429. The hook's rateLimit was exceeded. Retry-After is the limiter's wait, rounded up to whole seconds. Wait, then retry; the hook's owner can raise the limit with tsrelay hooks update <hook> --rate-limit 100/200.

tunnel_unavailable

561 Tunnel Unavailable. No tunnel attached to the hook within its holdTimeout. Retry-After is the holdTimeout, but never less than 5 s. The hook's owner can attach one with tsrelay attach <hook> --to 3000, or wait longer with tsrelay hooks update <hook> --hold-timeout 1m.

hold_queue_full

562 Tunnel Overloaded. No tunnel is attached to the hook, and too many calls are already waiting for one (the relay's --hook-hold-queue). Retry-After is the holdTimeout, but never less than 5 s. The hook's owner can attach a tunnel with tsrelay attach <hook> --to 3000.

tunnel_overloaded

562 Tunnel Overloaded. Too many calls are in flight on the tunnel at once (the relay's --tunnel-concurrency). Retry-After is 1; retry in a moment.

tunnel_lost

563 Tunnel Lost. The tsrelay process serving the hook disconnected while delivering the call. It reconnects on its own; check that it still runs.

upstream_unreachable

564 Upstream Unreachable. The tunnel is up, but nothing answered at its target. Start the app, or check the port or URL tsrelay forwards to.

upstream_timeout

565 Upstream Timeout. The app didn't send its response headers within the hook's responseTimeout, or its response stalled past the idleTimeout. The hook's owner can raise it with tsrelay hooks update <hook> --response-timeout 1m.

Call outcomes

A call's outcome in the inspector and the API is pending, ok, one of the codes above, or sender_canceled: the sender went away before the call finished, so no response was sent and no error is recorded against the hook.

Using the API

The API, the web UI and the CLI see these. Where one says the relay may not speak your tsrelay's API, install the relay's version of the CLI; see Updating the CLI.

forbidden

403. Your grants don't allow this: a missing role, or a hook you can see but only its owner can act on. Ask a relay admin to grant you the role; the relay's web UI shows them the grant.

not_identified

403. Tailscale couldn't tell the relay who is calling, so no grant applies. Check tailscale status, and reach the relay over Tailscale, not another network.

cross_site

403. An API write came from another site, as the browser's Sec-Fetch-Site header says. API writes must come from the relay's own web UI or from a tool such as the CLI.

channel_forbidden

403. The hook is in a channel your grants no longer match, so a tunnel can't serve it. Ask a relay admin to grant you the channel.

stream_limit

429. The node already has 8 event streams open, the most the relay allows one node. Close a browser tab or a client that watches the relay, then retry.

tunnel_limit

403. You already have as many tunnels attached as your maxTunnels grant allows. Stop one you no longer need with tsrelay stop, or ask a relay admin to raise the limit.

not_found

404. The relay has no such hook or call for you. tsrelay hooks list shows the hooks you own.

invalid

422. Something sent isn't valid, such as a hook setting above the relay's maximum. The problem's errors field names each one; correct them, then retry.

unsupported_field

422. The request has a field this relay doesn't know, so it is newer than the relay; within one major version, this is how an older relay says so. The problem's errors field names each one, like invalid does. Ask the relay's operator to upgrade the relay, or install the CLI version the relay runs.

bad_request

400. The relay couldn't read the request: malformed JSON, a tunnel request without its upgrade headers, or one that names no hook. This tsrelay may not speak the relay's API.

not_public

409. The hook isn't public, so it has no public URL to rotate. Make it public first with tsrelay hooks update <hook> --public.

superseded

409. A newer tunnel has taken the hook over, and a tunnel that reconnects after being replaced may not take it back. Stop the other tunnel, or attach again without resuming.

attach_rejected

409. The hook's attachPolicy is reject and a tunnel is already attached. Stop that one, or set the policy to takeover with tsrelay hooks update <hook> --attach-policy takeover.

call_pending

409. The call is still being delivered, so it can't be replayed yet. Retry once it has an outcome.

call_refused

409. The relay refused the call by channel and never delivered it, so there is nothing to replay. Send a new test request instead.

not_captured

409. The call can't be replayed because its request wasn't captured in full: bodies aren't captured for the hook, or the body or the query string was cut off at the capture limit. Turn capture on with tsrelay hooks update <hook> --capture to replay future calls.

misdirected_request

421. The relay doesn't answer to the host name the request used. Check --relay and TSRELAY_RELAY.

unsupported_media_type

415. API requests with a body must be application/json. This tsrelay may not speak the relay's API.

not_acceptable

406. The resource isn't available in the representation the Accept header or extension asked for; the detail lists those it is available in.

method_not_allowed

405. The resource exists, but doesn't take this method. This tsrelay may not speak the relay's API.

internal

500. Something went wrong on the relay itself, such as failing to save a hook. Retry; if it keeps failing, ask the relay's operator to check its logs.