md5_password_warnings is the off switch for a deprecation notice. PostgreSQL 18 deprecated MD5 password hashes, started emitting a WARNING whenever one is stored, and shipped this parameter in the same commit so that you could make the warning stop. It is a boolean, the default is on, and the context is user: any role can turn it off for its own session, and no privilege is required. It does not exist before 18. A 16 server that finds it in postgresql.conf refuses to start, which is worth knowing if one configuration template serves a mixed fleet.
The case against MD5 is old. It has not been a respectable hash for a long time, and PostgreSQL’s use of it has a worse problem of its own: the hash stored in pg_authid is all a client needs to authenticate. A stolen hash does not have to be cracked, because the hash is the credential. (PgBouncer treats this as a feature; an auth_file holding only MD5 hashes is enough for it to log in to a server that uses MD5.) SCRAM-SHA-256 arrived in PostgreSQL 10 and has been the default for password_encryption since 14. Nathan Bossart proposed a retirement schedule on pgsql-hackers in October 2024, and the deprecation was committed that December.
In 18, it warns the wrong people
On 18 the warning fires in one place: when CREATE ROLE or ALTER ROLE stores an MD5 hash.
1 SET password_encryption = md5;
2 SET
3 CREATE ROLE legacy_app LOGIN PASSWORD 'secret';
4 WARNING: setting an MD5-encrypted password
5 DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL.
6 HINT: Refer to the PostgreSQL documentation for details about migrating to another password type.
7 CREATE ROLE
The check is on what gets stored, not on what password_encryption says. A pre-hashed PASSWORD 'md5…' string sets it off under the default scram-sha-256 too, which covers provisioning scripts that hash on the client and pg_dumpall output replayed through psql. (\password hashes on the client using whatever the server’s password_encryption is, so it warns only when the server is still set to md5.)
What it does not notice is anyone using an MD5 password. On 18, a role with an MD5 hash logs in exactly as it did on 17, in silence. A hash that was set in 2015 and has ridden through every upgrade since is never going to be set again, so it never warns.
The exception is a major-version upgrade, which sets every old hash again in the new cluster. Do it with pg_dumpall and psql and you get one warning per MD5 role on the terminal. Do it with pg_upgrade and you get nothing. I upgraded a 16 cluster with MD5 roles to 18.6: the terminal output never mentioned them, and the hashes arrived intact. The warnings went to pg_upgrade_utility.log and pg_upgrade_server.log under pg_upgrade_output.d, a directory pg_upgrade removes on success unless you pass --retain.
So on 18 the warning reaches people who are creating new MD5 passwords, and a cluster that has been carrying the same ones through a decade of pg_upgrade runs hears nothing at all.
In 19, it warns everyone
PostgreSQL 19 corrects the aim. Tom Lane pointed out on pgsql-hackers that the login is where the eventual refusal will appear, so the login is where the warning belongs, and as of 19 beta 4 the same parameter also controls a warning on every successful authentication with an MD5 password:
1 $ psql -h 127.0.0.1 -p 5419 -U legacy_app -d postgres -c "SELECT current_user"
2 WARNING: authenticated with an MD5-encrypted password
3 DETAIL: MD5 password support is deprecated and will be removed in a future release of PostgreSQL.
4 current_user
5 --------------
6 legacy_app
7 (1 row)
It fires under both the md5 and password methods in pg_hba.conf, on replication connections as well as ordinary ones, and it is written to the server log as well as sent to the client. That is two log lines per connection, so an application that reconnects for every request will announce itself.
Two things about that log entry. With the stock log_line_prefix of '%m [%p] ', it does not say which role logged in; you need %u in the prefix, or log_connections and a match on the PID, before the log can tell you who to go and talk to. (Clusters created by the Debian packaging’s pg_createcluster already have a prefix with %u in it.)
And because the context is user, the role being warned can dismiss the warning itself. ALTER ROLE legacy_app SET md5_password_warnings = off, run by legacy_app with no privileges beyond its own login, removes both the client message and the log entry for every later connection. So does -c md5_password_warnings=off in the connection options, which is the only one of the two that works for a standby, since physical replication connections do not load per-role settings. client_min_messages is the polite version: it hides the warning from the client and leaves the log entry alone.
A quiet log therefore proves nothing, and that is before counting the roles that have an MD5 hash but currently get in by peer or trust and never trigger the warning. The catalog is the inventory. As a superuser:
1 SELECT rolname FROM pg_authid WHERE rolpassword ~ '^md5[0-9a-f]{32}$';
That pattern is the test the server itself applies. Every row needs a new password set while password_encryption is scram-sha-256, with \password or ALTER ROLE; a hash cannot be converted, so somebody has to supply the password again. You do not have to edit pg_hba.conf first. A line that says md5 performs SCRAM for any role whose stored password is a SCRAM secret, so the roles can be moved one at a time and the pg_hba.conf change left for the end. The clients do have to speak SCRAM, and that is the part to check before anything else.
As for the parameter, leave it on. If the upgrade to 19 turns up an application whose password you cannot change this week and whose connection rate is filling the log, turn the warning off for that one role with ALTER ROLE ... SET, and let everything else keep complaining. Setting it off in postgresql.conf silences the roles you have not found yet along with the one you have.
The 2024 proposal had 19 refusing to create MD5 passwords and 20 refusing to authenticate with them. 19 does neither, and the documentation has never committed to more than “a future release.” It is still coming. Bossart’s note on the thread was that CheckMD5Auth(), the function that queues this warning today, will be changed to return a failure instead.