Configuration reference
Every setting the configuration file understands, what it accepts, and the value it takes when you leave it out.
An INI-style file of named sections. The configuration guide covers how the pieces fit together and is the better place to start; this page is the exhaustive list to come back to.
A setting that is present in the file can be overridden by an environment variable, which is what makes a container image configurable without rewriting the file inside it. The name is SSLB3_, then the section and the key joined by an underscore and upper-cased — SSLB3_SYSTEM_MAXCONNECTIONS, SSLB3_THREAT_MODE. The key has to be in the file already: an override replaces a value, it cannot add a setting the file does not mention. More on overriding.
[System]
Process-wide settings. One of these sections.
[Log]
Where records go and how much is written. Access logging is off globally and turned on per route with the enable-access-log annotation, or per listener in the file.
[Listener-name]
One section per bound socket, named after the hyphen — [Listener-https] is a listener called https. As many as you need.
listeners.conf
[Listener-http] ListenPort = 8080 Serverfarm = web RedirectToTLS = true [Listener-https] ListenPort = 8443 Protocol = tls CertificateFile = /etc/sslb3/tls/fullchain.pem PrivateKeyFile = /etc/sslb3/tls/privkey.pem Serverfarm = web Layer7Module = http [Listener-postgres] ListenPort = 15432 Serverfarm = postgres AllowSourceRange = 10.0.0.0/8
HTTP/3 behind a load balancer
The settings, in short. HTTP/3 behind an AWS network load balancer is the whole deployment: why QUIC needs a load balancer of its own, what the connection ID carries, and what the kernel-assisted steering costs.
A QUIC connection is identified by its connection ID rather than by the address it came from — that is what lets a client change network and keep it. A load balancer in front has the same problem in reverse: the address it hashed on has changed, so it has to read the connection ID to find the instance holding the connection. It can only do that if the instance puts something recognisable in there, which is what ConnectionIdFormat = quic-lb does.
h3-behind-an-nlb.conf
[Listener-h3] Protocol = quic ListenPort = 8443 AdvertisePort = 443 # HTTP/3 lives on its own name here, because an AWS NLB will not carry a QUIC # listener if it is dualstack or has a security group. AdvertiseHost = h3.example.com AdvertiseMaxAge = 300 # 1 byte of config rotation, the 8-byte server ID, then a nonce: 17 in total, # which is the only length an NLB accepts. ConnectionIdFormat = quic-lb ServerIdEnvironment = AWS_LBC_QUIC_SERVER_ID ServerIdEncoding = base64 # Linux: steer datagrams to the worker that owns the connection, so a client # that changes network keeps it instead of reconnecting. ConnectionMigration = yes
The two halves are separate, and either is useful alone. ConnectionIdFormat settles which instance a moved client reaches, and is what a load balancer in front needs; ConnectionMigration settles which worker inside that instance, and is what the sockets sharing a port need. Turning on only the second one still breaks the moment there is more than one replica, which is why it is off by default.
And when it is off, plan for the client that moves anyway — a laptop changing network, a NAT rebinding. It arrives at a worker or an instance holding no state for its connection, and the only thing an endpoint with no state can send is QUIC's stateless reset. A client believes one only if its token matches the one issued with the connection, and that token is an HMAC of the connection ID under StatelessResetKey — so instances can speak for each other's connections exactly when they share it. Without a shared key every reset is rejected as unauthenticated and the client waits out the idle timeout instead, which is a minute of a dead connection before it falls back to TCP. It fixes nothing about routing; it turns a stall into an immediate fallback.
Bring a setup like this up unadvertised. Advertise = no runs the listener while no response carries Alt-Svc, so no browser can find it — and a client aimed at an address directly needs no invitation, so curl --http3-only --connect-to origin:443:h3-host:443 https://origin/ still exercises the whole path, load balancer included. Advertise once that works. The same setting is the way to stop HTTP/3 afterwards without dropping the connections already on it, which removing the listener would.
[Serverfarm]
The defaults every farm inherits. One of these sections, and every setting in it can be overridden per farm below.
[Serverfarm-name]
One per farm. Anything left out here falls back to [Serverfarm] above — that is the whole point of the split, so a farm section usually holds two or three lines rather than a copy of the defaults.
The Match* settings are the exception: they exist only here, because they are what distinguishes one farm from another. They are how routing happens natively in Rust rather than through a script.
farms.conf
[Serverfarm] Balancer = round-robin HealthCheck = true ConnectTimeout = 10 [Serverfarm-web] MatchHost = shop.example.com, www.shop.example.com Layer7Module = http [Serverfarm-api] MatchHost = shop.example.com MatchPath = /api/ Balancer = least-connections Layer7Module = http [Serverfarm-postgres] Balancer = least-connections HealthCheckType = tcp
Both farms above answer for the same host; the one with MatchPath is more specific, so /api/orders goes to the API farm and everything else to the web farm. Host and path are combined by AND.
[RealServer-n]
A backend. One section each, numbered — [RealServer-1], [RealServer-2] — and assigned to a farm by name. The suffix has to be a number: it keeps the order of the sections meaningful, and a section named [RealServer-web1] stops the configuration loading rather than being quietly skipped. Two sections with the same number are refused too.
backends.conf
[RealServer-1] IPAddress = 10.0.1.11 Port = 8080 Serverfarm = web Weight = 2 [RealServer-2] IPAddress = 10.0.1.12 Port = 8080 Serverfarm = web # Drained: configured, but taking no traffic. Weight zero is honoured # by every balancer, and such a backend is not handed to the kernel # fast path either. [RealServer-3] IPAddress = 10.0.1.13 Port = 8080 Serverfarm = web Weight = 0
Health checks
A health check belongs to a real server. Every setting below can be written in three places, and the most specific one wins — setting by setting, not section by section:
So a pool whose members are all alike says it once on the farm, and a member that differs — a different port, a different path — overrides just that and keeps the rest. Checks run on their own schedule, never on the request path, so a request never waits for one. The Unset column below is what applies when no level sets a value.
health.conf
[Serverfarm] # Every farm checks /health over http unless it says otherwise. HealthCheckType = http HealthCheckPath = /health [Serverfarm-api] # This farm's application answers its health path with 401 # when unauthenticated, and that is healthy. HealthCheckPath = /actuator/health/liveness HealthCheckStatus = 200,401 # Two members of one farm on different ports. Each is checked on # its own port, with nothing extra configured. [RealServer-11] IPAddress = 127.0.0.1 Port = 8080 Serverfarm = api [RealServer-12] IPAddress = 127.0.0.1 Port = 8081 Serverfarm = api # A member whose health endpoint is on a management port. [RealServer-13] IPAddress = 10.0.1.13 Port = 8080 Serverfarm = api HealthCheckPort = 9090
[AdminAPI]
The configuration API. TLS-only and authenticated, always — it changes configuration, so there is no plaintext mode and no anonymous one. This is what the ingress controller talks to.
[Threat]
Intrusion detection and prevention. Every rate below is a count over Window seconds from a single source address, and every one of them is zero by default, meaning "do not watch this" — a rate that is abusive for one deployment is ordinary for the next, so there is no default I could pick that would be right for yours.
threat.conf
[Threat] # Count and report, refuse nobody. Where every rollout should start. Mode = detect Window = 10 BlockDuration = 300 # One request for /.env or /.git/config has no innocent reading, # so one is the threshold. ProbeRate = 1 # A browser does not send malformed HTTP. Low threshold, high confidence. MalformedRate = 5 # Path enumeration and credential stuffing. NotFoundRate = 40 DeniedRate = 20
[LUA]
The scripts, and the limits they run under. Every hook is optional and unset by default — Lua is available, not required, and a deployment that never names a script never loads one.
What a script can call, and what each hook receives, is the Lua interface reference.
[ApiGateway]
Read by api_gateway.lua, and only when it is the request script — the section means nothing to any other role. Every setting is optional, and a value the gateway does not understand stops it starting, with the setting named. How they fit together is on the API gateway page.
[Prometheus]
The metrics endpoint: its own listener, off until you turn it on. See metrics and the statistics page.
[Stats]
The live statistics page, off on the same terms and for the same reason.
observability.conf
[Prometheus] Enabled = true ListenPort = 9101 Path = /metrics [Stats] Enabled = true ListenPort = 9102 Path = / RefreshInterval = 2
Both bind every interface when enabled, because a scraper or a browser is on another machine by definition and a loopback default would start, log that it is listening, and never be reachable. Both are unauthenticated and describe your whole deployment, so put them somewhere deliberate.
[FastPath]
Whether the kernel may forward eligible layer 4 services instead of SSLB3 copying their bytes. How eligibility is decided is its own page.
fastpath.conf
[FastPath] # auto: hand over what can be handed over, and a service staying # behind is ordinary rather than a fault. Mode = auto Backend = auto # Set false only where the network can route replies back without it -- # DSR-style. Leaving it on is correct nearly everywhere. SourceNat = true
Kubernetes deployments set most of this through the Helm chart rather than by editing a file. The install page covers the values, and the annotation reference covers what an individual Ingress can override.