Skip to main content

Configuration variables

The authorization server reads its settings from environment variables. The table lists every variable that src/main/resources/application.yml reads, with its default and the Spring property it sets. It is generated from that file, and the description is the comment above the property there.

Most defaults suit local development, such as PBAC_OIDC_ISSUER at http://localhost:8080. Before an instance serves real traffic, follow Harden an instance. It lists the five values the server refuses to start without and the defaults to change.

Variables​

VariableDefaultSpring propertyDescription
HIKARI_MAX_POOL_SIZE19spring.datasource.hikari.maximum-pool-size19 + the cluster bus's 1 dedicated LISTEN connection = 20 per pod.
HIKARI_MIN_IDLE5spring.datasource.hikari.minimum-idle—
INSTANCE_NAMElocalinstance.name—
LOKI_URLhttp://localhost:3100loki.url—
OPA_URLhttp://localhost:8181opa.urlDev default; production must set opa.url
PBAC_ADMIN_API_KEYemptypbac.admin.api-keyAdmin key: /admin/** requires it in the X-Admin-API-Key header. /access/** does not use it; AuthzenBearerFilter there takes a Bearer token or Basic auth. Required: the AS refuses to start when blank, unless allow-insecure-dev-key is on (dev profile, tests), in which case /admin/** is open.
PBAC_ADMIN_APP_NAMEPolicyArc Portalpbac.branding.app-name—
PBAC_ADMIN_ENABLEDtruepbac.admin.enabledWhen false, /admin/** returns 404 to a caller with a valid admin key; a caller without one is refused first. Use PBAC_ADMIN_ENABLED=false in production.
PBAC_ADMIN_LOGO_URIemptypbac.branding.logo-uri—
PBAC_ADMIN_PEPPERemptypbac.admin.pepperServer-wide secret added to plaintext before hashing into lookup_hash. Required: the AS always refuses a known dev pepper, and refuses a blank one unless allow-insecure-dev-key is on, which then uses a public fallback.
PBAC_ALLOW_INSECURE_DEV_KEYfalsepbac.allow-insecure-dev-keyExplicit opt-in for the public-source-tree dev fallback key. Required for local dev (./pbac create) and CI when PBAC_CREDENTIAL_KEY is unset. Production defaults to false — boot fails closed when no credential key is provided.
PBAC_BUNDLE_API_KEYdev-bundle-keypbac.bundle.api-key/bundles/** require it in the X-Bundle-API-Key header (OPA bundle fetch). Required: the default dev-bundle-key, a blank value or another known dev key refuses the start unless allow-insecure-dev-key is on (dev profile, tests).
PBAC_BUNDLE_CONFIRM_ACTIVATIONtruepbac.bundle.confirm-activation.enabledThe batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here).
PBAC_BUNDLE_CONFIRM_POLL_MS500pbac.bundle.confirm-activation.poll-msThe batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here).
PBAC_BUNDLE_CONFIRM_TIMEOUT_MS10000pbac.bundle.confirm-activation.timeout-msThe batch deploy (POST /admin/api/policy-bundle/deploy) is acknowledged only once OPA reports the built bundle's manifest revision as ACTIVE; otherwise the pre-deploy snapshot is restored and the caller gets a 422. Nothing on the AS can compile Rego, so OPA's own activation is the only real compile check — without this, a broken rule returned 200 and took the instance down at the next OPA restart. The budget must exceed OPA's bundle poll interval (1-2s here).
PBAC_CIMD_ALLOWED_INSECURE_HOSTSlocalhost,127.0.0.1,[::1]pbac.registration.cimd.allowed-insecure-hostsHosts allowed for insecure (http://) CIMD URLs when allow-insecure-localhost is true
PBAC_CIMD_ALLOW_INSECURE_LOCALHOSTfalsepbac.registration.cimd.allow-insecure-localhostAllow http:// client_id URLs for listed hosts (dev only; disable in production)
PBAC_CIMD_CACHE_TTL_SECONDS3600pbac.registration.cimd.cache-ttl-secondsCache TTL in seconds for resolved CIMD clients (1 hour default)
PBAC_CIMD_FETCH_TIMEOUT_SECONDS10pbac.registration.cimd.fetch-timeout-secondsHTTP fetch timeout in seconds
PBAC_CIMD_MAX_DOCUMENT_SIZE_BYTES65536pbac.registration.cimd.max-document-size-bytesMaximum size of fetched metadata document in bytes (64KB default)
PBAC_CREDENTIAL_KEYthe value of PBAC_GATEWAY_CREDENTIAL_KEY, else emptypbac.credential-keyShared AES-GCM credential key for at-rest encryption of AS secrets. PBAC_CREDENTIAL_KEY is the canonical env var; PBAC_GATEWAY_CREDENTIAL_KEY is the legacy fallback. When neither is set, the AS refuses to start unless allow-insecure-dev-key is on, in which case it uses a public dev fallback key and logs a warning.
PBAC_CREDENTIAL_KEY_DISCARD_PER_USERfalsepbac.credential-key-discard-per-userCompromise-response only. When true, THIS BOOT DELETES every per-user encrypted store — upstream exchanged tokens, upstream identity tokens, user upstream secrets, this AS's own access and refresh tokens, and every browser session. The OIDC signing key is re-keyed, not deleted. Users re-authenticate upstream and sign in again; client_credentials clients recover on their own. The control plane sets this for exactly one restart during a credential-key rotation. It must not be left on: an ordinary restart would then log out the whole instance.
PBAC_DOCS_URLhttp://localhost:3010pbac.docs.urlBase URL of the PolicyArc documentation site. Used by admin portal "learn more" / Privacy / Terms links. Local dev default points at the docs container; deployed environments MUST set PBAC_DOCS_URL (the AS Helm chart wires it from authServer.docsUrl).
PBAC_DPOP_ENABLEDtruepbac.dpop.enabled—
PBAC_DPOP_IAT_WINDOW_SECONDS60pbac.dpop.iat-window-seconds—
PBAC_DPOP_NONCE_ENABLEDfalsepbac.dpop.nonce.enabled—
PBAC_DPOP_NONCE_TTL_SECONDS300pbac.dpop.nonce.ttl-seconds—
PBAC_DPOP_SIGNING_ALGSES256,RS256pbac.dpop.signing-algs—
PBAC_ENVdevmanagement.opentelemetry.resource-attributes.deployment.environmentOpenTelemetry resource attributes. service.name is derived from spring.application.name automatically.
PBAC_FEEDBACK_ENABLEDtruepbac.feedback.enabledWhen true, AS admin portal includes a floating feedback widget that loads the widget JS and posts submissions back. Disable for on-prem deployments without a reachable feedback surface.
PBAC_FEEDBACK_URLhttp://localhost:8000pbac.feedback.urlBase URL of the feedback / submissions surface. The script tag loads ${url}/feedback-widget.js and the widget posts to ${url}/submissions. In dev this is the controlplane on :8000; in cluster, point it at the dashboard frontend's public BFF (e.g. https://dashboard.example.com/api/public).
PBAC_GATEWAY_CONNECT_TIMEOUT5spbac.gateway.connect-timeoutTCP connect timeout for upstream dials. Kept short so a bad upstream URL fails fast with a 502 — see GatewayProperties for the rationale.
PBAC_GATEWAY_CREDENTIAL_KEYemptypbac.gateway.credential-keyLegacy alias — CredentialEncryptor prefers pbac.credential-key (canonical) and falls back here. Kept for backward compatibility with existing deployments.
PBAC_GATEWAY_ENABLEDtruepbac.gateway.enabledWhen false, /gateway/** returns 404. Managed Resource Gateway feature flag.
PBAC_GATEWAY_LINK_STATE_SECRETemptypbac.gateway.link-state-secretHMAC secret for step-up link-state; set in prod
PBAC_GATEWAY_MAX_BODY_SIZE10MBpbac.gateway.max-body-sizeMaximum request/response body size buffered by the proxy WebClient.
PBAC_GATEWAY_RESPONSE_TIMEOUT30spbac.gateway.response-timeoutRead timeout once the upstream connection is established. Stays generous to accommodate slow legitimate APIs.
PBAC_MAX_REDIRECT_URIS25pbac.registration.max-redirect-urisCap on redirect_uris per client (DCR and the admin client form). Not wired into the Helm chart values yet — set PBAC_MAX_REDIRECT_URIS directly if a deployment needs a different bound.
PBAC_OIDC_ISSUERhttp://localhost:8080pbac.oidc.issuerDev default; production must set PBAC_OIDC_ISSUER
PBAC_OIDC_JWKS_CACHE_SECONDS300pbac.oidc.jwks-cache-secondsmax-age advertised by /.well-known/jwks.json.
PBAC_OIDC_JWKS_PATHemptypbac.oidc.jwks-path—
PBAC_OIDC_KEY_RETIREMENT_SECONDS86400pbac.oidc.key-retirement-secondsHow long a retired signing key stays published in JWKS so tokens it signed keep verifying. Should exceed id-token-validity-seconds + jwks-cache-seconds; below that OidcKeyRotationGuard logs a WARN at boot. It does NOT refuse to start — a tuning mistake should not turn into an outage.
PBAC_OIDC_STATEMENT_KEY_RETIREMENT_SECONDS34560000pbac.oidc.statement-key-retirement-secondsHow long a rotated-out SOFTWARE_STATEMENT key stays published. Statements default to a one-year lifetime, so this is sized against that rather than against an id_token: 400 days is a year plus a month of margin.
PBAC_OTEL_ENABLEDfalsemanagement.tracing.enabledOTLP export. Off by default. PBAC_OTEL_ENABLED=true is the single opt-in switch. /actuator/prometheus is unaffected either way.
PBAC_OTEL_EXPORTER_OTLP_ENDPOINTemptymanagement.otlp.tracing.endpointEmpty by default, same as the logging/metrics endpoints below — when OtelExportEnvironmentPostProcessor derives management.otlp.tracing.export.enabled=false, OtlpTracingConfigurations.Exporters (@ConditionalOnEnabledTracing("otlp")) is filtered out of auto-configuration entirely, so OtlpHttpSpanExporterBuilder#setEndpoint never runs and never sees this value. (An earlier revision defaulted this to a syntactically- valid loopback placeholder as defense-in-depth; that made this property always non-empty, which broke the postprocessor's own "is traces configured" check — see its Javadoc.)
PBAC_OTEL_EXPORTER_OTLP_LOGS_ENDPOINTemptymanagement.otlp.logging.endpointe.g. http://collector:4318/v1/logs
PBAC_OTEL_EXPORTER_OTLP_METRICS_URLemptymanagement.otlp.metrics.export.urle.g. http://collector:4318/v1/metrics
PBAC_OTEL_TRACE_SAMPLE1.0management.tracing.sampling.probabilityAlways-on when enabled; enterprises sample at their own collector.
PBAC_RATE_LIMIT_ENABLEDtruepbac.rate-limit.enabled—
PBAC_RATE_LIMIT_REQUESTS1000pbac.rate-limit.requests-per-window—
PBAC_RATE_LIMIT_WINDOW60pbac.rate-limit.window-seconds—
PBAC_ROTATION_ADMIN_PEPPERemptypbac.rotation.admin-pepperWritten by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state.
PBAC_ROTATION_BUNDLE_API_KEYemptypbac.rotation.bundle-api-keyWritten by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state.
PBAC_ROTATION_CREDENTIAL_KEYemptypbac.rotation.credential-keyWritten by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state.
PBAC_ROTATION_LINK_STATE_SECRETemptypbac.rotation.link-state-secretWritten by the control plane during a secret rotation and removed when it completes. DO NOT SET BY HAND — a value equal to its steady-state counterpart makes the rotation a silent no-op: every row still decrypts, the retirement assertion still passes, and the old value stays live. Boot refuses that case rather than proceeding. Comma-separated; empty is the steady state.
PBAC_SAFE_HTTP_ALLOWED_INSECURE_HOSTSlocalhost,127.0.0.1,[::1],host.docker.internalpbac.security.safe-http.allowed-insecure-hosts—
PBAC_SAFE_HTTP_ALLOW_INSECURE_LOCALHOSTfalsepbac.security.safe-http.allow-insecure-localhostOutbound HTTPS-only SSRF guard for IdP/JWKS/software-statement fetches. Production defaults to strict — only flip this on for dev/CI where mocks are reached over http://host.docker.internal etc. See SafeHttpProperties.
PBAC_SESSION_CLEANUP_CRON0 */5 * * * *spring.session.jdbc.cleanup-cronServlet HttpSession lives in Postgres, not Tomcat memory, so the authenticated subject survives a request landing on a different replica. Only /authorize and the two gateway link/connect endpoints touch a session — /token, /introspect and /gateway/** stay sessionless, so this is off the hot paths.
PBAC_SESSION_COOKIE_SECUREfalseserver.servlet.session.cookie.secureSpring Session defaults Secure to request.isSecure(), which is false when TLS terminates at the ingress and the AS sees plain HTTP — so it has to be forced rather than inferred. Off by default so local HTTP and ./pbac instances keep working; the chart sets it true.
PBAC_SESSION_TIMEOUT30mspring.session.timeoutServlet HttpSession lives in Postgres, not Tomcat memory, so the authenticated subject survives a request landing on a different replica. Only /authorize and the two gateway link/connect endpoints touch a session — /token, /introspect and /gateway/** stay sessionless, so this is off the hot paths.
PBAC_SOFTWARE_STATEMENT_LIFETIME_SECONDS31536000pbac.software-statement.lifetime-seconds—
PBAC_SOFTWARE_STATEMENT_VERIFYtruepbac.software-statement.verifyVerify the statement signature at /register. On by default: the trusted_issuers allow-list is keyed on iss, which is an unauthenticated claim until this is on.
PBAC_SPIFFE_AUDIENCEthe value of PBAC_OIDC_ISSUER, else http://localhost:8080pbac.spiffe.audience—
PBAC_SPIFFE_TRUST_BUNDLE_REFRESH_SECONDS300pbac.spiffe.trust-bundle-refresh-secondsSPIRE OIDC discovery URL is sourced from policy data (spiffe_trust_bundle_url in the pbac.operator.controls.trust row) — register a SPIRE endpoint via PATCH /admin/api/policy-data/by-key, not env vars.
PBAC_SWAGGER_ENABLEDtruespringdoc.api-docs.enabled
springdoc.swagger-ui.enabled
—
PBAC_TRUSTED_PROXY_COUNT0pbac.trusted-proxy-countWhen > 0, use X-Forwarded-For; take the Nth-from-right (the client before N trusted proxies)
PBAC_VERSIONdevmanagement.opentelemetry.resource-attributes.service.versionOpenTelemetry resource attributes. service.name is derived from spring.application.name automatically.
SCHEDULING_POOL_SIZE4spring.task.scheduling.pool.sizeBoot's default is 1. That put the Spring Session cleanup, the expired-token cleanup, the MCP session sweep and the 20s MCP SSE keepalive on a single thread — so one slow bulk DELETE delays keepalives and open streams look dead to their clients. Two of those jobs are DB-bound and were added alongside the session store.
SHUTDOWN_PHASE_TIMEOUT25sspring.lifecycle.timeout-per-shutdown-phaseHow long server.shutdown: graceful waits for in-flight requests before forcing the context closed. Must stay below the pod's terminationGracePeriodSeconds minus the preStop drain, or the kubelet SIGKILLs mid-drain and we are back to dropped requests.
SPRING_DATASOURCE_PASSWORDpbac-devspring.datasource.password—
SPRING_DATASOURCE_URLjdbc:postgresql://localhost:5432/pbac?options=-c%20TimeZone%3DUTCspring.datasource.url—
SPRING_DATASOURCE_USERNAMEpbacspring.datasource.username—

Settings that are not in the table​

A Spring property with no variable in application.yml can still be set from the environment. Spring maps the variable name to the property, so PBAC_OAUTH_TIMING_ID_TOKEN_VALIDITY_SECONDS sets pbac.oauth.timing.id-token-validity-seconds, the lifetime of an ID token, 3600 seconds by default. The Helm chart sets that variable from authServer.oidc.idTokenValiditySeconds.

Rate limiting​

PBAC_RATE_LIMIT_ENABLED, PBAC_RATE_LIMIT_REQUESTS and PBAC_RATE_LIMIT_WINDOW control a limit per client address on /token, /authorize, /register, /introspect, /revoke, /bc-authorize, /gateway/mcp, /gateway/link and /gateway/connect. Other paths are not limited. A request over the limit gets HTTP 429.

{"error":"too_many_requests","error_description":"Rate limit exceeded. Try again later."}

We set PBAC_RATE_LIMIT_REQUESTS=3 and sent five token requests, and the fourth and fifth got this response.

The client address comes from the connection unless PBAC_TRUSTED_PROXY_COUNT is above 0. Then the server takes the address in X-Forwarded-For that sits that many entries from the right end, so with 1 it takes the last entry. Each replica counts on its own. See Run more than one replica.