State Database
State database
By default, Easy OIDC uses SQLite for persisting OIDC protocol state such as authorization codes, OTPs, and refresh grants. This is the easiest option for single VM deployments and can scale vertically.
However, if you need to horizontally scale Easy OIDC, it also supports using
PostgreSQL instead of SQLite for persisting OIDC protocol state. To enable
this, add a state_database block to your config file:
Configuration
If state_database is omitted, it uses ./data/easy-oidc-state.db e.g:
"state_database": {
"driver": "sqlite",
"path": "/var/lib/easy-oidc/easy-oidc-state.db"
}Set an absolute path in production. SQLite is suitable for one Easy OIDC
replica and scales vertically.
To use PostgreSQL, explicitly configure state_database with the
postgresql driver and relevant settings:
"state_database": {
"driver": "postgresql",
"connection_string_secret": "EASYOIDC_STATE_DB_URL",
"max_connections": 16,
"query_timeout": "5s",
"migrations": {
"connection_string_secret": "EASYOIDC_STATE_MIGRATION_DB_URL"
}
}Run migrations before deployment
Before every new version rollout, run:
easy-oidc migrate --config config.jsonc
Migrations are forward-only. Easy OIDC refuses to start when the schema is missing, dirty, older, or newer than the binary expects.
In the uncommon event of a migration failing:
- stop the rollout;
- investigate the failed statement and restore from backup if necessary; and
- repair dirty migration metadata only after confirming the schema state—never guess a version.
Use separate database roles
For least privilege, give migration and runtime operations different roles:
- Migration role: database
CREATE,USAGE, CREATEonpublic, and ownership ofpublic.schema_migrationsandeasy_oidc_state. - Runtime role: database
CONNECT,USAGEonpublicandeasy_oidc_state,SELECTonpublic.schema_migrations, andSELECT,INSERT,UPDATE,DELETEon every state table.
USAGE on public is still required if its default PUBLIC privilege has been
revoked. Reapply runtime table grants after migrations when needed.
Production checklist
- Require certificate-verified TLS. Plaintext connections are accepted only on loopback addresses for development.
- Set
max_connectionsper replica so their combined pools remain below the PostgreSQL limit, with capacity left for migrations and administration. - Choose a
query_timeoutthat covers expected database latency. It bounds pool waits and queries; an unavailable or exhausted database fails readiness. - Give every replica the same issuer, signing key, encryption key, OTP secret, connector credentials, and state database.
- Back up the state schema and migration metadata together, and test point-in-time recovery. Restore signing and encryption secrets from their secret manager. In-flight logins may be lost at the recovery boundary.
Move from SQLite
There is no online importer:
- Drain traffic and stop all Easy OIDC replicas.
- Let short-lived authorization state expire.
- Configure PostgreSQL and run the migration command.
- Start every replica with the shared configuration and secrets.
- Require users with existing refresh grants to sign in again.
Retire the old SQLite file. Never reuse it as a rollback target because it could revive consumed codes or revoked grants. A rollback must use a new, empty SQLite database, and SQLite and PostgreSQL replicas must never run concurrently.