Limits and deployment
Tobaru limits admitted work and protocol metadata independently of upstream application latency. Upstream response-header and body-progress deadlines are disabled by default. Enable them only when an application needs a deadline enforced by the proxy.
Listener Resources
Section titled “Listener Resources”TCP listeners default to no connection cap (tcp_max_connections: null). Set a positive
integer to enable the cap. A permit covers the entire accepted connection, including
protocol detection and TLS. At capacity, new connections are closed
without starting a forwarding task. Existing connections count across reloads of the same
bind address, including removal and re-addition while connections survive. Lowering a cap
does not evict existing connections; admission resumes when their count falls below it.
Explicit null disables the cap. Unlimited generations are still counted for later reloads.
UDP listeners default to no association cap (udp_max_associations: null) and
udp_queue_capacity: 1024 packets per association. Set a positive association cap to limit
the number of source IP/port pairs. New associations are dropped at that cap; existing
associations continue. Packets are dropped when an association’s queue is full. Queue depth
must be between 1 and 65,535. Both settings are listener fields, alongside address and
transport.
These defaults preserve legacy capacity but do not protect against total FD or memory exhaustion. Configure limits to suit the deployment. For example, an explicit cap of 128 UDP associations with an eight-packet queue allows at most approximately 64 MiB of queued maximum-size payload per listener (128 times 8 times 64 KiB). This is a sizing example, not a throughput guarantee. In-flight payloads, sockets, tasks, and kernel buffers are additional. Multiple listeners multiply the budget. Raising both settings can grow memory consumption dramatically. TCP limits likewise exclude backend sockets and other listeners; size them against the process FD and memory limits.
TCP and UDP URL shortcuts use these same defaults. They still allow all peers and disable built-in firewall handling. Non-loopback shortcut binds produce one warning per URL load; the bind address is never silently changed.
Reverse Tunnels
Section titled “Reverse Tunnels”Tunnel limits are finite and independent of ordinary frontend limits:
| Field | Default | Counts |
|---|---|---|
tunnel_server.max_connections |
1024 | Accepted control, data, and handshaking sockets, including retained old sockets |
tunnel_server.max_handshakes |
32 | Concurrent TLS handshakes |
peers.NAME.max_active_connections |
128 | Queued/Opening backend leases plus returned streams, including cached H1/H2 backends |
peers.NAME.max_pending_opens |
32 | Opens waiting for a channel or carrier Ready |
peers.NAME.open_timeout_secs |
10 | Queue wait, data-channel creation, origin setup, and optional origin TLS; the agent’s local dial is independently capped at 10 seconds |
tunnel_client.max_active_connections |
128 | Agent-local dials and active origin connections |
tunnel_client.max_data_connections |
256 | Agent data sockets/reservations across control reconnections |
tunnel_client.max_concurrent_local_dials |
16 | Concurrent agent-origin connection setup |
Connection counts are at most 65,536, pending/local-dial counts at most 4,096,
and setup time at most 86,400 seconds. All counts/times are positive.
Pending/local-dial limits cannot exceed their active
limits; handshake limits cannot exceed the endpoint connection limit. null is not
an unlimited opt-out. Configure the client’s data cap at least as high as the hub’s
peer active budget, unless smaller local capacity is intentional.
Defaults are fixed rather than clamped to other limits. Lowering an endpoint’s
max_connections below 32 also requires setting max_handshakes. Lowering a peer’s
max_active_connections below 32 requires setting max_pending_opens; lowering a
client’s max_active_connections below 16 requires setting
max_concurrent_local_dials. Set each dependent limit no higher than its parent.
Only the control connection is maintained without demand. Four creation credits and four concurrent agent data dials bound on-demand channel creation. Larger bursts wait for creation capacity; they cannot create an unbounded pending queue. Each authenticated attachment claims the oldest live queued open; an attachment with no remaining demand is closed. Expired credits pruned by the scheduler and reported creation failures use bounded backoff. An attachment presenting an expired credit is rejected without backoff. Cached HTTP connections retain active leases between requests. The peer’s active limit is not an HTTP-request rate or stream count.
Outer setup, including DNS, has a ten-second deadline; ingress additionally requires ClientHello within three seconds. Control pings occur every 20 seconds with a 60-second matching-pong deadline. The agent closes control after 60 seconds without a valid frame. Data attachment, including receipt of the first Open, shares a single ten-second deadline starting when the agent receives CreateData. Data channels have no idle wait or heartbeat; no tunnel heartbeat bytes are injected into active payloads. The outer TCP carrier enables nodelay and keepalive (20-second idle, 10-second interval). OS probe counts still affect TCP failure detection.
Sixteen process-wide tunnel DNS permits remain held by OS resolver jobs until the blocking calls actually finish, even after caller cancellation or reload. Stuck export lookups can therefore delay hostname-based hub reconnects. IP literals and cached hub addresses for data dials bypass that pool. There is no unbounded resolver retry queue. The 32 KiB rustls write-buffer setting does not bound total TLS or socket memory.
Accepted sockets, in-flight data connections, origin sockets, visitor sockets, and one additional shutdown-handle FD per active public tunnel all consume resources. Size process/host limits accordingly. The handshake cap is not denial-of-service protection: unauthorized traffic can occupy it. Restrict the dedicated ingress port at the firewall where possible.
Transient control loss closes unassigned data connections and fails unfinished opens, but established independent data streams can continue, including existing H1/H2 backend reuse. An open becomes established when the hub accepts Ready and registers the socket under the current generation and session; its result may reach the caller after control loss. New physical opens fail promptly until a client registers again. No application request or failed TCP stream is replayed. Client reconnect uses exponential backoff with jitter, up to 60 seconds; only 60 seconds of stable connectivity resets it.
Every successfully prepared config reload closes all tunnel transports, even for an
unrelated listener edit, unchanged save, or touch. Active SSH, WebSocket, and download
connections are interrupted. Invalid reloads leave the running generation untouched.
Retired frontend routes cannot reopen a tunnel through new credentials. Async tunnel
task cleanup has a five-second budget; already-running bounded DNS calls may finish
later without authority to dial. Socket shutdown cannot undo delivered application
effects or erase bytes already buffered by higher layers. Direct-listener reload
behavior is unchanged. There is no added graceful signal-shutdown facility.
Use RUST_LOG=info for agent registration/reconnect and reload diagnostics. Channel
failures require RUST_LOG=debug and a debug-enabled build, such as cargo build.
Release binaries compile out these debug messages regardless of RUST_LOG.
A connected agent is not an origin health check. Existing HTTP
failure behavior remains: H1 paths may close a connection while H2-related paths can
produce gateway errors. There is no automatic fallback to another destination.
HTTP and TLS Timeouts
Section titled “HTTP and TLS Timeouts”http_timeouts belongs to each HTTP target:
http_timeouts: # Omission uses 10 seconds for H1 and http2.header_timeout_secs for H2/detection. request_header_timeout_secs: 10 response_header_timeout_secs: null upstream_body_progress_timeout_secs: null keepalive_idle_timeout_secs: 60 local_body_timeout_secs: 10Omitting the object, using {}, or supplying only some fields retains the defaults for
the remaining fields. Explicit null disables the applicable optional timer; positive
integer seconds enable it. Zero, negatives, fractional values, and unknown timeout fields
are rejected.
- H1 request headers have an absolute 10-second default, not a per-byte timer. A keepalive connection first waits for input under its separate idle budget, then reads headers.
- H2 and plaintext protocol detection inherit
http2.header_timeout_secswhen the request header setting is omitted. An explicit value overrides it;nulldisables that frontend timer, but not framing validation, header-size accounting, or separate H2 setup limits. - Keepalive defaults to 60 seconds. H2 idle retirement applies only with no active stream
tasks.
nulldisables idle retirement for both protocols, not reload draining. - H1 local actions drain request bodies under an absolute 10-second default, including
trailers.
local_body_timeout_secs: nulldisables this deadline. H2 local actions keep their existing protocol-specific progress and drain behavior. server_tls.handshake_timeout_secsdefaults to 10 seconds in termination mode only.nulldisables it. Passthrough omission remains valid; a positive termination-handshake setting is invalid for passthrough.
Upstream-Owned Latency
Section titled “Upstream-Owned Latency”Omitted or null response-header and upstream-body settings impose no application latency
deadline in any H1/H2 forwarding combination. A stalled backend may retain an admitted
connection or stream until cancellation or closure. Configured tcp_max_connections and
HTTP/2 admission limits still apply; H1-to-H1 forwarding has no connection cap by default.
An explicit response_header_timeout_secs is an absolute budget from submission of the
request head through receipt of the final response head. It includes concurrent upload
time. Informational responses, including 100 and 103, do not extend it. It does not
bound response-body transfer.
An explicit upstream_body_progress_timeout_secs bounds stalled upstream body reads and
upstream upload writes/capacity waits, including trailers and EOF. It is a progress budget,
not a maximum transfer lifetime. Downstream backpressure and waiting for frontend upload
bytes do not consume an upstream I/O budget. It does not change raw tunnels or upgrades.
Existing H2 connection/setup limits and incomplete-frame/header-block watchdogs remain active independently. Backend silence between complete messages is not an incomplete frame. Encoded and decoded metadata limits remain active even with response timers off.
For forwarding involving H2, http2.body_progress_timeout_secs retains its frontend
upload-read and downstream-write protection. Expect: 100-continue upload timing starts
only after Continue is forwarded or upload bytes arrive. An early final response replaces
a pending expectation without requiring the client to upload. A successful early response
does not otherwise cancel an active duplex upload. Long backend pauses do not consume the
next frontend operation’s initial progress budget.
Legacy H1-to-H1 streaming and raw tunnels retain unlimited frontend streaming lifetimes. No new mandatory upstream setup, raw inactivity, or whole-connection deadline is imposed.
HTTP Metadata and Logging
Section titled “HTTP Metadata and Logging”Legacy H1 request limits remain unchanged: less than 4 KiB per line, at most 38 fields plus the start line. The legacy H1-to-H1 response parser permits at most 128 fields, 16 KiB per line, and 64 KiB for the complete head including CRLFs. H1/H2 translation uses separate configurable field-list accounting rather than these legacy response-head limits. At most 16 informational responses precede a final response.
Legacy H1 chunked trailers permit at most 128 fields and 64 KiB of field lines including their CRLFs, excluding the final empty line. Over-limit fields are rejected before being forwarded. H2 and translation paths retain their separate bounded field-list accounting.
H1 access logs omit query strings and escape control characters in paths. Request-line and malformed-header errors do not echo raw contents. Paths may still contain sensitive application identifiers; use appropriate log access controls, filters, rotation, and deployment rate limits. Startup/configuration errors are not suppressed.
Deployment Contracts
Section titled “Deployment Contracts”- Routing matches raw, undecoded path segments, excluding queries. Escaped separators, dot segments, and repeated slashes are not normalized. Align backend interpretation and authorization with routing; do not use route predicates alone as an authorization boundary.
- Static serving roots and their ancestors must be controlled by trusted writers. Stable-tree traversal checks are not a race-resistant sandbox against hostile filesystem replacement.
- Built-in firewall commands require a trusted PATH and executable directories. Rules are protocol/port-wide, not specific to the listener’s bind address; earlier rules affect their effect. Prefer externally managed rules for shared ports/addresses or multiple instances.
- Reload validation failures retain the running configuration. Successful preparation stops old listeners before replacement binding; subsequent bind failure is not transactional and can interrupt service. Use a supervisor. H2 graceful shutdown and bounded draining remain.
- Backend selection remains one-attempt round-robin on connection establishment, with H2 connection reuse. Requests are not replayed and no proactive health checks are performed.
- UDP hostname resolution remains per association through the system resolver. Results are not permanently pinned at configuration load and no proxy-managed DNS cache is introduced.
YAML remains supported through the pinned yaml_serde continuation of serde_yaml, with
the existing Rust crate name preserved. Configuration compatibility fixtures cover aliases,
scalar typing, custom actions, duplicate keys, explicit nulls, and literal merge keys.
The pinned Git H2 dependency’s validation patches remain in use.