Configuration

kernoid reads one TOML file (--config <file> or KERNOID_CONFIG). Every key can be set or overridden by an environment variable: KERNOID__A__B=value sets a.b – two underscores separate the levels. Values are read as TOML where they parse (true, 10, ["api"]), as strings otherwise. Unknown keys are an error, so a misspelt key does not go unnoticed. kernoid check-config prints what kernoid read, with secrets hidden; secrets never appear in logs.

roles = ["all"]

[http]
listen = "0.0.0.0:8080"
trusted_proxies = ["10.0.0.0/8"]

[database]
url = "postgres://kernoid_app:…@db/kernoid?sslmode=verify-full"
migrate_url = "postgres://kernoid_owner:…@db/kernoid?sslmode=verify-full"

[blob]
kind = "s3"
endpoint = "http://garage:3900"
region = "garage"
bucket = "kernoid"
access_key_id = "…"
secret_access_key = "…"
allow_http = true

[encryption]
main_key = "…"   # 64 hex characters: openssl rand -hex 32

Keys

Key Default Meaning
roles ["all"] what this process does: api (HTTP), worker (background work), smtp (the MX and submission), imap (IMAP and POP3), all
http.listen 0.0.0.0:8080 address and port to listen on
http.tls.certificate, http.tls.key – PEM files; with them kernoid serves HTTPS itself (TLS 1.2 and 1.3, HTTP/2)
http.trusted_proxies [] addresses or networks whose Forwarded / X-Forwarded-* headers are believed
http.metrics_listen – serve Prometheus metrics at /metrics on this address – an internal one, it is not protected
http.login_ui /usr/share/kernoid/login the built login UI, served at /login; the container image has it there
http.web_app /usr/share/kernoid/app the built web app (mail, calendar, contacts), served at /app; the container image has it there
http.public_scheme https how instances are reached from outside; http only for trying kernoid out. Issuer and links are built from it and the instance’s primary domain
database.url required connection URL of the runtime role
database.migrate_url – connection URL of the role that owns the schema and runs kernoid migrate; leave out only if the runtime role owns the schema – then it could change events
database.max_connections 10  
database.statement_timeout_ms 30000 longest time one statement may run; 0 for no limit
database.idle_in_transaction_timeout_ms 10000 longest time a transaction may stay open without a statement; 0 for no limit
blob.kind required s3, or memory (lost on exit – for tests only)
blob.endpoint – S3 endpoint URL; leave out for AWS
blob.region us-east-1  
blob.bucket, blob.access_key_id, blob.secret_access_key required for s3  
blob.allow_http false allow an endpoint over plain HTTP, e.g. object storage on the same host
encryption.main_key required 64 hex characters (256 bit); wraps the tenant keys and the signing keys
encryption.previous_main_keys [] earlier main keys, only to read what is not rewrapped yet
modules.<name>.enabled false switches a compiled-in module on for the installation; the rest of the table is the module’s own configuration
outbound.allow_private_networks false let kernoid reach identity providers and back-channel logout URIs at private, loopback and link-local addresses and over plain HTTP – for an own Keycloak in the network; only where the tenants are trusted with that
http.acme.contact [] contact URIs for the ACME CA, e.g. mailto:ops@example.org
http.acme.terms_agreed false must be true to use ACME: you agree to the CA’s terms of service
http.acme.directory Let’s Encrypt the CA’s ACME directory URL
http.acme.http_listen 0.0.0.0:80 where the CA’s HTTP-01 requests arrive; other requests there are redirected to HTTPS
http.acme.renew_days 30 renew a certificate this many days before it ends
http.acme.ca_root – a further root certificate (PEM) to trust for the CA – for a test CA
oidc.key_rotation_days 90 a signing key signs this long, then the worker rotates it; 0 turns rotation off
oidc.key_publish_hours 24 a new signing key is published this long before it signs
oidc.retired_key_days 30 a key that no longer signs stays published this long (at least an hour)
mail.max_message_size 52428800 largest message accepted, in bytes (50 MiB)
mail.notification_from – the sender of kernoid’s own messages – registration codes – on a domain kernoid sends for; without it, people register only with identity providers
mail.tls.certificate, mail.tls.key – PEM files for SMTP, submission, IMAP and POP3, e.g. those of a reverse proxy such as Caddy; read again within a minute when they change. Without them: ACME’s certificates, else http.tls
mail.changes_kept_days 30 how long the journal of mail changes is kept; a client away longer (JMAP, IMAP) loads the mailbox anew – the journals of calendars, contacts and files as well
files.max_file_size 10737418240 largest file accepted, in bytes (10 GiB); content streams to object storage in parts
files.versions_kept 10 earlier contents kept per file
smtp.hostname – receiving mail is on when [smtp] is set: the host name the tenants’ MX records point to, used in the greeting and in Received and Authentication-Results
smtp.listen 0.0.0.0:25 where the MX listens (process role smtp)
smtp.submission_listen 0.0.0.0:587 submission with STARTTLS; only with certificates
smtp.submissions_listen 0.0.0.0:465 submission with TLS from the start; only with certificates
smtp.max_recipients 100 recipients per message
smtp.max_connections 1000 sessions at once; more connections wait
smtp.max_received 50 more Received headers than this means a loop: refused
smtp.junk_score 5.0 mail with at least this spam score goes to Junk
smtp.timeout_seconds 300 how long the server waits for a command or data
smtp.queue_days 5 how long outgoing mail is tried before it bounces
smtp.spf – the SPF record the DNS check suggests instead of v=spf1 mx -all, e.g. with a relay’s include:
smtp.relay.host, smtp.relay.port –, 587 hand all mail for other servers to this relay, over TLS with a valid certificate
smtp.relay.implicit_tls false TLS from the start (port 465) instead of STARTTLS
smtp.relay.username, smtp.relay.password – the login at the relay
imap.listen 0.0.0.0:143 mail clients are served when [imap] is set (process role imap): IMAP with STARTTLS; only with certificates
imap.tls_listen 0.0.0.0:993 IMAP with TLS from the start
imap.pop3_listen, imap.pop3_tls_listen – POP3 with STLS and with TLS from the start, e.g. 0.0.0.0:110 and 0.0.0.0:995; off unless set
imap.max_connections 5000 sessions at once per listener; more connections wait
imap.timeout_seconds 1800 a session that says nothing this long – IDLE included – is logged out
log.filter – e.g. info,kernoid=debug (syntax of tracing_subscriber::EnvFilter)
log.format text text or json
log.otlp_endpoint – send traces to an OpenTelemetry collector over OTLP/HTTP, e.g. http://otel-collector:4318/v1/traces

Without encryption.main_key the content in object storage cannot be read again. Keep a copy apart from the database and the object storage.

The same as environment variables, e.g. for a container:

KERNOID__DATABASE__URL=postgres://…
KERNOID__ENCRYPTION__MAIN_KEY=…
KERNOID__HTTP__TRUSTED_PROXIES='["172.16.0.0/12"]'

There are no modules compiled in yet – the first ones are planned for 1.0 (Jitsi, WOPI, virus scanning). A table [modules.<name>] for a module that does not exist is an error.