Configuration reference
InfraInbox reads one typed configuration, built up in this order — each later source overrides the same setting from an earlier one:
- built-in defaults;
- a YAML file (
--config <file>, or the file named byINFRAINBOX_CONFIG); - environment variables — a few plain aliases (
PUBLIC_URL,DATABASE_URL) first, then everyINFRAINBOX_*variable, then the_FILEvariant of any secret setting; - command-line flags, such as
serve --mode.
Every setting has an INFRAINBOX_ environment variable: its YAML path in upper case with dots turned into underscores, so database.url is INFRAINBOX_DATABASE_URL. A handful use a shorter alias instead (noted below). Secret settings (marked secret in the tables) also accept a _FILE variant that names a file to read the value from — prefer it over the plain variable for anything sensitive, since a plain environment variable is readable from docker inspect, a process listing, and the shell history of whoever set it.
To see the configuration InfraInbox actually loaded — every value, its source, and secrets redacted — run:
docker compose exec infrainbox /infrainbox config checkA YAML file mirrors the same nesting, for example:
auth: password: enabled require_mfa: ownersretention: events: 30dDurations are Go-style (90s, 15m, 24h) or a whole number of days (30d); 0 means “keep forever” where that’s meaningful.
Core and networking
Section titled “Core and networking”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_MODE |
self-hosted |
self-hosted or cloud. There is no reason to set this yourself — it’s what makes the same binary power InfraInbox Cloud, and a few settings below (outbound.allow_cgnat, insecure_http, the master key) are refused or required differently under cloud. |
INFRAINBOX_PUBLIC_URL (alias PUBLIC_URL) required |
— | The address browsers and phones reach this server at, scheme and host (and port, unless 443). Sign-in, the pairing QR and every generated ingest URL are built from it. |
INFRAINBOX_INGEST_URL |
same as public_url |
Base URL that generated source configurations (webhook targets, curl snippets) send events to. Only worth setting separately behind some unusual split-DNS setups. |
INFRAINBOX_DATA_DIR |
/var/lib/infrainbox |
Where the master key and any generated TLS certificate live. Back it up with the database. |
INFRAINBOX_LOG_LEVEL |
info |
debug, info, warn or error. Event and notification bodies are logged only at debug. |
INFRAINBOX_SERVE_MODE |
all |
Which components this process runs: all (everything, the only sensible choice for a single self-hosted instance), or api/worker to split them across processes. Also settable as serve --mode. |
INFRAINBOX_SERVE_STOP_GRACE |
30s |
How long shutdown has to finish in-flight work before the process exits. Match your container platform’s stop grace period (Compose’s stop_grace_period: 30s already does). |
INFRAINBOX_MIGRATIONS_AUTO |
true |
Apply pending database migrations automatically when the server starts. Turn this off only if you’d rather run infrainbox migrate up yourself as a separate step — with it off, serve refuses to start while migrations are pending. |
Database
Section titled “Database”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_DATABASE_URL (alias DATABASE_URL) required, secret |
— | The PostgreSQL connection URL, e.g. postgres://infrainbox:pw@postgres:5432/infrainbox. Prefer INFRAINBOX_DATABASE_URL_FILE. |
INFRAINBOX_DATABASE_APP_MAX_CONNS |
20 |
Maximum connections used by the API/ingest side. |
INFRAINBOX_DATABASE_WORKER_MAX_CONNS |
10 |
Maximum connections used by delivery workers and the scheduler. |
The master key
Section titled “The master key”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_MASTER_KEY (prefer _FILE) secret |
generated | 32 bytes, base64 (openssl rand -base64 32). Encrypts every stored secret (destination credentials, push keys, and so on). Left unset, one is generated into <data_dir>/master.key on the first start of a fresh database — back it up, apart from the database dump. |
INFRAINBOX_MASTER_KEY_PREVIOUS (prefer _FILE) secret |
— | Set during a key rotation: the key you’re rotating away from, while master_key holds the new one. See Backups and upgrades. |
HTTP and TLS
Section titled “HTTP and TLS”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_HTTP_LISTEN |
:8443 (:8080 if tls is off) |
Address of the main listener. |
INFRAINBOX_TLS |
on |
on: HTTPS, with cert_file/key_file or a generated self-signed certificate. off: plain HTTP behind a TLS-terminating proxy. |
INFRAINBOX_TLS_CERT_FILE |
— | PEM certificate chain for the main listener. |
INFRAINBOX_TLS_KEY_FILE |
— | PEM private key matching cert_file. Set both or neither. |
INFRAINBOX_TLS_CA_FILE |
— | PEM CA certificate of a private CA; its pin is included in the pairing QR code. |
INFRAINBOX_INSECURE_HTTP |
off |
private: also serve plain HTTP on the HTTPS port, to callers on private-IP ranges only (for LAN phones that can’t validate a private CA). Needs tls: on. |
INFRAINBOX_HTTP_TRUSTED_PROXIES |
none (tls: on) / RFC 1918 + loopback (tls: off) |
IP addresses or CIDR ranges whose X-Forwarded-* headers are trusted for the client’s real IP and scheme. Set this to your reverse proxy’s own address — never a whole subnet — or every rate-limit bucket collapses into one. See TLS and reverse proxies. |
INFRAINBOX_HTTP_TRUSTED_HOSTS (alias TRUSTED_HOSTS) |
none | Extra hostnames (comma-separated, no scheme/port/wildcard) the dashboard and API answer to besides public_url’s. Anything else gets a 421. |
INFRAINBOX_HTTP_READ_HEADER_TIMEOUT |
10s |
Longest wait for a request’s headers. |
INFRAINBOX_HTTP_READ_TIMEOUT |
1m |
Longest time to read a whole request. |
INFRAINBOX_HTTP_WRITE_TIMEOUT |
1m |
Longest time to write a response (the live event stream is exempt). |
INFRAINBOX_HTTP_IDLE_TIMEOUT |
2m |
How long an idle keep-alive connection stays open. |
INFRAINBOX_HTTP_MAX_HEADER_BYTES |
65536 |
Largest request header block, in bytes. |
Metrics
Section titled “Metrics”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_METRICS_LISTEN |
127.0.0.1:9464 |
Address of the internal /metrics listener (Prometheus format). Not published by the compose file by default. |
INFRAINBOX_METRICS_TOKEN secret |
— | Set this to also serve /metrics on the main, public listener, guarded by this bearer token, instead of only the internal one. |
Observability (OpenTelemetry)
Section titled “Observability (OpenTelemetry)”Off by default self-hosted. Set otel.endpoint to export traces to a collector you run yourself (Jaeger, Grafana Tempo, and so on) — see Security and privacy for what this does and doesn’t send anywhere.
| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_OTEL_TRACES |
false (true if otel.endpoint is set) |
Export traces over OTLP. |
INFRAINBOX_OTEL_ENDPOINT (alias OTEL_EXPORTER_OTLP_ENDPOINT) |
— | Your OTLP collector, e.g. https://otel.example.com:4318. |
INFRAINBOX_OTEL_HEADERS (alias OTEL_EXPORTER_OTLP_HEADERS) secret |
— | Headers sent with every OTLP export, key=value pairs separated by commas — often an API key your collector wants. |
Retention
Section titled “Retention”How long data is kept before it’s purged. 0 keeps it forever; open incidents are never purged regardless of retention.incidents.
| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_RETENTION_EVENTS |
30d |
Raw events (the first and last event of a retained incident are always kept). |
INFRAINBOX_RETENTION_INCIDENTS |
90d |
Resolved incidents, counted from resolution. |
INFRAINBOX_RETENTION_DELIVERIES |
30d |
Delivery attempt records. |
INFRAINBOX_RETENTION_RAW_PAYLOADS |
7d |
Raw inbound payloads kept for debugging. |
INFRAINBOX_RETENTION_RAW_PAYLOADS_PER_SOURCE |
20 |
Cap on raw payloads kept per source, on top of the age limit above. |
INFRAINBOX_RETENTION_HEARTBEAT_PINGS_PER_MONITOR |
100 |
Individual heartbeat pings kept per monitor. |
INFRAINBOX_RETENTION_HEARTBEAT_AGGREGATES |
90d |
Daily heartbeat summaries. |
INFRAINBOX_RETENTION_IDEMPOTENCY_KEYS |
1d |
How long a duplicate event with the same idempotency key is still recognized (minimum 24h). |
INFRAINBOX_RETENTION_AUDIT |
365d |
Audit log entries. |
INFRAINBOX_RETENTION_EXPIRED_SESSIONS |
30d |
Expired sessions and pairing tokens, counted from expiry. |
Delivery concurrency
Section titled “Delivery concurrency”How many in-flight sends each worker allows per transport, so one slow provider doesn’t starve the others. (A few more of these keys exist in the configuration loader than are listed here — they belong to destinations this build doesn’t ship yet, so there’s nothing to point them at.)
| Setting | Default |
|---|---|
INFRAINBOX_DELIVERY_CONCURRENCY_TELEGRAM |
8 |
INFRAINBOX_DELIVERY_CONCURRENCY_SLACK |
8 |
INFRAINBOX_DELIVERY_CONCURRENCY_DISCORD |
8 |
INFRAINBOX_DELIVERY_CONCURRENCY_WEBHOOK |
16 |
INFRAINBOX_DELIVERY_CONCURRENCY_PUSH |
32 |
Push notifications
Section titled “Push notifications”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_PUSH_WEBPUSH_VAPID_KEY (prefer _FILE) secret |
auto |
The server’s Web Push signing key. auto generates one on first start and stores it encrypted in the database. |
INFRAINBOX_PUSH_WEBPUSH_SUBJECT |
— | A contact for push services about this server’s traffic: mailto:you@example.com or an https:// URL. |
INFRAINBOX_PUSH_RELAY_ENABLED |
true |
Advertise InfraInbox’s push relay to pairing devices (see Security and privacy). |
INFRAINBOX_PUSH_RELAY_URL |
https://push.infrainbox.app |
The relay’s base URL. |
INFRAINBOX_PUSH_APNS_ENABLED |
false |
Send to Apple Push Notification service directly, with your own key, instead of through the relay — for a self-built iOS client. Needs the three settings below. |
INFRAINBOX_PUSH_APNS_KEY (prefer _FILE) secret |
— | The APNs authentication key (.p8, PEM). |
INFRAINBOX_PUSH_APNS_KEY_ID |
— | The key’s 10-character key ID. |
INFRAINBOX_PUSH_APNS_TEAM_ID |
— | Your Apple developer team ID. |
InfraInbox mails account links — password reset, the confirmation of a changed address — through an SMTP relay you name: a mail provider’s SMTP endpoint, or a local MTA. Without smtp.host nothing is mailed: Forgot password? doesn’t appear, an email change is refused, and infrainbox admin reset-password is how a password gets reset. Mail goes out after the page has answered, so a slow relay never slows sign-in, and the server logs only which kind of mail went out, never the address or the link.
| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_SMTP_HOST |
— | The relay’s host name. Setting it turns account mail on. |
INFRAINBOX_SMTP_PORT |
by tls |
The relay’s port; unset picks 587 for starttls, 465 for tls, 25 for none. |
INFRAINBOX_SMTP_USERNAME |
— | The relay’s user name; empty sends without logging in. |
INFRAINBOX_SMTP_PASSWORD (prefer _FILE) secret |
— | The relay’s password. |
INFRAINBOX_SMTP_TLS |
starttls |
starttls (required, not optional), tls (TLS from the first byte) or none (plain text, for a relay on the same host or network only). |
INFRAINBOX_SMTP_FROM |
— | The sender, e.g. InfraInbox <no-reply@example.com>. Required with a host. |
INFRAINBOX_SMTP_ALERTS_HOST, _PORT, _USERNAME, _PASSWORD, _TLS, _FROM |
— | A separate relay and sender for alert mail, so alert volume can never hold up a password reset. Unset, alert mail uses the relay above. |
To check the relay, run docker compose exec infrainbox /infrainbox config check, then use Forgot password? on the sign-in page with your own address.
Outbound connections
Section titled “Outbound connections”Destinations, webhooks and test sends may always reach public addresses and, self-hosted, private (RFC 1918) ones. Loopback, link-local, multicast and cloud-metadata addresses are never allowed — see Security and privacy.
| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_OUTBOUND_ALLOW_CGNAT |
false |
Also allow carrier-grade NAT addresses (100.64.0.0/10) — needed if a destination lives on a Tailscale tailnet reached that way. |
INFRAINBOX_OUTBOUND_ALLOW_ULA |
false |
Also allow IPv6 unique local addresses (fc00::/7). |
Authentication
Section titled “Authentication”| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_AUTH_PASSWORD |
enabled |
Who may sign in with a password: enabled (everyone), admins_only, or disabled. infrainbox admin reset-password always works regardless, so a broken identity provider never locks you out. |
INFRAINBOX_AUTH_REQUIRE_MFA |
none |
Who must set up a second factor: none, owners (workspace owners and instance admins), or all. |
INFRAINBOX_AUTH_PASSWORD_HASH_PROFILE |
default |
Argon2id cost: default (fine down to a Raspberry Pi / 1 GB host) or large (bigger hosts). |
INFRAINBOX_AUTH_PASSWORD_HASH_MEMORY_BUDGET_MIB |
128 |
Total memory concurrent password hashing may use; sign-ins beyond it wait briefly, then get rate-limited. |
INFRAINBOX_AUTH_ADMIN_EMAIL |
— | Seeds the first instance administrator’s email at start, for an unattended install. Only applies while the instance has no user yet. |
INFRAINBOX_AUTH_ADMIN_PASSWORD_FILE secret, file only |
— | The seeded administrator’s password. There’s deliberately no plain-variable form for this one. |
Sign in with an identity provider
Section titled “Sign in with an identity provider”Each entry in auth.providers is an OpenID Connect provider (Authentik, Keycloak, Authelia, Pocket ID and the like) or GitHub that people can sign in through. It’s a list, so it lives in the config file:
auth: providers: - id: authentik # part of the callback URL; keep it stable type: oidc # oidc or github name: Authentik # the button's label (default: the id; "GitHub" for github) issuer: https://auth.example.com/application/o/infrainbox/ client_id: infrainbox client_secret_file: /run/secrets/oidc # optional for oidc (a public client uses PKCE); required for github auto_create: allowed_domains # off (default), invite, allowed_domains or on allowed_email_domains: [example.com] scopes: [groups] # extra scopes; openid, email and profile are always asked for-
Callback URL. Register
https://infrainbox.example.com/v1/auth/oidc/<id>/callbackat the provider, built fromINFRAINBOX_PUBLIC_URL. For GitHub, that’s the callback of a GitHub OAuth App. -
Who gets an account. A first sign-in creates an account only if
auto_createallows it:offnever (an administrator creates or invites the person first),invitefor an address with a pending invite,allowed_domainsfor a verified address inallowed_email_domains,onalways. The first administrator is always created with the setup link: until setup is done, no provider creates an account. -
Matching. An account is found by the provider’s own subject, never by email, so an address changed at the provider can’t take over someone else’s account.
-
Keep ids stable. Changing a provider’s
issuerortypeunder an id that already has accounts stops the server from starting. Add the new provider under a new id instead. -
Plain HTTP. An
http://issuer passes the server’s checks but logs a warning on every start: the provider’s keys and tokens then travel unencrypted. The dashboard’s sign-in page only sends the browser to anhttps://authorization address, so the provider’s own pages must be served over HTTPS. -
GitHub and sensitive actions. GitHub can’t re-confirm a sensitive action; someone who signs in only with GitHub is asked to add a passkey or TOTP for that.
-
Check it.
infrainbox admin idp test <id>fetches the provider’s discovery document and signing keys and says what’s wrong. -
On the sign-in page. Each provider gets a “Continue with
” button under the password form; with auth.password: disabledthe buttons are all there is. An account with TOTP is asked for its code after the provider, as after a password. A refused sign-in comes back with one plain reason — no account andauto_createdoesn’t allow one, the address belongs to an account the identity isn’t linked to, the provider couldn’t be reached — and the details are in the audit log only. -
On the setup page. The first administrator can be your identity at a provider instead of a password: open the setup link, fill in the workspace, and choose “Continue with
”. The setup token is spent when the provider sends you back.
One OpenID Connect provider can also come from the environment, for an install without a config file. Its id is oidc, and it’s on when the issuer is set:
| Setting | Default | Meaning |
|---|---|---|
INFRAINBOX_OIDC_ISSUER |
— | The issuer URL, exactly as the provider’s discovery document states it. |
INFRAINBOX_OIDC_CLIENT_ID |
— | The client ID registered at the provider. |
INFRAINBOX_OIDC_CLIENT_SECRET_FILE secret, file only |
— | The client secret. Leave it unset for a public client. |
INFRAINBOX_OIDC_NAME |
SSO |
The button’s label. |
INFRAINBOX_OIDC_AUTO_CREATE |
off |
As auto_create above. |
INFRAINBOX_OIDC_ALLOWED_EMAIL_DOMAINS |
— | Comma-separated, as allowed_email_domains above. |
Workspace limits
Section titled “Workspace limits”Per-workspace quotas. 0 means no limit, which is the self-hosted default for all of them.
| Variable | Default | Meaning |
|---|---|---|
INFRAINBOX_QUOTAS_EVENTS_PER_DAY |
0 |
Events a workspace may ingest per UTC day. Past it, ingest answers HTTP 429 with Retry-After until midnight UTC. |
INFRAINBOX_QUOTAS_SOURCES |
0 |
Sources a workspace may have. |
INFRAINBOX_QUOTAS_MONITORS |
0 |
Heartbeat monitors a workspace may have. |
INFRAINBOX_QUOTAS_DESTINATIONS |
0 |
Destinations, not counting the built-in push one. |
INFRAINBOX_QUOTAS_MEMBERS |
0 |
Members of a workspace. |
INFRAINBOX_QUOTAS_DELIVERIES_PER_DAY |
0 |
Notifications a workspace may have sent per UTC day, across every destination. Re-notify and fallback sends count. Past it, a notice is recorded skipped with reason quota rather than sent. |
INFRAINBOX_QUOTAS_DELIVERIES_PER_DESTINATION_PER_DAY |
0 |
The same, for any one destination on its own. |
INFRAINBOX_QUOTAS_NEW_WORKSPACE_DELIVERIES_PER_DAY |
0 |
A lower daily notification total for a workspace’s first seven days. A per-workspace override wins over it. |
INFRAINBOX_QUOTAS_WORKSPACES |
0 |
Workspaces one account may own. It does not limit the workspaces somebody is invited into. |
Creating one more of something past its limit answers HTTP 409 quota_exceeded. infrainbox admin workspaces limits <workspace> shows one workspace’s limits, and its flags (--events-per-day, --deliveries-per-day, --sources, … and --clear) override the defaults for that workspace alone. Every workspace’s counts against its limits are on its Usage and limits panel.
Rate limits
Section titled “Rate limits”Per-instance token buckets; a request beyond the limit gets HTTP 429 with Retry-After. Each row is INFRAINBOX_RATELIMIT_<NAME>_PER_MINUTE and ..._BURST.
| Bucket | Default (per minute / burst) | Applies to |
|---|---|---|
INGEST_KEY |
600 / 300 | One source’s ingest key. |
INGEST_WORKSPACE |
3000 / 1000 | A whole workspace’s ingest traffic. |
AUTH_FAILURES |
30 / 20 | Failed credentials, per client IP. |
MANAGEMENT |
1200 / 200 | The management API, per signed-in user. |
PUBLIC |
120 / 60 | Unauthenticated routes (/v1/info, sign-in pages), per client IP. |
DESTINATION_TEST / DESTINATION_TEST_WORKSPACE |
6 / 3 and 30 / 10 | “Send test” on a destination. |
TELEGRAM_DISCOVER |
30 / 10 | The Telegram chat-discovery poll while setting up that destination. |
SOURCE_TEST |
6 / 3 | “Test” on a source. |
HEARTBEAT_MONITOR |
60 / 10 | Pings to one heartbeat monitor. |
AUTH_LOGIN |
30 / 15 | POST /v1/auth/login, per client IP. |
AUTH_TOTP |
30 / 15 | TOTP code verification. |
AUTH_REAUTH |
30 / 15 | Step-up re-authentication. |
AUTH_SETUP |
20 / 10 | First-run setup. |
AUTH_SIGNUP |
10 / 10 | Cloud sign-up, and opening a link that confirms an address, per client IP. |
AUTH_RESET |
10 / 10 | Asking for a password reset link, and using one, per client IP. |
AUTH_MAIL |
1 / 3 | Account mail to one address or account — reset links, re-sent confirmations — whoever asks. |
Development-only settings
Section titled “Development-only settings”dev.telegram_api_url, dev.apns_url, dev.fcm_url and dev.relay_url point delivery transports at a local test double, and dev.github_url does the same for GitHub sign-in, instead of the real provider. They exist for InfraInbox’s own CI and are refused outright if mode is cloud. Don’t set these on a real instance — doing so sends provider traffic (including credentials) to whatever URL you named, and the server logs a warning every time it starts with one set.