2026.09 / credential drift 063
Coolify PostgreSQL password authentication failed after a password change
A Coolify application may work for months, then fail immediately after a database password edit with FATAL: password authentication failed for user "…". Restarting both containers often changes nothing. Editing POSTGRES_PASSWORD may also appear to do nothing.
This is usually credential drift between three different places: the password stored by the PostgreSQL role, the database credentials saved in Coolify, and the connection setting inside the application’s newly created container. A fourth copy may be used by scheduled backups. The repair is to identify the authoritative value, reconcile every consumer, and prove both an application query and a fresh backup.
Do not delete or reinitialise the PostgreSQL volume to fix an authentication error. The error already proves that the client reached a PostgreSQL server; repair identity and credentials without destroying data.
First, distinguish authentication from connectivity
| Error | What it establishes |
|---|---|
password authentication failed for user | The server was reached and rejected the supplied identity or password. |
no pg_hba.conf entry | The server was reached, but no host-authentication rule accepted that connection shape. |
connection refused | Nothing accepted the TCP connection at that address and port. |
could not translate host name | The database hostname did not resolve. |
database ... does not exist | Authentication got far enough to identify a missing or incorrect database. |
Do not open port 5432, change Cloudflare, or replace an internal hostname when PostgreSQL explicitly reports a password failure. If the app is instead using localhost or cannot reach the server, start with the separate Coolify PostgreSQL connection guide.
Why changing POSTGRES_PASSWORD may not change PostgreSQL
The official PostgreSQL container uses variables such as POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB while initialising an empty data directory. Once a persistent volume already contains a database cluster, startup preserves that cluster. Replacing the container with a new environment value does not imply that the existing role’s password was altered.
That behaviour is essential: a routine restart must not recreate a production database. It also explains a common mismatch:
- PostgreSQL was initialised with password A and persisted in a volume.
- A Coolify field or Compose variable was later changed to password B.
- The app was recreated and began sending B.
- The existing database role still expected A.
Do not “fix” this by removing the volume so the image’s entrypoint runs again. That creates a new database cluster and can turn a credential incident into data loss.
1. Identify the exact failing client and role
Capture one fresh application attempt and the matching PostgreSQL log line. Record only the resource, timestamp, database name, role name, and error class. Do not copy the password or complete connection URL into a ticket or shared log.
# Private host-side examples; use the actual managed container names
docker logs --since 10m APP_CONTAINER
docker logs --since 10m DATABASE_CONTAINER
Confirm that the application is talking to the intended PostgreSQL resource. Two databases can have the same role name. A successful DNS lookup or a familiar username does not prove the target is correct. Compare the hostname portion of Coolify’s Internal URL with the application’s configured host, without displaying the URL’s credential section.
2. Map the credential copies without printing them
Build a private checklist of the places that can hold this connection:
- PostgreSQL’s stored password for the login role;
- Coolify database General settings and its generated Internal URL;
- the application’s runtime variable, often
DATABASE_URLor separateDB_*values; - Compose interpolation, an env file, or a secret mounted into the app;
- workers, migration jobs, cron tasks, and other applications using the same role;
- Coolify scheduled-backup configuration or an external backup job;
- connection poolers such as PgBouncer.
Check whether a variable exists without dumping it:
docker exec APP_CONTAINER sh -lc '
test -n "$DATABASE_URL" && printf "%s\n" "DATABASE_URL is present" || exit 1
'
A present value can still be stale, malformed, or overridden. Inspect the application source and startup command to learn which variable it actually reads. Avoid broad env or docker inspect output in shared terminals because unrelated secrets may be exposed.
3. Test credentials from the application network
Use a PostgreSQL client on the same private Docker network as the application. Test the expected host, port, role, and database. Let psql prompt for the password rather than placing it in a command argument:
psql \
--host=INTERNAL_DB_HOST \
--port=5432 \
--username=APP_ROLE \
--dbname=APP_DB \
--password \
--command='select current_user, current_database();'
If an old candidate works and the new one fails, the role was not rotated. If the new candidate works but the app fails, the app has not consumed the new value or parses it differently. If neither works, verify the exact role and target before resetting anything. A psql success from inside the database container through a local socket may bypass the same host-password path used by the app, so it is useful for administration but not sufficient as the final test.
4. Choose one source of truth, then reconcile it
Coolify’s PostgreSQL documentation says that when the username, password, or initial database is changed inside the running database, the matching General values should be updated so generated connection URLs and automated backups remain accurate. Treat the database role and Coolify metadata as a coordinated pair rather than unrelated settings.
There are two safe recovery directions:
Keep the new intended password
- Take or confirm a restorable backup before an emergency credential change.
- Connect through an authorised administrative path.
- Change the actual PostgreSQL login role to the intended password.
- Make Coolify’s General settings match.
- Copy the newly generated Internal URL into every authorised consumer.
- Recreate the app, workers, migration jobs, and poolers that receive credentials at startup.
In interactive psql, \password is preferable to embedding the secret in an SQL command because it prompts and avoids placing the cleartext password in command history:
-- Inside an authorised interactive psql session
\password APP_ROLE
Restore the known working database credential temporarily
If the database still accepts the previous credential and immediate recovery matters more than completing rotation, make Coolify and the consumers match the working role value, recreate them, and verify service. Then schedule a controlled rotation. Do not leave Coolify displaying one password while PostgreSQL accepts another; backups and the next redeployment can fail later.
Whichever direction you choose, use the database role named in the error. Do not reset the built-in superuser merely because it is convenient. Applications should normally use a dedicated, least-privilege login role.
5. Watch for connection-URL encoding
A password can be correct in PostgreSQL but wrong when embedded unescaped in a URI. Characters including @, :, /, ?, #, and % have structural meaning in URLs. Hand-built strings can therefore send a value different from the intended password.
Prefer the Internal URL generated by Coolify. If an application requires assembling a URL, percent-encode each component with a trusted URL library rather than editing it manually. Never log the result. If separate host, user, password, and database variables are supported, they can remove one layer of URI parsing, but they still need proper secret handling.
6. Recreate every long-running consumer
A running process does not receive changed environment variables automatically. Saving an application variable in Coolify is not proof that the current container uses it. Trigger one controlled deployment or restart that replaces the affected consumers, and follow the deployment to a terminal state.
Remember the quiet clients: queue workers, schedulers, one-off migration containers, replicas, dashboards, and poolers. An old pool can keep established sessions alive while new connections fail, which makes a partial rotation look intermittent. Replace or reload consumers deliberately rather than repeatedly restarting the database.
If the application still sees an old value after recreation, use the Coolify build-time versus runtime guide to find overrides and values baked into an image.
7. Verify more than a green deployment
- From the application network, authenticate with the intended role and run
select current_user, current_database();. - Load a public or private application path that performs a real database read.
- Perform one controlled write and read-back where the environment permits it.
- Check application and database logs for fresh authentication failures.
- Run a new backup after reconciliation and verify its terminal result.
- Confirm workers and scheduled jobs continue processing.
- Remove temporary diagnostic files and sessions.
An HTTP 200 from a static health endpoint is not enough. A deployment can be green while a worker or database-backed route fails. Similarly, seeing an archive file is weaker than testing recovery; the Coolify PostgreSQL restore-drill guide explains how to prove a backup without touching production.
A safer rotation pattern for important applications
Changing one shared role in place creates a moment when either old or new consumers fail. For a system that cannot tolerate that gap, use a staged role rotation after reviewing privileges:
- Create a replacement login role with only the required memberships and grants.
- Test that role from the application network.
- Update and recreate consumers in a controlled sequence.
- Prove reads, writes, workers, migrations, and backups.
- Inspect active sessions and remaining dependencies on the old role.
- Disable the old login before dropping it, then monitor for failures.
This is more work than changing one password, but it provides an overlap window and a clean rollback. Do not blindly clone superuser privileges. Ownership, default privileges, schema usage, sequence access, and migration rights need explicit review.
Compact recovery order
- Preserve data and confirm a restorable backup; do not remove the volume.
- Match one app failure to one PostgreSQL authentication log entry.
- Confirm the intended server, database, and login role.
- Test candidate credentials over the same private network path.
- Choose whether the intended new credential or the currently working database credential is authoritative.
- Synchronise the PostgreSQL role, Coolify General settings, generated Internal URL, application runtime, and backup consumers.
- Handle URL encoding and hidden overrides.
- Recreate every long-running consumer once.
- Prove an application query, controlled write, worker activity, and a new backup.
The key distinction is between container configuration and persistent database state. A new POSTGRES_PASSWORD value can recreate a container while the volume preserves the old role credential. Repair the role and Coolify metadata as one system, then update and recreate every consumer. That resolves the authentication failure without sacrificing the database it was meant to protect.
Authoritative references: Coolify’s official PostgreSQL guide covers managed credentials, Internal URLs, and keeping General settings consistent with changes made inside PostgreSQL. Its database General configuration guide covers applying connection changes. PostgreSQL documents password authentication, authentication error meanings, and role alteration. The official Postgres container image documentation explains initialisation variables and the empty-data-directory boundary.