Pre-release. v0.1 is not out yet, so there is nothing to install and no public source to clone — the quickstart builds from a checkout.
Configuration
Every setting is configuration and every setting has an environment variable: Onbe:Section:Key
becomes Onbe__Section__Key. Nothing is read from a file the server wrote, and nothing is
inferred from the environment it happens to be running in.
Misconfiguration is a startup failure, not a warning, and the failure names every key that is wrong at once rather than one per restart. A startup error names the key and never repeats the value, because the value is sometimes a secret and a log line is not a safe place for one.
How a value is read:
- An empty value is an unset value.
Onbe__Admin__ApiKey=disables the admin API; it is not a key of length zero. - A list is comma-separated, and
Onbe:RateLimit:TrustedProxiesis the only one. Signing keys are numbered instead. - The command line overrides the environment.
--Onbe:RateLimit:Burst=5beatsOnbe__RateLimit__Burst; that is for trying something out, not for a deployment.
Identity
Section titled “Identity”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:Issuer |
yes | — | This control plane’s own issuer URL, as it publishes it |
Onbe:UpstreamIdp:Issuer |
one of | — | The identity provider’s realm URL; discovery is derived from it |
Onbe:UpstreamIdp:MetadataUrl |
one of | — | The discovery document directly, for a provider shaped differently |
Onbe:UpstreamIdp:Audience |
yes | — | The audience a subject token must carry |
Onbe:UpstreamIdp:SponsorCheck:UsersUrl |
yes | — | Where to ask whether a user is still active |
Onbe:UpstreamIdp:SponsorCheck:TokenUrl |
yes | — | Where the control plane gets its own token to ask with |
Onbe:UpstreamIdp:SponsorCheck:ClientId |
yes | — | The client it asks as |
Onbe:UpstreamIdp:SponsorCheck:CacheTtl |
no | 30s | How long a sponsor’s status is reused. Never longer than Tokens:DefaultTokenTtl |
Exactly one of UpstreamIdp:Issuer and UpstreamIdp:MetadataUrl. Setting both is a startup
failure rather than a quiet precedence rule, because the two disagreeing is exactly the
situation somebody would otherwise debug for an afternoon.
The sponsor cache is bounded by the token lifetime for a reason: a disabled user’s tasks have to die within one token lifetime, so a cached “still active” may not outlive the token it would have been used to mint.
Storage
Section titled “Storage”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:Database:Provider |
no | postgres |
postgres, or sqlite for the embedded database |
Onbe:Database:ConnectionString |
postgres | — | A secret |
Onbe:Database:MigrationConnectionString |
no | the above | Postgres only: a role that may change the schema. A secret |
Onbe:Database:Path |
sqlite | — | Where the embedded database file lives |
A setting the chosen provider does not use is a startup failure, not a value silently ignored: a leftover connection string usually means somebody believes a different database is in use.
Signing
Section titled “Signing”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:Signing:Keys:N:Path |
yes* | — | Path to a PEM, typically a mounted secret. A secret |
Onbe:Signing:Keys:N:Pem |
yes* | — | The PEM inline. Exactly one of Path or Pem per key |
Onbe:Signing:Keys:N:Kid |
no | a thumbprint | An explicit key id; omit for the RFC 7638 thumbprint |
Onbe:Signing:ActiveKid |
no | the only key | Which key signs. Required once more than one is configured |
* Outside the Development environment, at least one key is required and a missing one stops the server. In Development an ephemeral key is generated and logged loudly.
Every configured key is published in JWKS; only the active one signs. The indices need not run from zero — a rotation adds a number and later removes another, so gaps are normal.
Lifetimes
Section titled “Lifetimes”| Setting | Required | Default | Bounds |
|---|---|---|---|
Onbe:Tokens:DefaultTaskTtl |
no | 30m | Must not be shorter than the token TTL |
Onbe:Tokens:DefaultTokenTtl |
no | 5m | Must not exceed the task TTL |
Onbe:Tasks:SweepInterval |
no | 1m | 1 second to 1 day |
Onbe:Tasks:SweepBatchSize |
no | 20 | Positive |
These are what a registration that names no lifetime is given. A registration that names its
own gets its own — max_task_ttl: PT6H runs six-hour tasks whatever the default says — and is
held only by the Onbe:Agents:* bounds. A token is always cut to what remains of its task, and
a task with under five seconds left is refused a token.
Registration bounds
Section titled “Registration bounds”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:Agents:MinTaskTtl |
no | PT1M |
Shortest max_task_ttl a registration may set |
Onbe:Agents:MaxTaskTtl |
no | P1D |
Longest max_task_ttl a registration may set |
Onbe:Agents:MinTokenTtl |
no | PT30S |
Shortest max_token_ttl a registration may set |
Onbe:Agents:MaxTokenTtl |
no | PT1H |
Longest max_token_ttl a registration may set |
Each Min* must not exceed its Max*, and each Tokens:Default* must sit within them; this is
where an operator holds the whole deployment to something shorter, not by lowering a default.
Durations here take either form. 00:05:00 and PT5M are both five minutes. A duration the
loader cannot parse is a startup failure naming the key, and years and months are refused
outright — P1M is not a length of time a token lifetime can be made of. Durations in an agent
registration are ISO 8601 only, because that is what the API contract says.
Admin and audit
Section titled “Admin and audit”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:Admin:ApiKey |
no | — | Enables the admin API. At least 32 characters. A secret |
Onbe:Audit:Sink:Url |
no | — | Where to copy audit records |
Onbe:Audit:Sink:BearerToken |
no | — | Sent to the sink. Requires an HTTPS sink. A secret |
Onbe:Audit:DrainInterval |
no | 5s | 1 second to 1 hour |
Onbe:Audit:DrainBatchSize |
no | 100 | Positive |
Without Admin:ApiKey the admin API is disabled entirely and every /admin request answers
503 — off, rather than open. A sink URL may not carry credentials in its user info, and a
bearer token for a plaintext sink is refused at startup rather than sent in the clear every
drain tick.
Audit chains and aggregation
Section titled “Audit chains and aggregation”| Setting | Required | Default | Bounds |
|---|---|---|---|
Onbe:Audit:Chains |
no | 16 | 1 to 256 |
Onbe:Audit:Aggregation:Enabled |
no | true |
true or false |
Onbe:Audit:Aggregation:Window |
no | PT1M |
PT1S to PT15M |
The ledger is that many hash chains, each with its own lock, and a record is placed on one by the human it names; a denial that names nobody is written once per reason per window and the rest counted into one summary record, so turning aggregation off is logged as a warning.
Rate limiting
Section titled “Rate limiting”| Setting | Required | Default | What it is |
|---|---|---|---|
Onbe:RateLimit:Enabled |
no | true |
A token bucket per source, per instance |
Onbe:RateLimit:PermitsPerMinute |
no | 600 | Sustained requests from one source |
Onbe:RateLimit:Burst |
no | 120 | Most permits the bucket holds. Never below one second of PermitsPerMinute |
Onbe:RateLimit:TrustedProxies |
no | — | Comma-separated CIDR networks whose X-Forwarded-For is believed |
A refused request is 429 with Retry-After and slow_down; /healthz and /readyz are never
limited, and 0.0.0.0/0 is refused as a trusted proxy because it would let any caller pick its
own bucket.
The five secrets
Section titled “The five secrets”The database connection string, the migration connection string, the signing key (Pem), the
admin API key and the sink’s bearer token. None of them is ever logged, returned in an error
response, or committed: the options types that hold them print a fixed placeholder instead of
their contents, so they cannot leak through a log line that formatted an object. The same
applies at runtime to task tokens, task grants and subject tokens.
Checking it
Section titled “Checking it”Onbe.Server doctor reads this configuration exactly as the server would and reports what is
wrong before anything depends on it — see the command line.
Variables that are not Onbe’s
Section titled “Variables that are not Onbe’s”| Variable | What it does |
|---|---|
ASPNETCORE_HTTP_PORTS |
The port the server listens on. The container image sets 5100 |
ASPNETCORE_ENVIRONMENT |
Development is the one host that generates an ephemeral signing key when none is configured |
ONBE_ADMIN_KEY |
Read by agent apply, not by the server |
© 2026 Onbe