Skip to content
tsrelay

Configuration

Every serve flag can also be set as an environment variable: --ephemeral-grace becomes TSRELAY_EPHEMERAL_GRACE. Run tsrelay serve --help for the full list. Client flags have a variable too unless they act (such as --public, --yes or --all) or name a target; the CLI page lists which.

Flag names follow one scheme, so a prefix tells you what a flag governs:

PrefixGoverns
hook-<setting>-default, hook-<setting>-max
A hook setting's default, and the ceiling a hook may set it to
user-*
Per-user limits and naming; grants can raise the limits
tunnel-*
Tunnels
capture-*
What the inspector keeps
funnel-*
Public hooks served through Tailscale Funnel
ops-*
The operations listener
otel-*
OpenTelemetry
FlagDefault
--hostnametsrelayTailnet machine name
--state-dir/var/lib/tsrelay in the container, $XDG_STATE_HOME/tsrelay for the binaryDirectory for tailnet state and hooks
--tlstrueHTTPS on 443; false serves HTTP on 80
--advertise-tagstag:tsrelayTags the relay node advertises
--cap-nametsrelay.mazetti.me/cap/relayCapability that carries grants
--user-sluglocal-partmoritz@example.com becomes ~moritz; login uses the full login
--user-slug-overridealice@b.com=alice-b, for collisions
--user-max-hooks / --user-max-tunnels10 / 5Per user; grants can raise them with maxHooks / maxTunnels
--user-max-hook-ttlunlimited (0)Longest a persistent hook may live. A grant's maxHookTtl only raises a limit set here; while this is unlimited, it has no effect
--ephemeral-grace10mHow long an ephemeral hook survives without its tunnel
--tunnel-concurrency32Concurrent calls per tunnel
--revalidate-interval60sHow often attached tunnels and open event streams are re-authorized
--shutdown-timeout20sHow long calls already delivered get to finish when the relay stops. Keep it below the pod's termination grace period; see restarts
--hook-hold-queue100Calls per hook that may wait for a tunnel to attach at once
--capture-calls100Calls per hook the inspector keeps in memory
--capture-limit-bytes65536Bytes the inspector keeps of each body, of each call's headers, and of its query string
--capture-total-bytes134217728Bytes the inspector keeps of all calls together (128 MiB); past it, the oldest calls of any hook go first. See sizing
--capture-bodiestruefalse keeps no bodies for any hook
--redact-headersnoneHeaders whose values are never kept, in addition to Authorization, Proxy-Authorization, Cookie, Set-Cookie and X-Api-Key, which are always redacted; names match without regard to case
--funnelfalseServe hooks their owners made public on the internet, through Tailscale Funnel; requires --tls
--funnel-port443The Funnel port for public hooks: 443, 8443 or 10000
--funnel-max-body-bytes26214400 (25 MiB)Largest request body of a call from the internet
--ops-listenoffAddress for /healthz, /readyz and /metrics (for example :9090), without authentication. Never on the relay's tailnet node; on a host that runs tailscaled, bind to a specific address such as 127.0.0.1:9090, since :9090 includes the host's tailnet address
--ops-pproffalseServe Go's profiler under /debug/pprof/ on the ops listener
--otelfalseExport traces and metrics over OTLP/HTTP; see Tracing
--otel-propagationtrueWith --otel: continue a sender's traceparent and pass the relay's own to the app, so one trace spans sender, relay and app; false forwards trace headers untouched
--log-formatjsonjson or text
--log-levelinfodebug, info, warn or error. The audit log is written at any level
--verbose-tailscalefalseInclude tsnet's own logs
--update-checktrueAsk GitHub for the latest tsrelay release on startup and every 6 hours, and tell admins when it's newer than the relay; false for air-gapped relays

Hook defaults and limits

Each hook setting has a relay-wide default, --hook-<setting>-default, and the numeric and duration settings also a maximum, --hook-<setting>-max:

SettingDefaultMaximum
attachPolicytakeover
hostHeaderpreserve
holdTimeout30s5m
responseTimeout2m30m
idleTimeout60s30m
maxBodyBytesunlimitednone
rateLimit100/200none
capturefalse

For example --hook-rate-limit-default=100/200, or --hook-max-body-bytes-default=1048576 --hook-max-body-bytes-max=10485760 (the default body size is unlimited, which a maximum wouldn't allow, so set both). A hook may only set a value within the maximum. A maximum of 0 means none. Zero isn't "no limit" everywhere, though; compatibility lists what it means for each limit.