Skip to content
Development documentation
This describes Keydra as it is being built and is not a released version. What it documents can change before a release.

Configuration reference

Every Keydra configuration property and environment variable, read from the backend's own application.properties.

Configuration properties#

Every keydra.* property, read from backend/src/main/resources/application.properties. A property with an environment variable is one a deployment is expected to set that way.

Property Environment variable Default Description

keydra.alerts.baseline-refresh

5m

How often a rule that compares with a historical window recomputes what that window read. A baseline is a fact about last week; asking the store for it on every reading would be a query per rule per reading for a number that does not move.
Profile: test

keydra.approvals.sweep-interval

KEYDRA_APPROVAL_SWEEP_INTERVAL

5m

How often the instance holding the lease looks for requests whose time has run out. This is the resolution of "it expired", which is a granularity nobody waiting a day notices.

keydra.approvals.ttl

KEYDRA_APPROVAL_TTL

24h

How long an operation waits for a second person before it stops being answerable. A day: long enough that a colleague in another timezone gets to see it, short enough that nobody agrees to a purge against a keyspace they last looked at three weeks ago. It expires rather than being deleted, because the failure this prevents is somebody believing an operation is arranged when it never will be.

keydra.backup.local-root

KEYDRA_BACKUP_DIR

backups

The directory a local backup destination writes inside. A destination names a directory relative to this, so no destination can be pointed at an arbitrary path on the machine.
Profile: test

keydra.cluster.instance-id

KEYDRA_INSTANCE_ID

What this instance calls itself, in the About page, in every log line and on every metric it produces. Generated when it is not set.

keydra.cluster.lease-seconds

KEYDRA_LEASE_SECONDS

15

How long the leader lease is held before it has to be renewed. An instance that stops renewing loses it to whoever asks next, so this is also how long the work can be interrupted by a crash.
Profile: test

keydra.cluster.reconcile-seconds

KEYDRA_RECONCILE_SECONDS

30

How often an instance checks whether it should be doing the leader work — claiming the lease if nobody holds it, and letting go of the schedules if it has lost it.
Profile: test

keydra.connections.health-check-interval

10s

How often a watched target is checked. A change in status is pushed over the notification hub rather than polled by the interface.

keydra.crypto.previous-secrets

KEYDRA_PREVIOUS_SECRET_KEYS

Keys that may still be read but are no longer written. This is what makes a key rotation something other than an outage: the new key writes, the old ones keep decrypting what they wrote, and the re-encryption moves everything across while the instance is up.
Profile: prod
Holds a secret. Never logged, never returned by the API.

keydra.crypto.secret

<redacted>

The key that encrypts every stored credential. There is no default and no fallback: without it there is nothing to decrypt target passwords, tunnel credentials, provider secrets or destination credentials with.
Profile: dev, test, prod
Holds a secret. Never logged, never returned by the API.

keydra.egress.allow-loopback

KEYDRA_EGRESS_ALLOW_LOOPBACK

false

Whether an address somebody typed may point back at the machine Keydra runs on. Off outside development.
Profile: dev, test

keydra.egress.allow-private

KEYDRA_EGRESS_ALLOW_PRIVATE

true

Whether an address somebody typed may be on a private network. Usually yes: a webhook to an internal chat relay and an S3-compatible store on the same subnet are the ordinary cases. Link-local is refused regardless and has no setting.

keydra.egress.allowed-hosts

KEYDRA_EGRESS_ALLOWED_HOSTS

Hosts that are allowed regardless of the rules above. An escape hatch for the one internal address a deployment has to reach.

keydra.http.content-security-policy

KEYDRA_CSP

default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'

The content security policy the browser is told to enforce. A property rather than a literal, so a deployment serving assets from elsewhere can widen the one directive it needs instead of turning the header off. Widening one means restating them all, which is the point: a policy is a whole or it is nothing.
Profile: dev

keydra.invitations.valid-for

KEYDRA_INVITATION_VALID_FOR

P7D

How long an invitation or password-reset link works. A link that works forever is a password with extra steps.

keydra.keys.scripting.enabled

KEYDRA_KEY_SCRIPTING

false

Whether a migration may carry a Lua script that decides what happens to each key. Off by default, and that is the interesting part: everything else runs against somebody’s server, and a script runs inside Keydra, in the process holding every target’s credentials. Turning it off refuses the request outright, whoever is asking.

keydra.keyspace.flush-interval

KEYDRA_KEYSPACE_FLUSH_INTERVAL

2s

How often the keyspace changes that have piled up are sent out. One message per mutation would be a broadcast per write on a server doing thousands a second, to every tab watching it. Lower means a list that catches up sooner; higher means fewer messages for the same news.

keydra.keyspace.lease

KEYDRA_KEYSPACE_LEASE

2m

How long a lease on a keyspace watch is good for. A browser can vanish — a closed laptop, a lost network — so a watch belongs to a lease that is renewed while somebody is looking rather than to a page that may never say goodbye.

keydra.mail.from

KEYDRA_MAIL_FROM

The address outgoing mail is sent from. Most relays refuse a message without one.
Profile: dev

keydra.mail.host

KEYDRA_MAIL_HOST

The SMTP relay outgoing mail goes through — invitations, password resets, and email alert deliveries.
Profile: dev

keydra.mail.language

KEYDRA_MAIL_LANGUAGE

en

What language a letter is written in when the account it is for has never said. An account that has used Keydra has a language preference and that answer wins; a brand-new one has expressed nothing, and an address does not carry a language.

keydra.mail.password

KEYDRA_MAIL_API_KEY

The relay password or API key.
Holds a secret. Never logged, never returned by the API.

keydra.mail.port

KEYDRA_MAIL_PORT

587

The relay port.

keydra.mail.tls

KEYDRA_MAIL_TLS

true

Whether to use TLS to the relay.

keydra.mail.username

KEYDRA_MAIL_USERNAME

The account Keydra authenticates to the relay as.
Profile: dev

keydra.metrics.clickhouse.enabled

false

Whether readings are also written somewhere that survives a restart. Off by default: another service in a deployment is a real cost, and an instance that does not want one must not be told it needs one.
Profile: dev, prod

keydra.metrics.clickhouse.password

KEYDRA_CLICKHOUSE_PASSWORD

The ClickHouse password.
Holds a secret. Never logged, never returned by the API.

keydra.metrics.clickhouse.retention-days

30

How long readings are kept. Enforced by ClickHouse’s own TTL rather than by a policy Keydra invents.

keydra.metrics.clickhouse.url

KEYDRA_CLICKHOUSE_URL

http://localhost:8123

The ClickHouse HTTP interface. HTTP rather than the JDBC driver, which is blocking — and this application is not.
Profile: dev

keydra.metrics.clickhouse.username

KEYDRA_CLICKHOUSE_USER

default

The ClickHouse user.

keydra.public-url

KEYDRA_PUBLIC_URL

Where Keydra is, as a browser sees it. A provider’s redirect and the links in outgoing mail are built from this, so it has to be the address people actually use rather than the one the process binds to.
Profile: dev

keydra.public-url-from-request

false

Whether the public address may be worked out from the request when none is configured. Convenient in development and a header somebody else controls in production.
Profile: dev, test

keydra.reachability.history-kept

KEYDRA_REACHABILITY_HISTORY

90d

How long the record of a reachability change is kept. Changes rather than answers, so this is a handful of rows a year for something that works. Not forever: what happened three years ago to a destination that has been replaced twice is not evidence of anything.

keydra.reachability.interval

KEYDRA_REACHABILITY_INTERVAL

10m

How often the instance holding the chores asks the identity providers and backup destinations whether they answer. Not on page load: ten people watching the status page would be ten times the outbound traffic of one, aimed at somebody else’s service. Zero turns the asking off.
Profile: test

keydra.reachability.minimum-gap

KEYDRA_REACHABILITY_MIN_GAP

30s

How soon after an answer the page’s Check now button may ask again. A button that can be held down is a way to make Keydra hammer somebody else’s service.

keydra.security.cookie-secure

KEYDRA_COOKIE_SECURE

true

Whether the session cookie is marked Secure. On in production: a session cookie sent over plain HTTP is a session cookie on the wire.
Profile: dev, test

keydra.security.dev-administrator.password

<redacted>

The password for that development administrator.
Profile: dev
Holds a secret. Never logged, never returned by the API.

keydra.security.dev-administrator.username

admin

An administrator seeded at start, for development only. Never set this in production: it is an account whose password is in the configuration.
Profile: dev

keydra.security.enabled

KEYDRA_SECURITY_ENABLED

true

Whether Keydra enforces who may do what. Turning it off admits everybody who can reach the address, with every permission, and every page then says so — an open instance that looks secured is how one ends up exposed.
Profile: dev, test

keydra.security.geoip-database

KEYDRA_GEOIP_DATABASE

A GeoIP database, if one is available. Used to say where a sign-in came from when comparing it with the ones before it.

keydra.security.sign-in.dormant-after

P60D

How long an account has to be unused before a sign-in to it is worth remarking on.

keydra.security.sign-in.max-failures-per-account

8

How many failures against one account the window allows. A refusal never counts towards the limit that produced it — otherwise knocking repeatedly would hold a named account shut.

keydra.security.sign-in.max-failures-per-network

40

How many failures from one network the window allows.

keydra.security.sign-in.retention

P90D

How long the record of sign-in attempts is kept.

keydra.security.sign-in.suspicious-accounts

5

How many distinct accounts one source may sign in to before that itself is the anomaly.

keydra.security.sign-in.suspicious-failures-before

5

How many failures immediately before a successful sign-in make that success worth flagging. A stolen password is a correct password, so the successes are what get compared.

keydra.security.sign-in.suspicious-volume

50

How many sign-ins in the window count as a rate that suggests something automated.

keydra.security.sign-in.throttle-enabled

KEYDRA_SIGN_IN_THROTTLE

true

Whether repeated sign-in failures are counted and refused. The limit is answered before the password hash, because Argon2id is slow on purpose and unlimited attempts consume the server’s memory as well as guessing a password.

keydra.security.sign-in.travel-allowance

PT2H

How fast somebody would have had to travel between two sign-ins for the pair to be impossible. Needs a GeoIP database to mean anything.

keydra.security.sign-in.window

PT15M

The rolling window failures are counted over.

keydra.security.socket-origin-check

KEYDRA_SOCKET_ORIGIN_CHECK

true

Whether a WebSocket handshake must come from an origin Keydra recognizes. A socket is not covered by the same-origin policy the way a fetch is, so this is the check that replaces it.

keydra.security.socket-origins

KEYDRA_SOCKET_ORIGINS

The origins a WebSocket may be opened from, beyond the public URL. For a deployment where the interface is served from somewhere else.
Profile: dev

keydra.sessions.sweep-interval

KEYDRA_SESSION_SWEEP_INTERVAL

1h

How often expired session rows are deleted. A session table nobody prunes is a table that grows for as long as the application runs.

keydra.store.authz-ttl

30s

How long the identity behind a cookie and the permissions it carries are cached. The safety net, not the mechanism: anything that changes who may do what publishes an invalidation, and the TTL only covers the message nobody heard.

keydra.store.url

KEYDRA_STORE_URL

redis://localhost:6481

Keydra’s own Redis, for the shared cache and for re-broadcasting notifications between instances. Never one of your targets: a cache living in a server somebody is browsing is a cache somebody empties with a bulk delete. Leave it unset for a single instance.
Profile: dev

Environment variables#

Every environment variable Keydra’s own configuration reads, with the property it sets.

Environment variable Property Default Description

KEYDRA_ACCESS_LOG

quarkus.http.access-log.enabled

true

Whether every request is logged. A request line carries a path, and a path here can name a key.

KEYDRA_APPROVAL_SWEEP_INTERVAL

keydra.approvals.sweep-interval

5m

How often the instance holding the lease looks for requests whose time has run out. This is the resolution of "it expired", which is a granularity nobody waiting a day notices.

KEYDRA_APPROVAL_TTL

keydra.approvals.ttl

24h

How long an operation waits for a second person before it stops being answerable. A day: long enough that a colleague in another timezone gets to see it, short enough that nobody agrees to a purge against a keyspace they last looked at three weeks ago. It expires rather than being deleted, because the failure this prevents is somebody believing an operation is arranged when it never will be.

KEYDRA_BACKUP_DIR

keydra.backup.local-root

backups

The directory a local backup destination writes inside. A destination names a directory relative to this, so no destination can be pointed at an arbitrary path on the machine.

KEYDRA_BEHIND_PROXY

quarkus.http.proxy.proxy-address-forwarding

false

Whether to believe the X-Forwarded-* headers. On behind a reverse proxy, and only then: a deployment that trusts them with nothing in front is a deployment where a caller states their own address.

KEYDRA_CLICKHOUSE_ENABLED

keydra.metrics.clickhouse.enabled

true

Whether readings are also written somewhere that survives a restart. Off by default: another service in a deployment is a real cost, and an instance that does not want one must not be told it needs one.

KEYDRA_CLICKHOUSE_PASSWORD

keydra.metrics.clickhouse.password

The ClickHouse password.

KEYDRA_CLICKHOUSE_URL

keydra.metrics.clickhouse.url

http://localhost:8123

The ClickHouse HTTP interface. HTTP rather than the JDBC driver, which is blocking — and this application is not.

KEYDRA_CLICKHOUSE_USER

keydra.metrics.clickhouse.username

default

The ClickHouse user.

KEYDRA_COOKIE_SECURE

keydra.security.cookie-secure

true

Whether the session cookie is marked Secure. On in production: a session cookie sent over plain HTTP is a session cookie on the wire.

KEYDRA_CSP

keydra.http.content-security-policy

default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; object-src 'none'

The content security policy the browser is told to enforce. A property rather than a literal, so a deployment serving assets from elsewhere can widen the one directive it needs instead of turning the header off. Widening one means restating them all, which is the point: a policy is a whole or it is nothing.

KEYDRA_DB_PASSWORD

quarkus.datasource.password

The database password.

KEYDRA_DB_URL

quarkus.datasource.reactive.url

postgresql://localhost:5432/keydra

The reactive PostgreSQL URL, as postgresql://host:port/database. No JDBC prefix — the application is non-blocking and uses a reactive driver.

KEYDRA_DB_USERNAME

quarkus.datasource.username

keydra

The database user.

KEYDRA_EGRESS_ALLOW_LOOPBACK

keydra.egress.allow-loopback

false

Whether an address somebody typed may point back at the machine Keydra runs on. Off outside development.

KEYDRA_EGRESS_ALLOW_PRIVATE

keydra.egress.allow-private

true

Whether an address somebody typed may be on a private network. Usually yes: a webhook to an internal chat relay and an S3-compatible store on the same subnet are the ordinary cases. Link-local is refused regardless and has no setting.

KEYDRA_EGRESS_ALLOWED_HOSTS

keydra.egress.allowed-hosts

Hosts that are allowed regardless of the rules above. An escape hatch for the one internal address a deployment has to reach.

KEYDRA_GEOIP_DATABASE

keydra.security.geoip-database

A GeoIP database, if one is available. Used to say where a sign-in came from when comparing it with the ones before it.

KEYDRA_INSTANCE_ID

keydra.cluster.instance-id

What this instance calls itself, in the About page, in every log line and on every metric it produces. Generated when it is not set.

KEYDRA_INVITATION_VALID_FOR

keydra.invitations.valid-for

P7D

How long an invitation or password-reset link works. A link that works forever is a password with extra steps.

KEYDRA_JSON_LOGS

quarkus.log.console.json.enabled

true

JSON on the console instead of the human-readable format, so a log shipper does not have to parse a layout written for a person.

KEYDRA_KEY_SCRIPTING

keydra.keys.scripting.enabled

false

Whether a migration may carry a Lua script that decides what happens to each key. Off by default, and that is the interesting part: everything else runs against somebody’s server, and a script runs inside Keydra, in the process holding every target’s credentials. Turning it off refuses the request outright, whoever is asking.

KEYDRA_KEYSPACE_FLUSH_INTERVAL

keydra.keyspace.flush-interval

2s

How often the keyspace changes that have piled up are sent out. One message per mutation would be a broadcast per write on a server doing thousands a second, to every tab watching it. Lower means a list that catches up sooner; higher means fewer messages for the same news.

KEYDRA_KEYSPACE_LEASE

keydra.keyspace.lease

2m

How long a lease on a keyspace watch is good for. A browser can vanish — a closed laptop, a lost network — so a watch belongs to a lease that is renewed while somebody is looking rather than to a page that may never say goodbye.

KEYDRA_LEASE_SECONDS

keydra.cluster.lease-seconds

15

How long the leader lease is held before it has to be renewed. An instance that stops renewing loses it to whoever asks next, so this is also how long the work can be interrupted by a crash.

KEYDRA_MAIL_API_KEY

keydra.mail.password

The relay password or API key.

KEYDRA_MAIL_FROM

keydra.mail.from

The address outgoing mail is sent from. Most relays refuse a message without one.

KEYDRA_MAIL_HOST

keydra.mail.host

The SMTP relay outgoing mail goes through — invitations, password resets, and email alert deliveries.

KEYDRA_MAIL_LANGUAGE

keydra.mail.language

en

The language an invitation is written in when the account it is for has never said which one it reads. en or tr.

KEYDRA_MAIL_PORT

keydra.mail.port

587

The relay port.

KEYDRA_MAIL_TLS

keydra.mail.tls

true

Whether to use TLS to the relay.

KEYDRA_MAIL_USERNAME

keydra.mail.username

The account Keydra authenticates to the relay as.

KEYDRA_MAX_BODY_SIZE

quarkus.http.limits.max-body-size

25M

The largest request body accepted. This is what bounds a key import and a backup restore.

KEYDRA_OIDC_CLIENT_ID

quarkus.oidc.client-id

keydra

The client id at that provider.

KEYDRA_OIDC_ROLES_CLAIM

quarkus.oidc.roles.role-claim-path

realm_access/roles

Where the roles are in the token, as a path. A claim of viewer, operator or admin becomes a grant of that role on the instance.

KEYDRA_OIDC_SECRET

quarkus.oidc.credentials.secret

The client secret at that provider.

KEYDRA_OIDC_URL

quarkus.oidc.auth-server-url

The issuer of a single OIDC provider configured at boot. Providers added in the interface are the supported path and need none of these four; this is for a deployment that configures one before there is anybody to sign in and add it.

KEYDRA_OTLP_ENDPOINT

quarkus.otel.exporter.otlp.endpoint

Where OpenTelemetry traces are exported. Setting it is what turns tracing on; there is no second flag.

KEYDRA_PREVIOUS_SECRET_KEYS

keydra.crypto.previous-secrets

Keys that may still be read but are no longer written. This is what makes a key rotation something other than an outage: the new key writes, the old ones keep decrypting what they wrote, and the re-encryption moves everything across while the instance is up.

KEYDRA_PUBLIC_URL

keydra.public-url

Where Keydra is, as a browser sees it. A provider’s redirect and the links in outgoing mail are built from this, so it has to be the address people actually use rather than the one the process binds to.

KEYDRA_REACHABILITY_HISTORY

keydra.reachability.history-kept

90d

How long the record of a reachability change is kept. Changes rather than answers, so this is a handful of rows a year for something that works. Not forever: what happened three years ago to a destination that has been replaced twice is not evidence of anything.

KEYDRA_REACHABILITY_INTERVAL

keydra.reachability.interval

10m

How often one instance asks the identity providers and backup destinations whether they answer. Zero turns the asking off.

KEYDRA_REACHABILITY_MIN_GAP

keydra.reachability.minimum-gap

30s

How soon after an answer the status page’s Check now button may ask again.

KEYDRA_RECONCILE_SECONDS

keydra.cluster.reconcile-seconds

30

How often an instance checks whether it should be doing the leader work — claiming the lease if nobody holds it, and letting go of the schedules if it has lost it.

KEYDRA_SECRET_KEY

keydra.crypto.secret

The key that encrypts every stored credential — target passwords, tunnel keys, provider secrets, destination credentials. 32 random bytes, base64. There is no default: without it, nothing that was stored can be read.

KEYDRA_SECURITY_ENABLED

keydra.security.enabled

true

Whether Keydra enforces who may do what. Turning it off admits everybody who can reach the address, with every permission, and every page then says so — an open instance that looks secured is how one ends up exposed.

KEYDRA_SESSION_SWEEP_INTERVAL

keydra.sessions.sweep-interval

1h

How often expired session rows are deleted. A session table nobody prunes is a table that grows for as long as the application runs.

KEYDRA_SIGN_IN_THROTTLE

keydra.security.sign-in.throttle-enabled

true

Whether repeated sign-in failures are counted and refused. The limit is answered before the password hash, because Argon2id is slow on purpose and unlimited attempts consume the server’s memory as well as guessing a password.

KEYDRA_SOCKET_ORIGIN_CHECK

keydra.security.socket-origin-check

true

Whether a WebSocket handshake must come from an origin Keydra recognizes. A socket is not covered by the same-origin policy the way a fetch is, so this is the check that replaces it.

KEYDRA_SOCKET_ORIGINS

keydra.security.socket-origins

The origins a WebSocket may be opened from, beyond the public URL. For a deployment where the interface is served from somewhere else.

KEYDRA_STORE_URL

keydra.store.url

redis://localhost:6481

Keydra’s own Redis, for the shared cache and for re-broadcasting notifications between instances. Never one of your targets: a cache living in a server somebody is browsing is a cache somebody empties with a bulk delete. Leave it unset for a single instance.

KEYDRA_TRUSTED_PROXIES

quarkus.http.proxy.trusted-proxies

Which proxies may set those headers, as addresses or CIDR ranges. Without it, believing them means believing anybody.

Note:Quarkus reads more than these

This table lists the variables Keydra’s configuration names explicitly. Quarkus also maps any property to an environment variable by upper-casing it and replacing every non-alphanumeric character with an underscore, so a Quarkus setting not listed here can still be set that way. Confirm the mapping against your Quarkus version before relying on it for a property with unusual characters in its name.

Edit this page