2026-04-21
v10.33.1
Summary
Add
--expect-messageflag toradiator-clientfor validating response Reply-MessageConfiguration backup and export filenames now include the server's cluster and instance IDs for multi-server distinguishability
Service-level-objective is now opt-in for single-server backends — a server without server-selection and without an explicit service-level-objective block has no health monitoring; backends with server-selection automatically receive the default SLO on every server that does not have an explicit block
New failure-rate N/M syntax replaces failure-threshold for clearer circuit-breaking configuration
New recovery-probe-count parameter controls how many consecutive successful probes are required before a degraded server is marked healthy again
SLO violation counters: SLOFailureThresholdViolations, SLORecovered, SLOStillFailing
Backend configuration write API removed — use radconf configuration snippets to create, update, and delete backends
PostgreSQL and MySQL backends now start successfully when the database is temporarily unavailable at startup
New least-connections server-selection strategy routes each request to the server with the fewest in-use connections for PostgreSQL, MySQL, and LDAP backends
Pool saturation on one server causes immediate failover to the next usable server rather than queuing requests
Fix Management API session authentication to work independently of the AAA pipeline result. Session identity is now read directly from the session store, fixing 401 errors for Active Directory users whose
uidattribute is absent from the directory.Configuration export and import now default to full configuration (all content directories included)
Backend mappings now preserve all values from multi-valued sources consistently across LDAP, HTTP, SQL, and JSON backends
The
jsonpathfilter now accepts JSON strings directly, and thejsonfilter no longer errors when the input is already parsed JSON.Fix RADIUS challenges when proxying with the query mode
Add
--expect-attrflag toradiator-clientfor asserting response attribute valuesDuration units now work consistently in statistics intervals and certificate directory refresh intervals
Move
rangesupport from the YubiKey HTTP backend to thebackendaction in the handler pipelineReduce the default concurrent connection limit for HTTP-style backends from 1024 to 100
Environment variables can now be used for duration, boolean, number, IP address, and IP prefix configuration values in addition to strings and file paths
Allow the YubiKey HTTP backend
usernameandsecretto use request-scoped expressions
Add --expect-message to radiator-client
The radiator-client tool now supports --expect-message flag which asserts
that the response Reply-Message attribute matches the given string exactly.
If the response has no Reply-Message or the value differs, the program exits
with a non-zero status code.
radiator-client \
--server 127.0.0.1 \
--secret mysecret \
--user alice \
--password secret \
--expect-code access-reject \
--expect-message "Invalid password"
See radiator-client documentation for more details.
Server identification in backup and export filenames
Configuration backup directories and export ZIP filenames now include the server's cluster ID (if set) and instance ID. This makes it easy to identify which server a backup or export originated from when managing multiple servers.
Previous format: backup-2026-03-31T12-00-00Z
New format (with cluster ID): backup-C01-R00-2026-03-31T12-00-00Z
New format (without cluster ID): backup-R00-2026-03-31T12-00-00Z
The same applies to configuration and template export filenames:
radiator-config-R00-2026-03-31T12-00-00Z.zipradiator-templates-C01-R00-2026-03-31T12-00-00Z.zip
Service Level Objective improvements
SLO is now opt-in for single-server backends
Previously, a default service-level-objective was applied automatically to every
backend server. Now, health monitoring is opt-in with one exception:
- Backends with
server-selection(HA): every server that does not have an explicitservice-level-objectiveblock automatically receives the default SLO (failure-rate 3/5,initial-backoff-period 3s,max-backoff-period 30s,recovery-probe-count 2). Add an explicit block to a server to override these defaults. - Single-server backends (no
server-selection): no SLO is applied unless an explicitservice-level-objectiveblock is present. Add the block to enable circuit breaking for single-server backends.
service-level-objective {
failure-rate 3/5;
initial-backoff-period 3s;
max-backoff-period 30s;
recovery-probe-count 2;
}
Other improvements
Backend service-level-objective blocks gain two further improvements.
failure-rate N/M replaces failure-threshold. The new syntax directly expresses
the intent: degrade when N or more of the last M requests fail (default: 3/5).
recovery-probe-count N controls how many consecutive successful probe responses
are required before a degraded server is declared healthy again (default: 2). While
probes are accumulating, the backoff interval is held at initial-backoff-period to
keep the probe cadence fast and predictable.
See Service Level Objective for configuration details and examples.
Backend configuration write API removed
The Management API endpoints for creating, updating, and deleting backend configurations at runtime have been removed. This covers all write operations (POST, PUT, DELETE) that previously operated directly on backend configurations for PostgreSQL, MySQL, LDAP, RADIUS, and HTTP backends:
POST /api/v1/backends— create backendPUT /api/v1/backends/{name}— replace backend configurationDELETE /api/v1/backends/{name}— delete backend- And backend sub-resource write endpoints (RADIUS servers, LDAP servers, LDAP operations, HTTP queries, SQL servers)
Use radconf configuration snippets instead. Place a .radconf file in the
configuration directory, edit it through the Management UI or API file endpoints,
and deploy it with the pending configuration workflow. Radiator applies the change
live without a restart.
The read-only endpoints (GET /api/v1/backends, GET /api/v1/backends/{name},
GET /api/v1/backends/status, GET /api/v1/backends/{name}/status) and the file
backend reload endpoint (POST /api/v1/backends/{name}/file/reload) are unaffected.
PostgreSQL, MySQL, and LDAP HA: lazy pooling and least-connections selection
Lazy connection pooling — PostgreSQL, MySQL
PostgreSQL and MySQL backends now use lazy connection pooling. Connections to database servers are established on demand when requests arrive rather than eagerly at startup.
Previously, if a database server was unavailable when Radiator started, the startup connection attempt failed and Radiator entered recovery mode. With lazy pooling, Radiator starts successfully regardless of database availability and reconnects automatically when the databases become reachable.
New server-selection strategy: least-connections — PostgreSQL, MySQL, and LDAP
A new server-selection least-connections strategy is now available for PostgreSQL, MySQL, and LDAP backends. Each request is routed to the available server with the fewest in-use connections. When servers are tied (e.g. all pools idle), servers with a lower priority value are preferred; within the same priority, alphabetical name order is used as a tiebreaker — the same as fallback.
postgres "USERS" {
server-selection least-connections;
server "replica1" { host "pg-replica1.example.com"; connections 15; ... }
server "replica2" { host "pg-replica2.example.com"; connections 15; ... }
}
The strategy is also available for LDAP backends:
ldap "LDAP_CLUSTER" {
server-selection least-connections;
server "LDAP1" { url "ldap://ldap1.example.com:389/"; ... }
server "LDAP2" { url "ldap://ldap2.example.com:389/"; ... }
}
Pool saturation failover — PostgreSQL, MySQL, and LDAP
When a server's connection pool appears saturated (all connections in use), Radiator now skips that server immediately and tries the next usable server rather than queuing the request until acquire-timeout expires. If every available server's pool appears full, the request fails immediately with a PoolExhausted error instead of waiting. This applies regardless of which server-selection strategy is configured.
See Backend Load Balancing and the PostgreSQL server-selection, MySQL server-selection, and LDAP server-selection reference pages for configuration details and examples.
Removed JSON Patch Configuration Persistence
The server no longer reads or writes radiator_config.json files. Previously, configuration changes made via the Management API were persisted as a JSON patch file and re-applied on the next server start. This feature has been removed. Configuration changes should now be made directly to radconf files either via the management UI, API or by editing the files on disk.
The --force-configuration-file (-f) CLI flag has also been removed.
Full configuration export and import by default
The configuration export and import API and UI now default to a full configuration export/import, including all content directories (licenses, management, ui-settings, db, tls, lua, templates).
API changes
- Export (
GET /configuration/export): Allinclude*parameters now default totrue. To exclude a directory, passincludeLicenses=false,includeManagement=false, etc. - Import (
POST /configuration/import): Allimport*parameters now default totrue.templateImportModeandlicenseImportModenow default toreplaceinstead ofappend/ignore. To preserve existing content, explicitly passimportManagement=false,licenseImportMode=ignore, etc.
UI changes
The export and import dialogs now include a "Full" / "Custom" toggle. Selecting "Full" performs a complete export or import of all content directories. The default selection is "Custom", preserving the previous granular per-directory control.
Fix: backend mappings consistently preserve multi-valued results
Backend mapping operators now handle multi-valued sources consistently across
LDAP, HTTP, SQL, and JSON file backends. When a source returns more than one
value, all values are now preserved instead of silently keeping only part of
the result.
This mainly fixes backend-mapping logic. It affects =, +=, and ?=
semantics when the source produces multiple values and also makes empty-source
handling consistent for single-value targets.
Documentation
The LDAP backend documentation now also shows how to guard dynamic LDAP bind
flows against missing User-Name and password attributes in incoming RADIUS
requests before calling FIND_USER and BIND_USER.
JSON filter improvements
The jsonpath filter now accepts both parsed JSON values and JSON strings. An
explicit | json step before | jsonpath(...) is no longer required:
# Both forms work
vars.ip = reply_attrs | jsonpath("$.framed_ip_address");
vars.ip = reply_attrs | json | jsonpath("$.framed_ip_address");
The | json step is still useful when the same parsed value is needed multiple
times. The json filter no longer errors when its input is already a parsed
JSON value.
See the updated Filters documentation.
Add --expect-attr to radiator-client
The radiator-client tool now supports --expect-attr flag for asserting
decoded response attribute values using exact string equality or regular
expressions. The flag can be specified multiple times. Attribute names are
matched case-insensitively, while values are compared case-sensitively by
default.
Four operators are available:
| Operator | Description |
|---|---|
<name>==<value> | Exact match (attribute must be present) |
<name>!=<value> | Exact non-match |
<name>=~/<regex>/ | Regex match (attribute must be present) |
<name>!=~/<regex>/ | Regex non-match |
Regex patterns must be wrapped in /.../ delimiters. Append i after the
closing delimiter (/.../i) for case-insensitive matching.
Values are compared against the same decoded representation shown in the table output, so hex bytes, enums, and IP addresses all work as expected.
radiator-client \
--server 127.0.0.1 \
--secret mysecret \
--user alice \
--password secret \
--expect-attr "Filter-Id==myfilter" \
--expect-attr "State=~/^0x[0-9a-f]+$/" \
--expect-attr "Reply-Message!=~/^Internal error$/"
See radiator-client --help and radiator-client documentation for
more details.
Duration units in configuration intervals
Duration syntax is now accepted in a few remaining places that previously required raw numbers.
statisticsintervalaccepts values like30s,5m, and1h30min addition to bare milliseconds. See statistics configuration.x509-directoryintervalaccepts the same duration syntax when rescanning certificate directories. See certificates configuration.
For the supported duration syntax and units, see Duration Units.
Move YubiKey cloud PAP range to the handler pipeline and adjust HTTP backend default concurrency
The backend action in the handler pipeline now supports range, which lets
Radiator slice the PAP value before passing it to the backend. This moves the
range configuration from the YubiKey HTTP backend definition to the handler
side.
HTTP-style backends now default to 100 concurrent connections instead of
1024. This applies to the generic HTTP backend and the specialized Duo,
YubiKey, and RSA-AM backends. Deployments that need higher concurrency can
still set connections explicitly.
Documentation
Environment variable support for all configuration value types
Environment variables can now be used for all configuration value types: durations, booleans, numbers, IP addresses, and IP prefixes. Previously, only string and file path values supported environment variable expressions.
This enables fully parameterized configurations where every setting -- listen ports, timeouts, feature toggles, client source networks -- can be controlled through environment variables. This is particularly useful in containerized and systemd-managed deployments where multiple Radiator instances share a single configuration and differ only by environment.
New value types
| Value type | Example |
|---|---|
| Duration | timeout env.TIMEOUT | default("5s"); |
| Boolean | require_message_authenticator env.REQUIRE_MA; |
| Number | port env.RADIUS_PORT; |
| IP address | ip env.LISTEN_IP; |
| IP prefix | ip env.CLIENT_NETWORK; |
All expressions support the default() filter for fallback values.
Documentation
Support request-scoped YubiKey backend credentials
Radiator now resolves the YubiKey HTTP backend username and secret when the backend is called. You can populate these values from variables set earlier in the handler pipeline, as long as the resolved YubiKey secret is still a valid base64-encoded API key.
backends {
yubikey "YUBIKEY_AUTH" {
url "https://api.yubico.com/wsapi/2.0/verify";
username "%{vars.aaa.yubikey.user}";
secret "%{vars.aaa.yubikey.secret}";
timeout 4s;
}
}