tobaru uses YAML configuration files. Multiple configuration types can be combined in a single file or split across multiple files.
- Configuration Structure
- Server Config
- TCP Targets
- Server TLS
- Client TLS
- HTTP Routing
- HTTP Actions
- UDP Targets
- IP Groups
- URL-Based Configuration
- Command Line
A configuration file is a YAML array containing one or more entries. Each entry can be:
- Server Config -- defines a listener with one or more forwarding targets
- IP Group -- defines a reusable named set of IP ranges
# Server config (has 'address')
- address: "0.0.0.0:443"
transport: tcp
targets:
- location: backend:443
allowlist: 0.0.0.0/0
# IP group (has 'group')
- group: internal
ip_masks:
- 192.168.0.0/16
- 10.0.0.0/8Multiple config files can be passed on the command line and are merged together. This is useful for separating IP group definitions from server definitions.
- YAML:
.ymlor.yamlextension (recommended) - JSON:
.jsonextension, with optional//single-line comments
address: "0.0.0.0:8080" # Bind address (required)
transport: tcp | udp # Default: tcp
# TCP-specific settings (only when transport: tcp)
tcp_nodelay: true # Disable Nagle's algorithm (default: true)
tcp_keepalive: true | false | null | { idle_secs: int, interval_secs: int }
use_iptables: false # Auto-configure iptables rules (default: false)
# One or more targets
target: TcpTargetConfig # Single target
targets: [TcpTargetConfig] # Multiple targetsaddress: "0.0.0.0:8080" # IPv4
address: "[::]:8080" # IPv6Controls TCP keepalive probes on the server-side (client-facing) socket.
# Use defaults (idle: 300s, interval: 60s)
tcp_keepalive: true # or omit the field
# Disable keepalive
tcp_keepalive: false
tcp_keepalive: null
# Custom values
tcp_keepalive:
idle_secs: 120
interval_secs: 30When use_iptables: true, tobaru automatically configures iptables/ip6tables rules to drop packets from IPs not in the allowlist at the kernel level, before they reach the application.
Requires root or CAP_NET_RAW + CAP_NET_ADMIN capabilities. Supported on Linux only.
# Clear all tobaru-managed iptables rules
sudo tobaru --clear-iptables-all
# Clear rules for specific config files only
sudo tobaru --clear-iptables-matching config.yamlEach TCP target defines a forwarding destination with access control and optional TLS.
targets:
- allowlist: string | [string] # IP masks or group names (required)
location: string # Single backend address (e.g., "backend:8080")
locations: [string | object] # Multiple backends (round-robin)
server_tls: ServerTlsConfig # Optional incoming TLS config
tcp_nodelay: true # Default: true
tcp_keepalive: ... # Same options as server-level (default idle: 120s, interval: 30s)target (singular) is an alias for a single-element targets array.
Locations specify where to forward traffic. Each location is either a plain address string or an object with additional settings.
# Simple string address
location: backend:8080
# UNIX domain socket path
location:
path: /run/app.sock
# Address with outgoing TLS
location:
address: upstream.example.com:443
client_tls:
verify: true
# Multiple backends (round-robin load balancing)
locations:
- backend1:8080
- backend2:8080
- backend3:8080The address and addresses field names are accepted as aliases for location and locations.
Controls which source IPs may use this target. An IP outside this target's allowlist may still use another eligible target on the listener. Connections from IPs not matching any target's allowlist are rejected.
CIDRs are normalized to their network address: 192.0.2.123/26 means
192.0.2.64/26. Host bits are cleared without changing the prefix length, for
IPv4 and IPv6 literals and named-group entries. Equivalent spellings count as
the same network for duplicate checks. Bare addresses still match only that host.
Generated IPv4 firewall rules cover the portion of each CIDR within the mapped
IPv4 block (::ffff:0:0/96), including IPv6 prefixes that contain that entire block.
# Allow all IPv4
allowlist: 0.0.0.0/0
# Multiple CIDR ranges
allowlist:
- 192.168.1.0/24
- 10.0.0.0/8
# Specific addresses
allowlist:
- 1.2.3.4
- 2001:db8::1
# Named IP groups
allowlist:
- internal
- trusted
# Mix of literals and groups
allowlist:
- internal
- 203.0.113.0/24TCP routing checks all applicable conditions before selecting a target. A TLS
target must match the source IP, SNI, and ALPN offer. A raw forwarding target
without server_tls accepts arbitrary bytes, including TLS. A plaintext HTTP
target is not an opaque TLS fallback.
Eligible targets are ranked in this order:
- Longest matching source-IP prefix in the target's allowlist.
- TLS over raw forwarding at the same IP prefix length.
- Exact SNI over the deepest matching wildcard, then shallower wildcards, then
any. - An explicitly matched ALPN protocol over
any. - Earlier target in configuration order for remaining ties.
Passthrough and termination have equal priority. A more specific SNI pattern whose IP or ALPN conditions fail does not hide eligible broader patterns. When a target lists multiple IP prefixes or SNI patterns, its most specific matching entry is used. Repeated IP prefixes across TCP targets are allowed; duplicates within a single target's allowlist remain errors.
For example, a raw /32 overrides a TLS /0 for that source IP. At equal IP
prefix lengths, a matching TLS catch-all wins over a raw catch-all. Configuration
order does not override these specificity rules. UDP retains longest-prefix IP
routing without TLS inspection.
IP specificity is measured within the client's address family. For IPv4 clients,
all, ::/0, and 0.0.0.0/0 have equal specificity; the internal IPv4-mapped
IPv6 representation does not give an IPv4 catch-all extra priority. IPv6 prefixes
covering the entire mapped IPv4 range likewise have IPv4 /0 specificity.
targets:
# Internal users get admin backend
- location: admin-backend:8080
allowlist: 10.0.0.0/8
server_tls:
mode: passthrough
sni_hostnames: app.example.com
# Everyone else gets public backend
- location: public-backend:8080
allowlist: 0.0.0.0/0
server_tls:
mode: passthrough
sni_hostnames: app.example.comA location or locations target without server_tls is protocol-agnostic.
It remains eligible when no TLS route matches, including when another TLS
target allows the same source IP. Its position in the target list does not
make it override a more specific eligible route.
targets:
# Forward everything not selected by a more specific route, including TLS.
- allowlist: 0.0.0.0/0
location: /run/catch-all.sock
- allowlist: 0.0.0.0/0
location: /run/api.sock
server_tls:
mode: passthrough
sni_hostnames: api.example.comHere, TLS for api.example.com uses /run/api.sock; TLS for other names,
connections without SNI, and non-TLS traffic use /run/catch-all.sock.
When inspection is necessary, the ClientHello read has a five-second deadline. Malformed, incomplete, or oversized ClientHellos may fall back to an eligible raw target, replaying every buffered byte unchanged. Server-first protocols may wait for this deadline. A raw target known to outrank every applicable TLS route can be selected immediately without inspection. TLS-looking bytes never fall back to a plaintext HTTP target.
Once a target is selected, TLS handshake/authentication errors and backend connection errors terminate the connection. They do not retry another target.
Access control: a broad raw target intentionally grants fallback access, including when a specific TLS target's IP or ALPN condition fails. A target's allowlist is not a global deny rule for its SNI hostname. Remove or narrow the raw fallback, and any broader TLS routes, when unmatched traffic must be rejected.
Configures TLS handling for incoming connections.
server_tls:
mode: terminate | passthrough # Default: terminate
# SNI hostname matching
sni_hostnames: string | [string] # Hostname patterns (see below)
# ALPN protocol matching
alpn_protocols: string | [string]
# Certificate (required for terminate mode)
cert: string # Path to certificate file
key: string # Path to private key file
# Client certificate authentication (terminate mode only)
client_ca_certs: [string] # Paths to CA certificate PEM files
client_fingerprints: [string] # SHA256 fingerprints
# Deprecated
optional: false # true is auto-migrated to two separate targetsPassthrough -- reads the SNI and ALPN from the TLS ClientHello without decrypting. The raw TLS stream is forwarded transparently to the backend. No certificate or key is needed on the proxy.
Terminate (default) -- performs a full TLS handshake, decrypts the traffic, and can inspect HTTP content before forwarding. Requires cert and key.
Both modes can coexist on the same port for different SNI hostnames:
targets:
# Passthrough for public API
- location: api-backend:443
allowlist: 0.0.0.0/0
server_tls:
mode: passthrough
sni_hostnames: api.example.com
# Terminate for admin panel
- location: admin-backend:8080
allowlist: 10.0.0.0/8
server_tls:
mode: terminate
cert: admin.crt
key: admin.key
sni_hostnames: admin.example.comsni_hostnames: example.com # Exact match only
sni_hostnames: "*.example.com" # Any subdomain, but NOT example.com itself
sni_hostnames: ".example.com" # example.com AND all subdomains
sni_hostnames: any # Any SNI value
sni_hostnames: none # Connections with no SNI
sni_hostnames: # Multiple patterns
- api.example.com
- "*.cdn.example.com"
- any
- noneAmong eligible TLS targets at the same IP prefix length, matching priority is
exact match > deepest wildcard > shallower wildcard > any. All matching
patterns are considered before applying the target ranking.
Hostnames are case-insensitive. Trailing dots are stripped.
TLS termination accepts an optional positive server_tls.handshake_timeout_secs.
It bounds handshake completion after ClientHello routing, independently of the
existing ClientHello read timeout. Omitted or null disables the additional
deadline. It is invalid for passthrough, where the backend owns the handshake.
Terminated TLS does not accept 0-RTT early data.
server_tls:
cert: server.crt
key: server.key
handshake_timeout_secs: 10In passthrough mode, ALPN protocols from the ClientHello are matched against the configured list. In terminate mode, the configured protocols are advertised in the ServerHello.
alpn_protocols: h2 # Single protocol
alpn_protocols: # Multiple protocols
- h2
- http/1.1
alpn_protocols: any # Match any nonempty ALPN offer
alpn_protocols: none # Match only when no ALPNany controls matching, not protocol negotiation. Termination advertises only
explicit protocol names; any without explicit names negotiates no ALPN.
any does not match an absent ALPN offer; use [any, none] to accept both.
Passthrough leaves negotiation to the backend. HTTP actions dispatch negotiated
h2 to HTTP/2 and otherwise retain the HTTP/1 session, including legacy custom
ALPN names. Use [h2, http/1.1, none] to accept both standard protocols and clients
without ALPN. any does not by itself negotiate HTTP/2.
For terminating HTTP targets, omitted ALPN is derived from http_protocols:
[h2, http/1.1, none] by default, [http/1.1, none] for H1-only, or [h2] for
H2-only. Explicit ALPN lists are preserved and rejected if they permit a disabled
HTTP protocol. Custom names, any, and none require H1 enabled. Explicit empty
lists or null retain the generic no-ALPN behavior and also require H1 enabled.
Raw forwarding and TLS passthrough do not derive HTTP ALPN settings.
Requires clients to present a valid certificate. Only available in terminate mode (TLS 1.3 sends client certificates inside the encrypted tunnel, so passthrough mode cannot inspect them).
Two methods are supported and can be combined:
client_ca_certs-- accept any client certificate that chains to one of the provided CA certificatesclient_fingerprints-- accept client certificates matching specific SHA256 fingerprints
When both are configured, a certificate is accepted if it passes either check.
server_tls:
mode: terminate
cert: server.crt
key: server.key
sni_hostnames: secure.example.com
# Accept any cert signed by this CA
client_ca_certs:
- /path/to/ca.crt
# Also accept these specific self-signed certs
client_fingerprints:
- "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
- "1122334455667788990011223344556677889900112233445566778899001122"Fingerprints require 32 bytes (64 hexadecimal digits), with optional colons or spaces. Invalid fingerprints are rejected when loading configuration.
# Generate a CA and client certificate
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-nodes -keyout ca.key -out ca.crt -days 365 -subj "/CN=MyCA"
openssl ecparam -genkey -name prime256v1 -out client.key
openssl req -new -key client.key -out client.csr -subj "/CN=Client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt -days 365
# Get a certificate's SHA256 fingerprint
openssl x509 -in client.crt -noout -fingerprint -sha256Configures TLS for outgoing connections to backend servers. Set within a location object.
location:
address: upstream.example.com:443
client_tls:
verify: true # Verify server cert via WebPKI (default: true)
cert: string # Client certificate for mTLS
key: string # Client private key for mTLS
sni_hostname: string | null # SNI to send (default: derived from address, null to disable)
alpn_protocols: [string] # ALPN protocols to negotiate
server_fingerprints: [string] # SHA256 fingerprints for cert pinning# Enable with defaults (verify: true)
client_tls: true
# Enable with verification disabled
client_tls: "no-verify"
# Full object form
client_tls:
verify: true
sni_hostname: custom.example.com
server_fingerprints:
- "AA:BB:CC:..."client_tls cannot be used with passthrough mode (would cause TLS-in-TLS).
verify: false and no-verify disable WebPKI certificate verification. Without
configured certificate pins, an active attacker can impersonate the backend;
use verification or explicit SHA256 pins for untrusted networks.
client_tls:
server_fingerprints:
- "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"# Get a server's certificate fingerprint
openssl s_client -connect example.com:443 < /dev/null 2>/dev/null | openssl x509 -outform PEM > server.crt
openssl x509 -in server.crt -noout -fingerprint -sha256HTTP path-based routing is available when TLS is terminated (or for plain HTTP). A target with http_paths and/or default_http_action is treated as an HTTP target.
target:
allowlist: 0.0.0.0/0
http_paths:
/api/:
http_action:
type: forward
addresses:
- api-backend:8080
/static/:
http_action:
type: serve-directory
path: /var/www/static
default_http_action:
type: forward
addresses:
- default-backend:8080Timeouts belong to the HTTP target and apply to all of its routes. On the existing
H1-to-H1 session they are disabled when omitted or null; configured values must
be positive seconds. Mixed plaintext protocol detection and HTTP/2 paths use
finite defaults described below.
target:
allowlist: 127.0.0.1/32
http_timeouts:
request_header_timeout_secs: 15
response_header_timeout_secs: 30
keepalive_idle_timeout_secs: 60
default_http_action:
type: forward
location: 127.0.0.1:8080- The request-header deadline starts when HTTP handling begins for the first request, or when the first byte of a subsequent request is received. Partial header progress does not reset it.
- The response-header deadline starts after upstream request headers are sent and lasts until final response headers arrive. Informational responses do not reset it. It includes any concurrent request upload and backend processing; choose a value suitable for those workloads or leave it disabled.
- The keepalive idle deadline bounds waiting for the next request's first byte. Once data arrives, the request-header deadline applies instead.
Expiration closes the affected HTTP session. These are not body-transfer or whole-session deadlines; upgraded tunnels are not subject to them.
http_protocols is an ingress allowlist, default [http1, http2]. It must be a
nonempty list without duplicates; select [http1] or [http2] to disable the
other protocol. The old http2.prior_knowledge option is no longer accepted;
replace prior_knowledge: true with target-level http_protocols: [http2].
With both protocols enabled, plaintext connections are detected incrementally:
PRI selects H2; the first mismatch selects H1. All consumed bytes, including
any read-ahead from optional TLS detection, are replayed unchanged. The H2 library
validates the remaining preface. Invalid prefaces never fall back to H1. This
reserves PRI as an H2-only method on mixed plaintext listeners, while longer H1
methods such as PRINT still work. Detection runs only once per connection, not
for each keepalive request. Single-protocol listeners skip detection entirely.
Detection uses one absolute deadline from entry into HTTP dispatch, including
waiting for the first byte: http_timeouts.request_header_timeout_secs when set,
otherwise http2.header_timeout_secs (10 seconds). Byte progress does not extend
it. After selection, the existing handler's header/handshake deadlines apply.
TLS never sniffs HTTP bytes. Negotiated h2 selects H2; http/1.1, custom ALPN
names, and no ALPN select H1, provided that protocol is enabled. Omitted TLS ALPN
is derived as described above; explicit ALPN is authoritative. H2-only TLS
requires h2 negotiation. Generic TLS target matching still precedes HTTP
dispatch. Raw streams and TLS passthrough are unaffected. Upgrade-based h2c is
not supported.
A forward action independently selects upstream_protocol: http1 | http2
(default http1). H2 egress uses prior knowledge over clear TCP/Unix sockets or
requires negotiated h2 over TLS. Omit client ALPN to derive [h2], or configure
exactly [h2]; incompatible explicit lists are rejected. No automatic fallback.
target:
allowlist: 127.0.0.1/32
http_protocols: [http1, http2]
http2:
max_concurrent_streams: 64
max_connections: 256
max_backend_connections: 256
max_header_list_size: 65536
header_timeout_secs: 10
connect_timeout_secs: 10
body_progress_timeout_secs: 30
drain_timeout_secs: 5
default_http_action:
type: forward
upstream_protocol: http2
location:
address: backend.example:443
client_tls: trueProtocol policy and limit values shown are defaults. Enabling H2 validates local
response statuses and headers for H2 compatibility, even when an explicit ALPN
list restricts TLS to H1. Use [http1] for H1-only local response configurations.
max_concurrent_streams is per ingress H2 connection (maximum 4096).
max_connections bounds admitted H2 connections per compiled target/generation,
after TLS negotiation. max_backend_connections initializes two separate
target-global limits: active new-path exchanges and live new-path backend
transports, including dialing, idle and draining connections. Both connection
limits are capped at 65536. Each frontend additionally retains at most 16 H2
backend drivers across all actions. Admission refuses excess work rather than
creating an unbounded queue. H1-to-H1 connections retain their existing policy.
Header limits are 1024 through 1048576 decoded bytes, with a fixed 128-field cap.
Receive windows are 64 KiB per stream and 1 MiB per connection; forwarding chunks
are at most 16 KiB. Up to 16 informational responses are forwarded. Encoded
field-section bytes and pre-consumption response-section counts are also bounded.
Trailer fields are validated independently of initial headers and header patches.
Pseudoheaders and oversized trailer sections are rejected before conversion to
ordinary fields, for both ingress requests and upstream H2 responses, using the
pinned h2 fork fixes. Literal # fragments are rejected
before URI normalization on both H2 ingress and H1-to-H2 forwarding, so the backend
cannot receive a different path from the one used for routing. Encoded %23
remains permitted. These checks require no additional configuration flags.
For sections received over HTTP/2, max_header_list_size is an inclusive receive
policy, counting each field's name, value, and 32-byte overhead. Valid trailer
sections exactly at the limit are preserved in full; sections one byte over it
are rejected, not truncated. HTTP/1 fields on translation paths retain their
existing accounting: raw field-line length, including the colon and whitespace,
plus 32 bytes. The configured policy is distinct from the H2 decoder's additional
abuse limits, which can terminate the connection rather than only the stream.
All H2 timeouts must be positive and at most 86400 seconds. The connect deadline
includes action-slot waiting, DNS/TCP/Unix setup, TLS and H2 handshake/readiness.
The header deadline is absolute for incomplete frames/header blocks, not extended
by trickling bytes; expiry closes the connection. Existing explicit request-header
timeout overrides the ingress frame/header default. Generic pre-ALPN TLS handshake
policy remains controlled by server_tls.handshake_timeout_secs.
Default response-header deadline is 30 seconds after upload completion, plus a 30-second wait for Expect's 100/final response. Explicit response-header timeout instead remains absolute from response processing, including concurrent upload and informational responses. Body inactivity is shared across upload and response; meaningful progress in either direction keeps a duplex exchange alive. Frontend idle means no active streams, default 60 seconds, overridden by explicit keepalive idle timeout. Reload/idle retirement sends GOAWAY and bounds each owner's drain; top-level reload does not synchronously join established TCP sessions.
H2 preserves the logical request scheme and authority independently of backend TLS/SNI. Host patches update outgoing authority. Repeated headers, DATA and trailers are preserved; H1 ingress retains its existing UTF-8 restriction. Cross-version translation rejects unsupported transfer-coding chains and framing-changing patches, and uses H1 chunking when trailers may follow. H2-to-H1 backend leases are exclusive and not cached. H2 backend reuse is frontend/action-scoped, without cross-action coalescing or automatic retries; round-robin advances per physical connection, not per stream.
H2-to-H1 requests use one canonical Host, Connection: close, no incoming
Content-Length or TE field, and chunked framing whenever the upload is open.
DATA bytes cannot supply their own chunk boundaries. Failed uploads are not
completed with a synthesized terminal chunk, and their backend sockets are never
reused. If an early response is already in progress, upload failure half-closes
the backend write side so an EOF-dependent response can finish.
CONNECT, extended CONNECT/WebSocket, H3 and push are not supported on H2 paths.
H1 upgrades directed to an H2-only action receive 501. Local H2 responses must be
final statuses; HEAD/204/205/304 do not transmit payload. Custom reason phrases
are H1-only. close intentionally closes the entire H2 connection and its siblings;
ordinary local responses end only their stream.
Paths use raw longest-prefix matching, including the query string. Keys without
a trailing / are not exact matches. A trailing / provides a subtree boundary;
the bare mount also matches when it has no query string.
http_paths:
/api/: # matches /api, /api/, /api/users; not /apiculture or /api?x=1
/health: # matches /health, /health?x=1, and /healthcheckOnly the longest matching key is considered. If its required headers do not
match, default_http_action is used, not a shorter configured prefix.
Each path entry can require specific HTTP headers to match. Header keys are case-insensitive.
http_paths:
/:
- required_request_headers:
x-api-key: secret123
http_action:
type: forward
addresses: [backend:8080]
- required_request_headers:
host: app.example.com
http_action:
type: forward
addresses: [app-backend:8080]When multiple entries exist under the same path, they are evaluated in order. The first match wins.
The host key in required_request_headers supports the same wildcard patterns as sni_hostnames. Port suffixes in the Host header (e.g., example.com:8080) are automatically stripped before matching.
http_paths:
/:
# Exact match
- required_request_headers:
host: app.example.com
http_action:
type: forward
addresses: [app-backend:8080]
# Wildcard subdomains
- required_request_headers:
host: "*.api.example.com"
http_action:
type: forward
addresses: [api-backend:8080]
# Base domain + all subdomains
- required_request_headers:
host: ".example.com"
http_action:
type: forward
addresses: [default-backend:8080]
default_http_action:
type: serve-message
status_code: 404When possible, prefer SNI-level routing (sni_hostnames) over Host header matching. SNI routing operates at the TLS layer before HTTP parsing, avoiding the overhead of decryption and request parsing. Host header routing is useful for plain HTTP, or when multiple virtual hosts share the same TLS certificate.
Forwards the HTTP request to one or more backend servers. Supports connection reuse and header manipulation; WebSocket upgrades remain H1-to-H1 only.
http_action:
type: forward
upstream_protocol: http1 # http1 (default) or http2
locations: [string | object] # Backend address(es) -- round-robin if multiple
replacement_path: string # Rewrite the request path
request_header_patch: # Modify request headers
default_headers: # Add if not already present
X-Forwarded-For: "..."
overwrite_headers: # Set unconditionally
Host: backend.internal
remove_headers: # Remove
- X-Debug
response_header_patch: # Modify response headers (same structure)
overwrite_headers:
X-Served-By: tobaru
request_id_header_name: string # Add a unique request ID header to the request
response_id_header_name: string # Add a unique request ID header to the responseThe address, addresses, and location field names are accepted as aliases for locations.
Backend reuse is local to a client connection and the selected HTTP action. Changing Host/header rules to select another action opens a different backend connection, even when the path prefix is unchanged. Round-robin selection occurs when a backend connection is opened, not on every request.
Header names in patches are case-insensitive. Repeated received fields retain their value order; an overwrite replaces all values for that name. Patches must not change the received body's framing (for example, changing Content-Length or removing chunked Transfer-Encoding). Such requests/responses are rejected and the connection is closed; body bytes are not rewritten to match a patched length or transfer-coding stack. Transfer-Encoding fields with parameters must remain unchanged; their quoted values are not normalized. Patches also cannot manufacture or relabel a successful protocol upgrade: the original and forwarded handshakes must agree, including the case-sensitive protocol version when present.
Forwarding supports HEAD/bodyless responses, informational responses including 100 Continue and 103 Early Hints, and close-delimited response bodies. An early final response during an unfinished upload is forwarded and both connections are closed rather than reused on the existing H1-to-H1 path. New H2 paths continue upload alongside successful response DATA for duplex streaming. H1 backend error responses stop upload through write-side shutdown while the response drains. See HTTP/2 Policy and Limits for protocol selection, cancellation, and unsupported features. CONNECT remains unsupported.
Returns a static HTTP response.
http_action:
type: serve-message
status_code: 200 # HTTP status code (required)
status_message: "OK" # Custom status text (optional)
content: "Hello, world!" # Response body (default: "")
response_headers: # Custom response headers
Content-Type: "text/plain"
response_id_header_name: string # Add a unique request ID headerServes static files from a directory. MIME types are automatically detected.
After route selection, the query is removed and the file path is percent-decoded.
Decoded parent-directory segments and NUL bytes are rejected; names such as
report..final.txt are allowed. Both the requested file and a directory's
index.html must resolve inside the canonical serving root. Keep serving trees
immutable or trusted: canonicalization does not eliminate concurrent symlink
replacement races.
http_action:
type: serve-directory
path: /var/www/static # Directory path (required)
response_headers: # Custom response headers
Cache-Control: "max-age=3600"
response_id_header_name: string # Add a unique request ID headerImmediately closes the connection.
http_action:
type: close
# Shorthand string form
http_action: closeUDP forwarding with round-robin load balancing and stateful association tracking.
The optional listener-level udp_max_associations caps the total across all
targets. Omitted or null means unlimited; configured values must be positive
integers. At capacity, packets for new client addresses are dropped, while
existing associations continue forwarding. Idle cleanup frees capacity.
- address: 0.0.0.0:53
transport: udp
udp_max_associations: 4096 # Optional, shared by all targets on this listener
target:
addresses: [string] # Backend address(es) -- round-robin
allowlist: string | [string] # IP masks or group names
association_timeout_secs: 200 # Timeout for UDP associations (default: 200, min: 5)The address, location, and locations field names are accepted as aliases for addresses.
Named, reusable sets of IP ranges. Define groups before referencing them in server configs (ordering within the file matters).
# Define groups
- group: internal
ip_masks:
- 192.168.0.0/16
- 10.0.0.0/8
- 172.16.0.0/12
- group: trusted
ip_masks:
- 1.2.3.4
- 5.6.7.8
- internal # Groups can reference other groups
# Use in server config
- address: 0.0.0.0:8080
transport: tcp
target:
location: backend:8080
allowlist:
- internal
- trustedThe built-in group all is equivalent to ::/0, covering both IPv4 and IPv6.
Use 0.0.0.0/0 to allow only IPv4.
ip_mask (singular) is accepted as an alias for ip_masks.
For simple forwarding, configs can be specified as URLs on the command line instead of YAML files.
# TCP forwarding
tobaru tcp://127.0.0.1:8080?target=192.168.1.10:80
# Multiple targets (round-robin)
tobaru "tcp://127.0.0.1:8080?target=backend1:80&target=backend2:80"
# Forward to UNIX socket
tobaru tcp://127.0.0.1:8080?target-path=/run/app.sock
# UDP forwarding
tobaru udp://127.0.0.1:53?target=8.8.8.8:53&target=8.8.4.4:53URL configs always use allowlist: 0.0.0.0/0 (allow all). For access control, use a YAML config file.
| Scheme | Parameter | Description |
|---|---|---|
tcp |
target or target-address |
Backend TCP address |
tcp |
target-path |
Backend UNIX socket path |
udp |
target or target-address |
Backend UDP address |
tobaru [OPTIONS] <CONFIG PATH or CONFIG URL> [CONFIG PATH or CONFIG URL] ...
OPTIONS:
-t, --threads NUM Worker threads (default: auto-detected from CPU count)
--dry-run Validate config, routing, and TLS files without binding listeners
--clear-iptables-all Clear all tobaru iptables rules and exit
--clear-iptables-matching Clear iptables rules for specified config files and exit
-h, --help Show help
Configuration file changes, including atomic replacements, are debounced for
three seconds while current listeners continue serving. Invalid replacement
configuration retains the last-good listeners. Valid replacements stop and await
old listener tasks before rebinding, so reload is not gapless. Existing TCP
sessions keep their old configuration until completion; UDP associations restart.
Unexpected listener errors or panics exit the process nonzero. Bind and firewall
failures also exit nonzero, including during reload. --dry-run does not verify
that ports can be bound or firewall commands can succeed.
# Run with a single config file
tobaru config.yaml
# Run with multiple config files (merged together)
tobaru servers.yaml ip_groups.yaml
# Simple TCP forwarding via URL
tobaru tcp://127.0.0.1:8080?target=192.168.1.10:80
# Custom thread count
tobaru --threads 4 config.yaml
# Clear iptables rules
sudo tobaru --clear-iptables-matching config.yaml
sudo tobaru --clear-iptables-allConfiguration files are automatically watched for changes and reloaded without restart.
These fields are accepted with a deprecation warning and will be removed in a future version.
| Deprecated | Replacement |
|---|---|
serverTls |
server_tls |
bindAddress |
address |
addresses (in targets) |
locations |
optional (in server_tls) |
true auto-migrates to TLS and non-TLS targets; raw forwarding also accepts unmatched/malformed TLS. See migration notes. |