Authentication
By the end of this page you will have a role with a password, a working psql connection over it, and a host-based rule file that controls who gets asked for that password in the first place.
The one method: SCRAM-SHA-256β
ScramDB authenticates every password-protected connection with SCRAM-SHA-256, the same mechanism PostgreSQL itself defaults to. There is no separate md5 method, no LDAP, Kerberos, GSSAPI, or OAuth integration, and no client-certificate authentication. Passwords are hashed with PBKDF2-HMAC-SHA256 before storage; the plaintext password is never persisted, and pg_roles never exposes the hash.
A role can only authenticate by password if both of these are true:
- It was created (or altered) with
LOGIN. A role withoutLOGIN(the default for a bareCREATE ROLE) is refused at connection time with "role ... is not permitted to log in". - It has a password set. A
LOGINrole with no password set is refused with "password authentication failed for user ...".
Step by step: create a role and connectβ
-
Connect as the bootstrap superuser. A fresh ScramDB instance creates a superuser role named
scramdbautomatically on first startup:psql -h localhost -p 5432 -U scramdb scramdbNo password prompt: the superuser starts with no credential at all, and the default host-based rule trusts
127.0.0.1outright. If your only path to the node isn't localhost, for example a Kubernetes pod reachable solely throughkubectl exec, see Seeding a password for the bootstrap superuser below. -
Create a role with a password and permission to log in:
CREATE ROLE app_user LOGIN PASSWORD 'a-real-password';Expected output:
CREATE ROLE. -
Confirm it landed, without exposing the password:
SELECT rolname, rolcanlogin, rolpassword FROM pg_roles WHERE rolname = 'app_user';Expected output: one row,
rolcanloginist,rolpasswordisNULL(ScramDB never surfaces the hash through this view). -
Connect as the new role from a fresh terminal:
psql -h localhost -p 5432 -U app_user scramdbpsqlprompts for the password you set in step 2. On success you land at thescramdb=>prompt.
If it fails: connecting from a non-localhost address before you've set up a password or a matching rule fails with password authentication failed. Either set a password on the role (step 2) or add a rule for that address in hba_file (below). If the server itself is running with --pg-no-auth, every connection is trusted with no password prompt at all, roles and passwords notwithstanding; see Security overview and Docker: Securing a node.
Host-based rules (hba_file)β
hba_file (under [general] in the config TOML) points at a pg_hba.conf-style file that decides, per incoming connection, whether it's trusted outright, asked for a password, or rejected. Rules are evaluated top to bottom; the first matching line wins, and a connection matching no line is rejected.
Each line has the shape:
<conn_type> <database> <user> <address> <method>
| Field | Accepted values |
|---|---|
conn_type | host, hostssl (TLS required), hostnossl (TLS forbidden). ScramDB is TCP-only today: local lines are parsed but never match a connection, so a Unix-socket rule has no effect. |
database | all, or one exact database name. No group or comma-list syntax. |
user | all, or one exact role name. No group or comma-list syntax, and no working sameuser keyword: writing sameuser matches a role literally named sameuser, not "the role matching the database name" the way PostgreSQL's real keyword does. |
address | An IPv4 or IPv6 CIDR, for example 10.0.0.0/8 or ::/0. No hostname form. |
method | trust (no password), password (SCRAM-SHA-256 handshake), or reject. md5 and scram-sha-256 are also accepted spellings for the same password check as password; ScramDB always performs the SCRAM-SHA-256 handshake regardless of which of the three you write. An unrecognized method keyword makes that one line silently skipped, not a config error, so double-check the keyword if a rule you added doesn't seem to take effect. |
Step by step: write and load a rule fileβ
-
Create a rule file that trusts your office network outright and requires a password (over TLS) from everywhere else:
# /etc/scramdb/pg_hba.confhost all all 10.0.0.0/8 trusthostssl all all 0.0.0.0/0 passwordhostssl all all ::/0 password -
Point
hba_fileat it in your config TOML:[general]hba_file = "/etc/scramdb/pg_hba.conf" -
Restart ScramDB and check the log for a clean startup. A missing or unreadable path is a fatal error, not a silent fallback: the server logs
FATAL: hba_file '<path>' unreadable: <reason>and exits immediately, so a typo'd path is loud rather than a surprise open door. -
Test from a client inside
10.0.0.0/8: connect withpsqland confirm you land at the prompt with no password request. Test from outside that range oversslmode=require: confirm you're prompted for a password.
If it fails: a connection that matches no line at all is rejected outright. If a client you expect to work is being rejected, check the file top to bottom for the first line that could match its address, database, and user, and remember all/exact-name only, no groups.
What happens with no hba_file setβ
Leaving hba_file empty (the default) is not "no rules": ScramDB installs a built-in default equivalent to:
host all all 127.0.0.1/32 trust
host all all ::1/128 trust
host all all 0.0.0.0/0 password
host all all ::/0 password
In other words: localhost is trusted, everything else needs a password. This is the same built-in ruleset the Docker deployment guide describes.
What happens with --pg-no-authβ
--pg-no-auth (or pg_no_auth = true in [general]) disables authentication entirely and ignores hba_file completely, even if one is configured. Every connection, from any address, to any database, as any user, is trusted with no password check. This is the flag the published Docker image sets by default; see Security overview for why that matters and how to turn it off.
Seeding a password for the bootstrap superuserβ
The connection in step 1 above works with no password because of the default hba_file rule above: 127.0.0.1 is trusted, nothing else is. That's a safe default, not a broken one, right up until you need to reach the superuser from somewhere that isn't localhost. The most common reason is a container orchestrator: a Helm or kustomize install has no localhost to run psql from except kubectl exec, so the first connection to a chart-driven cluster needs a credential seeded in before anyone can reach it.
Three environment variables seed that first credential, read once at startup and never again once the superuser has one:
SCRAMDB_INITIAL_PASSWORD_FILE(recommended): a path to a file whose first line is the password, typically a mounted Kubernetes Secret or Docker secret. The value itself never appears in the pod spec or/proc/<pid>/environ.SCRAMDB_INITIAL_PASSWORD: the literal password value. Simpler to wire up, but weaker: it's visible inkubectl describe podand/proc/<pid>/environ.SCRAMDB_RANDOM_INITIAL_PASSWORD: set to exactlyyesto have ScramDB generate a strong random password and log it exactly once at first boot, the same convention MySQL's--initializeuses.
Exactly one may be set. More than one fails startup rather than picking a winner: more than one initial superuser password source is set (...); unset all but one - ambiguity about which credential is live is refused, never resolved by precedence.
The guarantee that matters most for a security-sensitive credential: seeding only ever runs while the superuser has no credential yet. ScramDB checks that before it looks at any of the three variables, so a redeployed pod, a rotated Secret, or a leftover environment variable can never reset a password you've already set, and can never lock you out. Every restart after the first is a no-op.
For the full precedence, the exact file-parsing rules, the empty-password refusal, and copy-pasteable Kubernetes and Docker examples, see Environment Variables: Seeding the superuser password.
Managing roles and passwordsβ
-- Change a role's password
ALTER ROLE app_user PASSWORD 'a-new-password';
-- Clear a role's password (it can no longer authenticate by password)
ALTER ROLE app_user PASSWORD NULL;
-- Rename a role
ALTER ROLE app_user RENAME TO app_svc;
-- Remove a role entirely
DROP ROLE app_svc;
DROP ROLE nonexistent and ALTER ROLE nonexistent ... both error loudly rather than silently doing nothing; use DROP ROLE IF EXISTS for an idempotent teardown script. Dropping a role that still holds grants succeeds; the grants are simply removed along with it.
Role attributes (SUPERUSER, CREATEDB, CREATEROLE, LOGIN, REPLICATION, BYPASSRLS), role membership, and what each attribute actually gates are covered in Roles and privileges.