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.

Every setting here can also be given as an environment variable, which is what makes a container image configurable without rewriting the file inside it. The name is the section and the key joined by an underscore and upper-cased — SSLB_SYSTEM_MAXCONNECTIONS, SSLB_THREAT_MODE.

[System]

Process-wide settings. One of these sections.

SettingAcceptsUnsetWhat it does

User

a user name

empty

Drop to this user after binding. Empty stays as started.

Group

a group name

empty

Drop to this group after binding.

Daemon

true, false

false

Fork into the background. Leave off under systemd, which wants the process in the foreground.

PidFile

a path

empty

Where to write the process id. Empty writes none.

CommunicationModule

tcp

tcp

The transport module used for connections.

Worker

threadpool

threadpool

The worker model.

Threads

integer

0

Worker threads. Zero means one per available core.

StartSSLB

true, false

true

Whether to start serving at all. Off is for validating a configuration without binding anything.

MaxConnections

integer

0

Concurrent connections across the process. Zero is unlimited.

MaxAuthConcurrency

integer

0

How many basic-auth password verifications may run at once; zero picks half the cores. Verifying is meant to be expensive and a wrong password is never cached, so without a cap a handful of guesses can starve the proxying. Requests past the cap get 503, not 401 — busy is not the same as wrong.

WorkerBacklog

integer

128

Accept queue depth per worker.

ShutdownTimeout

seconds

30

How long established clients get to finish once shutdown starts.

KeepHeaderNameCase

true, false

true

Forward HTTP/1.1 header names to the backend as the client wrote them. The parser lower-cases everything internally, which is correct by the RFC and wrong for a backend comparing names literally. HTTP/1.1 only — h2 and h3 define field names as lower case on the wire. What a Lua script sees is unaffected.

RouteHeader

true, false

true

Add X-SSLB3-Route to every response served by the HTTP module, saying which path decided where the request went: native, lua or default. On by default because a route quietly falling off the native path is otherwise invisible. Turn it off where telling clients how the balancer is configured is unwanted.

MaxAffinitySessions

integer

100000

How many cookie affinity sessions may be held at once across every farm. A client that discards cookies mints a new session per request, so the store needs a ceiling as well as an expiry. Reaching it evicts the least recently used.

SessionStoreFile

a path

empty

Where to keep affinity sessions across a restart. Empty disables it. Only a clean shutdown writes the file — a SIGKILL leaves the last one and clients are redistributed, deliberately, so that affinity costs no I/O while serving.

[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.

SettingAcceptsUnsetWhat it does

LogName

a name

SSLB

The name records are written under.

DebugLevel

0–5

3

How much is written. Higher is more.

LogDir

a path

empty

Directory for log files. Empty logs to standard output, which is what a container wants.

UseSyslog

true, false

false

Send records to syslog instead.

LogSocket

a path

empty

The syslog socket, when the default is not right.

RemoteLogging

true, false

false

Send records to a remote collector.

SyncLogfile

true, false

false

Flush every record to disk as it is written. Safer across a crash, slower in the ordinary case.

AccessLog

true, false

false

Write an access record per request. Off globally; also settable per route by annotation.

AccessLogFormat

text, json, combined

text

How a record is rendered. combined is the Apache/nginx format most log tooling already parses.

AccessLogLevel

0–5

3

The level access records are emitted at. Matches the default DebugLevel, so turning access logging on is enough to see it.

[Listener-name]

One section per bound socket, named after the hyphen — [Listener-https] is a listener called https. As many as you need.

SettingAcceptsUnsetWhat it does

ListenPort

1–65535

0

The port to bind. Required — a listener without one binds nothing.

BindAddress

an address

::

The address to bind. The default is every interface, both families.

BindIPv4

true, false

true

Whether to bind the IPv4 family.

BindIPv6

true, false

true

Whether to bind the IPv6 family.

BindAddressIPv4

an address

empty

A distinct IPv4 address, where one socket per family is wanted.

BindAddressIPv6

an address

empty

The same for IPv6.

Protocol

tcp, tls, udp, uds, quic

tcp

What this listener speaks. tls terminates; quic serves HTTP/3; uds listens on a Unix socket.

Serverfarm

a farm name

unset

The farm this listener sends to, when it is not decided by matching.

Layer7Module

http, lua

unset

What parses the traffic. Unset copies bytes without parsing, which is what makes a service eligible for the kernel fast path.

CertificateFile

a path

empty

The certificate chain, for a TLS or QUIC listener.

PrivateKeyFile

a path

empty

The matching private key.

PrivateKeyPassword

a password

unset

For an encrypted private key.

RedirectToTLS

true, false

false

Answer everything on this listener with a redirect to https. The usual shape of a plaintext listener that exists only to send people to the encrypted one.

AdvertisePort

1–65535

0

The port named in Alt-Svc for HTTP/3, when it differs from the bound one.

HandshakeTimeout

seconds

10

How long a TLS handshake may take before it is abandoned.

AllowSourceRange

CIDRs, comma separated

empty

Addresses admitted. Empty admits anything not denied.

DenySourceRange

CIDRs, comma separated

empty

Addresses refused. Resolved against the allow list by specificity, with an exact tie going to deny.

ProxyProtocol

v1, v2

empty

Expect a PROXY protocol header, so the real client address survives a load balancer in front.

ProxyProtocolFrom

CIDRs

empty

Which senders may speak it. A PROXY header from anywhere else is a client claiming to be someone.

TrustXForwardedForFrom

CIDRs

empty

Which senders may set X-Forwarded-For and be believed. Same reasoning.

MaxDatagram

bytes

4096

Largest UDP datagram accepted.

MaxSessions

integer

8192

Concurrent UDP sessions tracked for this listener.

Socket

a path

empty

Serve on a Unix socket instead of a port.

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

[Serverfarm]

The defaults every farm inherits. One of these sections, and every setting in it can be overridden per farm below.

SettingAcceptsUnsetWhat it does

Balancer

round-robin, weighted, least-connections, lua

round-robin

How a backend is chosen. There are four and no others; an unknown name fails the parse.

HealthCheck

true, false

true

Whether farms probe their backends.

Layer7Module

http, lua

empty

What parses the traffic for farms that do not say.

Selector

default, match, lua

default

How a farm is chosen for a request. match is the native host/path routing; lua hands the decision to a script.

ThreatDetection

off, detect, block

empty

What the intrusion detector does. Empty follows the balancer-wide [Threat] Mode.

ImpersonationEnforcement

true, false

true

Whether the impersonation signal refuses as well as reports.

ConnectTimeout

seconds

10

How long to wait dialling a backend.

IdleTimeout

seconds

0

How long a connection may be silent. Zero disables.

MaxConnectionDuration

seconds

0

How long a connection may last at all. Zero disables. The one worth setting: an idle timeout does nothing against a connection trickling a byte to look busy.

MaxIdleConnections

integer

256

Keep-alive connections held open to backends.

FailedBackendRetries

integer

3

How many times a failed backend is retried before it is taken out.

FailedBackendRetryInterval

seconds

1

How long between those retries.

SessionAffinity

none, cookie

none

Whether a returning client goes back to the backend that served it.

SessionCookieName

a cookie name

SSLB3_AFFINITY

The affinity cookie.

SessionTimeout

seconds

3600

How long a session survives unused.

ForwardedHeaders

true, false

true

Add X-Forwarded-For and its companions to backend requests.

RealServers

names, comma separated

unset

Backends listed on the farm instead of each naming its farm.

[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.

SettingAcceptsUnsetWhat it does

MatchHost

host names, comma separated

empty

Hosts this farm answers for. The core of native routing.

MatchPath

path prefixes, comma separated

empty

Path prefixes, combined with MatchHost by AND. Unset matches any path for those hosts.

MatchScheme

http, https

empty

Restrict to requests that arrived one way. Matches how the request reached this balancer, not X-Forwarded-Proto — a header a client can set is not something to route on.

MatchListener

listener names

empty

Only requests arriving on these listeners.

MatchSNI

server names

empty

Only connections whose TLS handshake asked for these names.

MatchAddress

local addresses

empty

Only requests that arrived on these local addresses. How one balancer serves a different pool per address, as a web server does.

HealthCheckType

tcp, http, https, smtp, lua, noop

inherits

The probe for this farm.

Balancer

as above

inherits

Overrides [Serverfarm] for this farm.

HealthCheck

true, false

inherits

Overrides [Serverfarm] for this farm.

Layer7Module

http, lua

inherits

Overrides [Serverfarm] for this farm.

ThreatDetection

off, detect, block

inherits

Overrides [Serverfarm] for this farm.

ImpersonationEnforcement

true, false

inherits

Overrides [Serverfarm] for this farm.

ConnectTimeout

seconds

inherits

Overrides [Serverfarm] for this farm.

IdleTimeout

seconds

inherits

Overrides [Serverfarm] for this farm.

MaxConnectionDuration

seconds

inherits

Overrides [Serverfarm] for this farm.

SessionAffinity

none, cookie

inherits

Overrides [Serverfarm] for this farm.

SessionCookieName

a cookie name

inherits

Overrides [Serverfarm] for this farm.

SessionTimeout

seconds

inherits

Overrides [Serverfarm] for this farm.

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-name]

A backend. One section each, assigned to a farm by name.

SettingAcceptsUnsetWhat it does

IPAddress

an address

empty

Where the backend is. Required.

Port

1–65535

0

Its port. Required.

Serverfarm

a farm name

default

Which farm it belongs to. A backend belongs to exactly one.

Protocol

tcp, tls

tcp

How to reach it. tls speaks TLS to the backend. Only these two — a UDP listener reaches its backends without one being declared here.

Weight

integer

1

Relative share under the weighted balancer, and only under that one. Zero drains a backend without removing it — but round-robin and least-connections do not read weight at all, so under those a zero-weight backend still takes its turn. Set Balancer = weighted on the farm if you are draining this way.

HealthCheck

true, false

true

Whether this backend is probed.

SNI

a server name

unset

The name presented in the handshake to this backend.

VerifyCertificate

true, false

true

Check the backend's certificate. Set false for an internal certificate nothing trusts.

CertificateFile

a path

unset

A client certificate to present to this backend.

backends.conf

[RealServer-web1]
IPAddress = 10.0.1.11
Port = 8080
Serverfarm = web
Weight = 2

[RealServer-web2]
IPAddress = 10.0.1.12
Port = 8080
Serverfarm = web

# Drained: configured, but taking no traffic. Only the weighted
# balancer reads Weight, so this drains under Balancer = weighted
# and is ignored under round-robin or least-connections.
[RealServer-web3]
IPAddress = 10.0.1.13
Port = 8080
Serverfarm = web
Weight = 0

[HealthCheck]

How a probe is performed, when a farm has health checking on. Checks run on their own schedule rather than on the request path, so a request never waits for one.

SettingAcceptsUnsetWhat it does

Type

tcp, http, https, smtp, lua, noop

tcp

What kind of probe. noop marks backends up without testing them.

CheckPeriod

seconds

10

How often each backend is probed.

CheckURL

a path

/

The path requested, for an http or https check.

HostHeader

a host name

empty

The Host header sent with that request.

ResponseTimeout

seconds

3

How long a probe may take before it counts as failed.

FailTolerance

integer

3

Consecutive failures before a backend is taken out of rotation.

health.conf

[HealthCheck]
Type = http
CheckURL = /healthz
CheckPeriod = 10
ResponseTimeout = 3
FailTolerance = 3

[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.

SettingAcceptsUnsetWhat it does

Enabled

true, false

false

Whether the API listens. The ingress controller needs it.

BindAddress

an address

127.0.0.1

Loopback by default, unlike the metrics and stats listeners — this one changes configuration, so it should be reached deliberately.

ListenPort

1–65535

6443

The port.

AuthType

basic, apikey

basic

How callers authenticate. There is no anonymous mode.

ApiKey

a key

empty

The key, for apikey auth. Preferred over a username and password.

Username

a name

sslb

For basic auth.

Password

a password

empty

For basic auth.

CertificateFile

a path

empty

The API is TLS-only, so this is required for it to start.

PrivateKeyFile

a path

empty

The matching key.

PrivateKeyPassword

a password

unset

For an encrypted key.

BindIPv4 / BindIPv6

true, false

true

Which families to bind.

[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.

SettingAcceptsUnsetWhat it does

Mode

off, detect, block

off

block refuses, detect counts and reports without refusing anyone, off does not look.

Window

seconds

10

The window every rate below is counted over.

Interval

milliseconds

500

How often detectors flush and the aggregator decides — also the worst-case delay between crossing a threshold and being refused.

BlockDuration

seconds

300

How long a source stays blocked, refreshed while it keeps offending. It has to expire: a permanent list eventually blackholes a whole CGNAT range on the strength of one bad client behind it.

MaxTrackedSources

integer

65536

Sources counted at once. Beyond this, addresses not already tracked are ignored.

ConnectionRate

integer

0

Connections per window from one address. The only signal needing no request, so the one that works in front of a protocol SSLB3 does not parse.

RequestRate

integer

0

Requests per window from one address. Needs the HTTP module.

NotFoundRate

integer

0

404s per window. A source producing these at a rate is enumerating paths.

DeniedRate

integer

0

401s and 403s per window. At a rate, that is credential stuffing.

MalformedRate

integer

0

Requests per window that were not valid HTTP. The strongest signal and the cheapest, because the parser has already judged. A browser does not send malformed HTTP. Worth a far lower threshold than the rest.

ProbeRate

integer

0

Requests per window for something no legitimate client asks a frontend for. /.env is a request for your credentials and /.git/config for your source history; neither has an innocent reading, so 1 is the sensible setting. Only the path is inspected, never a payload. .well-known is excluded, ACME living there.

ImpersonationRate

integer

0

Requests per window from a client whose TLS handshake or headers contradict the browser its User-Agent claims to be. A rate rather than one occurrence, deliberately: corporate TLS inspection produces exactly this disagreement innocently. A few is a proxy; a stream is a scanner wearing a browser’s name.

HoneypotRate

integer

0

Requests per window for HoneypotPath. One is conclusive, so 1 is the sensible setting.

HoneypotPath

a path

empty

A path nothing legitimate would ever request.

BlocklistSources

true, false

true

Whether a source that crosses a threshold is blocked as a source. Turn off where traffic arrives SNATed and an address stands for a crowd.

EnforceAtConnection

true, false

true

Whether a blocked source is refused at accept rather than per request. Same caveat behind a SNATing ingress.

ExemptSourceRange

CIDRs

empty

Addresses never blocked — monitoring, health checkers, your own offices.

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.

SettingAcceptsUnsetWhat it does

ScriptDirectory

a path

etc/scripts

Where scripts are loaded from.

Request

a script name

unset

Runs per request, for per-request logic.

Session

a script name

unset

Owns a connection outright, for a protocol you want to speak yourself.

Selector

a script name

unset

Chooses the farm for a request, when Selector = lua.

Balancer

a script name

unset

Chooses the backend, when Balancer = lua.

HealthChecker

a script name

unset

Performs the probe, when the check type is lua.

Startup

a script name

unset

Runs once as the balancer starts.

Shutdown

a script name

unset

Runs once as it stops.

InstructionLimit

integer

10000000

Instructions a script may execute before it is stopped — the guard against a script that never returns.

MemoryLimit

bytes

0

Memory ceiling for the Lua state. Zero is unlimited.

Sandbox

true, false

false

Restrict what scripts can reach.

UserDataMaxEntries

integer

100000

Entries in the shared store scripts use to keep state between requests.

UserDataTTL

seconds

0

How long an entry survives. Zero never expires.

[Prometheus]

The metrics endpoint: its own listener, off until you turn it on. See metrics and the statistics page.

SettingAcceptsUnsetWhat it does

Enabled

true, false

false

Whether the endpoint listens. Off because it is unauthenticated and describes your whole deployment.

ListenPort

1–65535

9101

The port. 9090 is Prometheus itself; exporters conventionally sit in the 9100+ range.

BindAddress

an address

::

Every interface, because a scraper is on another machine by definition.

Path

a path

/metrics

Where the metrics are served.

BindIPv4 / BindIPv6

true, false

true

Which families to bind.

[Stats]

The live statistics page, off on the same terms and for the same reason.

SettingAcceptsUnsetWhat it does

Enabled

true, false

false

Whether the page is served. Off for the same reason as the metrics.

ListenPort

1–65535

9102

The port, next to the metrics endpoint.

BindAddress

an address

::

Every interface, for the same reason.

Path

a path

/

Where the page is served. The WebSocket sits directly beneath it, so /stats puts it at /stats/ws.

RefreshInterval

seconds

2

Seconds between pushes. The whole cost of the feature: one pass over the backend list per interval per connected browser.

BindIPv4 / BindIPv6

true, false

true

Which families to bind.

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.

SettingAcceptsUnsetWhat it does

Mode

userspace, auto, kernel

auto

userspace serves everything and programs nothing. auto hands over what can be handed over, and a service staying behind is ordinary. kernel is the same but reports a service that should have moved and did not as a fault.

Backend

auto, nftables, ipvs

auto

Which facility to use. auto asks the host what it can actually do rather than assuming.

Table

a name

sslb3

The nftables table created for these rules.

HelperSocket

a path

/run/sslb3/netlink-helper.sock

Where the privileged netlink helper listens. The helper is a separate process with no Lua and no listener — it is the only component needing CAP_NET_ADMIN.

SourceNat

true, false

true

Rewrite the source address on forwarded connections. Correct nearly everywhere; set false only on a network that can route replies back without it, DSR-style. Without a rewrite and without such a network, connections hang.

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.