Radiator Server Documentation — latest
2026-06-02

v10.33.3

Summary
  • Added the must action for converting ignored required action results to rejects.

  • Structured logging for EAP-TLS, EAP-TTLS, EAP-PEAP and EAP-TEAP handshakes

  • Added metadata to configuration export and backup ZIP files so imports and restore dialogs can show archive version, creator, timestamps, and deployment changes.

  • Configuration import now previews ZIP archive metadata and warnings before import, including missing metadata, version mismatch, and missing selected content directories.

  • JSON file backend mapping block now accepts an optional JSONPath selector argument for per-element iteration

  • Add secret parameter with full filter pipeline support to pap, backend, yubikey, hotp, and totp actions, plus a dedicated yubikey() OTP helper filter

  • LDAP search filters now accept readable multiline formatting in triple-quoted config strings without causing LDAP filter parse errors

  • Add PBKDF2-HMAC password hashing (SHA-1, SHA-256, SHA-512)

  • Added TACACS+ trace-level packet logging and per-server packet size controls

  • Files page backup restore moved to the overflow menu, backup timestamps now follow the encoded backup name, and empty directory changes now survive deploy and restore flows.

  • Lowered the default connections for RADIUS backend servers from 1024 to 10

  • The template render CLI now includes Management UI template metadata headers by default.

  • The template CLI can now verify template files and generated files against template metadata checksums.

  • Fixed a documentation search highlighting crash that could break docs pages after opening highlighted search results.

  • Servers can now use @pre-client together with clients during migration from static to dynamic client matching

  • Added RFC 9887-compatible TACACS+ over TLS settings, except certificate revocation checking

  • ipmap now always starts population in the background at backend creation time, and lookups reject until the first snapshot is available.

  • The Management API ipmap populate endpoint now triggers repopulation asynchronously instead of waiting for completion.

  • Added PROXY protocol v2 support before TLS handshakes on RADIUS/RadSec and TACACS+ TLS listeners.

  • Fixed Management UI sidebar links to configuration files so filename and template query parameters are preserved.

  • Missing configuration files now show the not-found state immediately instead of waiting for retries.

  • Counter and histogram history retention default extended from 24 hours to 7 days

  • Statistics API and Prometheus /metrics now expose floating-point gauges (e.g. load averages, rates, fractional ratios)

  • Scalar counter values returned by the /statistics JSON endpoints may now be fractional.

  • Improved the documentation index layout, navigation scroll position, and linkable docs search.

must Action

Added the must action for wrapping another action that should produce a decision. If the wrapped action returns ignore, must converts the result to reject and sets aaa.reason.

must pap;

Structured EAP-TLS handshake logging

EAP-TLS and the EAP methods that tunnel TLS inside it (EAP-TTLS, EAP-PEAP, EAP-TEAP) now emit a structured logging scope around the inner TLS handshake. The new records make it easier to follow one supplicant's handshake, inspect the negotiated TLS session and peer certificate chain, and understand why a handshake failed.

Successful handshakes are logged at Debug, handshake failures at Warn, and fragment metadata at Trace. Failure logs also cover EAP response timeouts and unexpected EAP response type/id while the TLS handshake is active. Fragment records do not include payload bytes.

See server TLS - EAP-TLS handshake logging for concrete JSON examples and the complete field reference.

No configuration changes are required.

Configuration Archive Metadata

Configuration export and backup ZIP files now include .radiator-archive-metadata.json at the archive root. The metadata records the radiator-server version that created the archive, when it was created, and the management user involved when available.

Automatic deployment backups also record the deployed files and their change types. The Management UI shows this information in the backup restore dialog, and configuration import responses now include a non-blocking warning when an archive was created by a different radiator-server version.

The configuration import dialog now inspects a selected ZIP archive before import and shows archive metadata plus the same warnings that the import would return. This lets operators review missing or unreadable metadata, version mismatch, and missing selected content directories before creating or replacing pending configuration. See Configuration Import and Export.

JSONPath selector argument for jsonfile mapping

The mapping block in jsonfile queries now accepts an optional JSONPath expression argument. When provided, the expression selects elements from the JSON document and the inner field mappings execute once per matched element with doc bound to each element individually.

query "FIND_USER" {
    mapping "$.users[?(@.username == '%{aaa.identity}')]" {
        user.username = doc | jsonpath("$.username");
        user.password = doc | jsonpath("$.password");
    }
}

This eliminates repeated filter expressions in field mappings and enables use cases that require repeating mapping executions, such as populating the ipmap backend from a JSON file.

See jsonfile documentation for details.

Filter pipelines on authentication actions and a YubiKey OTP helper filter

The pap, backend, yubikey, hotp, and totp actions now accept a secret parameter that takes a full context expression, including the filter pipeline. The expression result replaces the protocol PAP response that the action would otherwise read, so any context attribute can be shaped (for example with substring(), uppercase(), reveal, or any other filter) into the value to authenticate. This gives one consistent way to express two-factor extraction, attribute-driven authentication, and cross-protocol routing across all five actions:

# Validate the static password portion of a combined <password><totp> field
pap {
    secret radius.request.password | substring(0, -6);
}

# Validate the trailing 6 digits as the TOTP code
totp {
    secret radius.request.password | substring(-6);
}

# Forward only the password portion to an upstream RADIUS server
backend {
    name "UPSTREAM_RADIUS";
    secret radius.request.password | substring(0, -6);
}

Masked password values such as radius.request.password are now revealed automatically along the action pipeline so substring() and similar string filters return the cleartext slice rather than the mask placeholder.

Radiator also includes a dedicated yubikey() filter for common password + OTP deployments so they do not need hard-coded substring() offsets. The filter takes a placement (where the OTP sits in the input) and an extract (which component to return). For example, radius.request.password | yubikey(password-otp, public-uid) validates the trailing 44 characters as a Yubico OTP and returns the 12-character public UID. Other extract values are otp (full 44-character OTP), ciphertext (encrypted 32-character payload), and password (the static password sitting next to the OTP in a combined PAP field). Combined with the new secret parameter this becomes:

pap {
    secret radius.request.password | yubikey(password-otp, password);
}

backend {
    name "YUBIKEY_CLOUD_OTP";
    secret radius.request.password | yubikey(password-otp, otp);
}

For YubiKey cloud validation with a local SQL ownership check, extract the presented public UID before the backend lookup and reject malformed input immediately:

modify vars.presented_yubikey_public_uid = radius.request.password | yubikey(password-otp, public-uid) | recover(none);

if all {
    vars.presented_yubikey_public_uid == none;
} then {
    reject "Malformed YubiKey OTP";
}

See YubiKey Authentication and Filters for usage examples and configuration guidance.

Readable multiline LDAP search filters

LDAP search filters now tolerate line breaks and indentation when you write them as triple-quoted strings in Radiator configuration. Before this change, triple-quoted strings preserved formatting exactly, which meant a readable filter such as a multiline (&...) expression could fail before the LDAP search was sent because RFC 4515 does not allow whitespace between filter components.

Radiator now strips layout-only whitespace from multiline LDAP filters before parsing and sending the search. This lets you format long compound filters for readability without changing the effective LDAP filter expression.

search "FIND_USER" {
    base "dc=example,dc=com";
    scope sub;
    filter """
        (&
            (uid=%{aaa.identity})
            (objectClass=inetOrgPerson)
        )
    """;
    mapping {
        user.username = uid;
    }
}

See LDAP search operation for the updated filter documentation.

PBKDF2 password hashing

Radiator now verifies PBKDF2-HMAC-SHA1, PBKDF2-HMAC-SHA256 and PBKDF2-HMAC-SHA512 stored passwords on all backends. The {pbkdf2-sha512}, {pbkdf2-sha256}, {pbkdf2-sha1} and legacy {pbkdf2} prefixes are accepted (case-insensitive, per RFC 2307bis) in both PHC string format and the LDAP <iter>$<b64-salt>$<b64-hash> layout. Iteration count and salt embedded in the hash are honoured.

See Password Hashing for details.

TACACS+ trace logging and packet size controls

Radiator now emits structured TACACS+ packet dissections at TRACE level for incoming and outgoing TACACS+ traffic. TACACS+ servers also support per-server max-packet-size, with a default of 4096 bytes and a maximum of 65536 bytes. Oversized frames are rejected, counted, and upsampled from Debug to Warn at most once per minute.

See TACACS+ Authentication, Authorization, and Accounting for the trace log format, a real packet example, and the operational guidance for max-packet-size.

Files, Backups, and Empty Directories

The management files page no longer shows a pending-changes banner when there are no actual pending changes. Restoring from backup remains available from the files page overflow menu.

Backup list timestamps now follow the timestamp encoded in the backup name, including the newer server-ID-prefixed backup naming scheme.

Empty directory trees now behave as real configuration changes in pending configuration workflows. They are counted as deployable changes, survive deployment, and are preserved across backup download/import and backup restore flows.

RADIUS Backend connections Default Lowered to 10

The default value of connections in a RADIUS backend server (and in the server-template of a radius-dns-sd backend) has been lowered from 1024 to 10. The previous default predated per-server connection accounting and was larger than what almost any deployment actually used.

The new default matches the SQL backend defaults and keeps backend resource usage predictable when min-connections is also configured.

How to migrate

If you previously relied on the implicit 1024 pool size, set connections explicitly on each affected RADIUS backend server:

backends {
    radius "UPSTREAM" {
        server "primary" {
            host "radius.example.com";
            secret "mysecret";
            connections 1024;
        }
    }
}

If you have not configured connections and your traffic comfortably fits in 10 concurrent connections, no action is required.

See connections for sizing guidance.

Template Render CLI Metadata Headers

radiator template render now writes the same generated-template metadata header that Management UI uses when it creates a configuration from a template. The header records the source template filename, template checksum, and template values so Management UI can later identify and edit the generated configuration through the template form.

Use --no-header when a script needs the previous body-only rendered output.

radiator template verify validates template metadata and template syntax. When given both a template file and a generated configuration file, it also verifies the generated file metadata, checksum, and rendered content.

@pre-client and clients Client Matching Changes

This release changes how servers resolve clients and tightens configuration validation for the affected server types: HTTP, RADIUS (UDP, TCP, TLS, reverse) and TACACS+ (TCP, TLS).

@pre-client and clients can co-exist

Servers that support @pre-client can now be configured with both @pre-client and clients at the same time.

Radiator tries @pre-client first. If @pre-client does not select a client, or returns an error, Radiator falls back to clients. This is probably most useful for migrations from a static clients list to dynamic matching, or as a failover path when the dynamic source is temporarily unavailable.

See @pre-client and clients for details.

Breaking: servers require clients or @pre-client to start

The affected servers now refuse to start unless at least one of clients or @pre-client is configured.

Previously, a RADIUS UDP server with neither setting started up and logged a WARN "Radius UDP server has no clients configured" but accepted no requests. The other servers returned generic startup errors that did not clearly explain the missing setting.

From this release, every affected server fails configuration loading with:

<server> starting failed: either '@pre-client' or 'clients' must be configured

How to migrate

Audit your configuration for any server clauses that have no clients binding and no @pre-client pipeline. Either:

Server clauses commented out or relying on the previous "no clients" warning must be updated before upgrading.

TACACS+ RFC 9887 compatibility

Radiator can now be configured for an RFC 9887-compatible TACACS+ over TLS deployment. Use TLS 1.3, mutual TLS, obfuscation disabled;, and protocol-error-reply true; to match the RFC 9887 profile.

Certificate revocation checking is not supported, so CRL and OCSP validation remain outside this compatibility scope.

See TACACS+ Authentication, Authorization, and Accounting for setup examples and more details.

ipmap Startup and Populate Behavior Changes

This release changes how ipmap performs its initial population and how manual repopulation is reported through the Management API.

Startup population now runs in the background

Radiator now always starts one ipmap populate run automatically when the backend is created. That startup populate runs in the background.

Lookups do not wait for startup population to finish. Until the first successful snapshot has been published, ipmap queries run against an empty map and reject if no prefix matches.

This change avoids retaining request waiters during startup and keeps memory usage more predictable when the populate pipeline is large.

For startup-critical uses such as dynamic client resolution in @pre-client, expect connecting clients to retry until the first snapshot is available.

See ipmap.

Breaking: prepopulate was removed

prepopulate true; is no longer supported in ipmap configuration.

Remove the setting from existing configurations:

backends {
    ipmap "RADIUS_CLIENTS" {
        interval 5m;

        @populate {
            backend {
                name "SQLITE";
                query "POPULATE_CLIENTS";
            }
        }
    }
}

Radiator now performs startup population automatically, so no replacement setting is required.

Breaking: first lookup no longer triggers synchronous population

Earlier releases could populate ipmap on first use and make initial callers wait for the populate pipeline to complete. That no longer happens.

If the first snapshot is not ready yet, early lookups reject. Review startup paths that depend on ipmap and make sure clients can retry during startup.

Management API populate requests are now asynchronous

POST /api/v1/backends/{backend_name}/ipmap/populate now queues a manual repopulation request and returns HTTP 200 immediately.

The HTTP response confirms that the request was accepted by the Management API. It does not mean that repopulation has completed successfully.

Check logs to confirm when repopulation succeeds or fails.

See ipmap.

PROXY Protocol Before TLS

Radiator now accepts proxy-protocol v2; together with tls { ... } on RADIUS/RadSec and TACACS+ TLS listeners.

See PROXY Protocol Support and servers.listen.proxy-protocol for configuration details.

Sidebar items that open configuration files now keep file-related query parameters intact when navigating. This prevents links without a template-hint from turning the query string into part of the filename when opening or editing template-generated configuration files.

The configuration file details view also stops retrying file loads after a 404 response, so missing files show the not-found state immediately while transient failures still use the existing retry behavior.

Counter history retention extended to 7 days

The built-in default retention for counter and histogram history is now 7 days at 5 minute intervals (was 24 hours at 1 minute). Memory usage per counter is unchanged.

To keep the previous 1 minute granularity, set it explicitly:

statistics-counter {
    samples 1440;
    interval 60;
}

Floating-point gauges in statistics

Statistics endpoints previously returned only whole-number counters and silently filtered out floating-point gauges. Floating-point gauges are now returned alongside integer counters everywhere counter values appear:

  • GET /statistics/counters/{path}
  • GET /statistics/counters/{path}/timeseries
  • GET /statistics/counters/{path}/children
  • GET /statistics/counters/{path}/children/timeseries
  • GET /statistics/counters

Reset endpoints still operate only on integer counters; floating-point gauges have no reset operation and are skipped.

Prometheus /metrics renders non-finite values as NaN, +Inf, and -Inf.

Documentation Index Usability

The documentation index now keeps card content aligned to the top, hides missing-description placeholders, and opens documentation pages at the top when you navigate from the index. Documentation search also accepts ?q=<term> links, so shared documentation URLs can open with a search term already filled in.

AAA side-effect actions preserve action results

Several AAA actions that only update context state or perform other side effects now preserve the previous action result instead of changing it. This prevents configurations such as ignore; modify { ... } or ignore; log "AUTHENTICATION" { ... } from accidentally turning the request into an accept.

This may be a minor breaking change for configurations that relied on these side-effect actions to set the result. However, using them for accept/reject/ignore decisions was a poor security practice; use explicit result-setting actions instead.

The corrected actions are modify, log, rewrite, message, reason, reject_errors, cache, and RADIUS proxy copy / filter.