Radiator Server Documentation — v10.33.1
2026-04-21

v10.33.1

Summary
  • Add --expect-message flag to radiator-client for validating response Reply-Message

  • Configuration 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 uid attribute 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 jsonpath filter now accepts JSON strings directly, and the json filter no longer errors when the input is already parsed JSON.

  • Fix RADIUS challenges when proxying with the query mode

  • Add --expect-attr flag to radiator-client for asserting response attribute values

  • Duration units now work consistently in statistics intervals and certificate directory refresh intervals

  • Move range support from the YubiKey HTTP backend to the backend action in the handler pipeline

  • Reduce 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 username and secret to 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.zip
  • radiator-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 explicit service-level-objective block 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 explicit service-level-objective block 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 backend
  • PUT /api/v1/backends/{name} — replace backend configuration
  • DELETE /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): All include* parameters now default to true. To exclude a directory, pass includeLicenses=false, includeManagement=false, etc.
  • Import (POST /configuration/import): All import* parameters now default to true. templateImportMode and licenseImportMode now default to replace instead of append/ignore. To preserve existing content, explicitly pass importManagement=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:

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

  • statistics interval accepts values like 30s, 5m, and 1h30m in addition to bare milliseconds. See statistics configuration.
  • x509-directory interval accepts 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 typeExample
Durationtimeout env.TIMEOUT | default("5s");
Booleanrequire_message_authenticator env.REQUIRE_MA;
Numberport env.RADIUS_PORT;
IP addressip env.LISTEN_IP;
IP prefixip 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;
  }
}

Documentation