service
service is Turna's reverse proxy middleware. It can proxy to one or more upstream servers with weighted round-robin, random or least-connections balancing, health checks, session affinity and traffic mirroring, or select upstreams by request path prefix.
server:
http:
middlewares:
backend:
service:
insecure_skip_verify: false
pass_host_header: true
proxy: http://proxy.internal:3128
loadbalancer:
servers:
- url: http://localhost:3000
- url: http://localhost:3001Fields
| Field | Default | Description |
|---|---|---|
insecure_skip_verify | false | Skip upstream TLS certificate verification. Also applies to active health checks and mirror requests. |
h2c | false | Use HTTP/2 without TLS for http:// upstreams, required for plain text gRPC servers. |
pass_host_header | When explicitly false, clear r.Host before proxying. | |
proxy | Optional HTTP or HTTPS forward proxy URL used for upstream requests. Credentials may be included in the URL. | |
loadbalancer.servers | Upstream list; each entry has url (http://, https:// or unix:///path.sock) and optional weight (default 1). | |
loadbalancer.strategy | round_robin | round_robin (smooth weighted), random (weighted) or least_conn (active requests / weight). |
loadbalancer.sticky.cookie | Session affinity cookie, see below. | |
loadbalancer.health_check | Active health check, see below. | |
loadbalancer.passive_health_check | Passive health check, see below. | |
retry | 0 | Retries on other upstreams when an upstream is unreachable. |
mirror | Shadow traffic, see below. | |
prefixbalancer.prefixes | Path-prefix-specific upstream lists. prefix accepts a single string or a list of prefixes. | |
prefixbalancer.default_servers | Default upstreams when no prefix matches. | |
timeouts.dial | 30s | Timeout of establishing the upstream TCP connection. |
timeouts.tls_handshake | 10s | Timeout of the upstream TLS handshake. |
timeouts.response_header | Time to wait for the upstream response headers after the request is sent. Empty means no limit. Exceeding it gives 502 and counts as an upstream failure for retry. | |
timeouts.idle_conn | 90s | Idle keep-alive connections are closed after this. |
timeouts limit the upstream connection only; reading a long response body is not limited. To limit the whole request, put timeout in front of service.
WebSockets and streaming
service proxies WebSocket (Connection: Upgrade) and streaming responses (SSE) transparently. WebSocket upgrades are forwarded over the same transport as regular requests, so wss:///https:// upstreams work, honoring insecure_skip_verify. Path rewrites and the pass_host_header setting apply to upgrades too.
WebSocket upgrades use HTTP/1.1 to the upstream; backends that only speak HTTP/2 cannot accept them.
Unix sockets
An upstream written as unix:///path/to.sock is reached over that unix socket and spoken to with plain HTTP. The request path is sent unchanged, the socket path is not part of it.
service:
loadbalancer:
servers:
- url: unix:///var/run/docker.sockHealth checks, retries, h2c and mirror servers work the same way, and proxy is not used for socket upstreams. The socket must be reachable by the Turna process, so mount it into the container. See the Docker socket proxy for a complete example.
Prefix Balancer
service:
prefixbalancer:
prefixes:
- prefix: /api
servers:
- url: http://api:3000
- prefix:
- /admin
- /internal
servers:
- url: http://admin:3000
default_servers:
- url: http://web:3000If the prefix balancer is configured, it is used instead of the plain loadbalancer.
prefix can be a single string or a list; a group matches when the request path starts with any of its prefixes. Groups are checked in order and the first match wins.
The loadbalancer options (strategy, sticky, health_check, passive_health_check) also apply to every prefix group and the default servers. The same upstream URL used in several groups shares its health and connection state.
Load Balancing
service:
retry: 1
loadbalancer:
strategy: least_conn
servers:
- url: http://app-1:3000
weight: 3
- url: http://app-2:3000Unhealthy or ejected upstreams are skipped. When every upstream is unavailable, turna still tries them (fail open) instead of rejecting the request.
Sticky Sessions
loadbalancer:
sticky:
cookie:
name: turna_lb
max_age: 1h
secure: true| Field | Default | Description |
|---|---|---|
name | turna_lb | Cookie name. Prefix groups get a _<index> suffix. |
path | / | Cookie path. |
domain | Cookie domain. | |
max_age | session | Cookie lifetime. |
secure | false | Secure flag. |
http_only | true | HttpOnly flag. |
same_site | lax | lax, strict or none. |
The cookie holds a hash of the upstream URL. If that upstream is unhealthy, another one is picked and the cookie is updated.
Active Health Check
loadbalancer:
health_check:
path: /healthz
interval: 10s
timeout: 2s
status: "200-299"
unhealthy_threshold: 2| Field | Default | Description |
|---|---|---|
path | / | Probe path, may include a query string. |
method | GET | Probe method. |
host | Host header of the probe. | |
headers | Extra probe headers. | |
interval | 10s | Time between probes. |
timeout | 5s | Probe timeout. |
status | 200-399 | Accepted status codes, e.g. 200,204,300-399. |
healthy_threshold | 1 | Consecutive successes to mark healthy. |
unhealthy_threshold | 1 | Consecutive failures to mark unhealthy. |
Passive Health Check
loadbalancer:
passive_health_check:
max_fails: 3
fail_timeout: 30s
fail_statuses: [502, 503, 504]An upstream is ejected for fail_timeout after max_fails failures within fail_timeout. Connection errors always count as failures; fail_statuses adds response codes.
Mirror
Mirror sends a copy of requests to shadow upstreams. Mirror responses are discarded and never change the client response.
service:
loadbalancer:
servers:
- url: http://app-v1:3000
mirror:
servers:
- url: http://app-v2:3000
percent: 10| Field | Default | Description |
|---|---|---|
servers[].url | Shadow upstream. | |
servers[].percent | 100 | Percent of requests to mirror. |
max_body_size | 1048576 | Requests with larger bodies are not mirrored. |
timeout | 10s | Mirror request timeout. |
max_in_flight | 100 | Concurrent mirror requests; extra ones are dropped. |
WebSocket upgrades are not mirrored.