Fix PostgreSQL "fe_sendauth: no password supplied" in Django
Fix the PostgreSQL error fe_sendauth no password supplied in Django: find why the password is empty, check DATABASE_URL parsing and environment loading in systemd, Docker, and cron, escape special characters, and use .pgpass or secrets safely.
Problem statement
Django starts, or a management command runs, and the first query fails:
django.db.utils.OperationalError: connection to server at "db.internal" (10.0.1.20),
port 5432 failed: fe_sendauth: no password supplied
The fe_ prefix means the message comes from the client library (libpq, the "frontend"), not the server. PostgreSQL asked for a password, and the client had none to send. The server is reachable and expects password authentication; Django simply passed an empty password. The question is where it got lost.
- Print what Django sees:
python manage.py shell -c "from django.db import connection; print(bool(connection.settings_dict['PASSWORD']))". - If it is empty, the environment variable is not loaded in this context: systemd, cron, Docker, or
sudoeach start with their own environment. - With
DATABASE_URL, URL-encode special characters in the password (@becomes%40,/becomes%2F). - Check that the settings read the variable you actually set (
DB_PASSWORDversusPOSTGRES_PASSWORD). - For scripts and cron, a
~/.pgpassfile supplies passwords without putting them on the command line.
Step-by-step solution
1. See whether Django has a password
Run the check in exactly the context that fails, such as inside the container or as the service user:
sudo -u deploy bash -lc 'cd /srv/app && .venv/bin/python manage.py shell -c "
from django.db import connection
s = connection.settings_dict
print(s[\"HOST\"], s[\"USER\"], \"password set:\", bool(s[\"PASSWORD\"]))"'
Never print the password itself into logs or shell history. If password set: False, keep reading. If it is set, the server is rejecting a wrong password, which produces a different message: see Fix PostgreSQL Password Authentication Failed.
2. Load the environment where the process runs
Each launcher has its own environment:
- systemd ignores your shell profile. Use
EnvironmentFile=/etc/myapp/envin the unit and make sure the file is readable by root (systemd reads it before dropping privileges). - cron runs with an almost empty environment. Source the file in the job or use a wrapper script.
- sudo resets the environment by default;
sudo -u deploy envshows what survives. - Docker Compose only passes variables listed under
environment:orenv_file:to the container.
3. Check DATABASE_URL parsing
dj-database-url and django-environ split the URL on :, @, and /. A password containing those characters is cut short or ends up empty:
# broken: password contains @ and /
DATABASE_URL=postgres://app:p@ss/word@db.internal:5432/appdb
# fixed: percent-encode the password
DATABASE_URL=postgres://app:p%40ss%2Fword@db.internal:5432/appdb
Encode a password safely with Python:
python3 -c "import urllib.parse, getpass; print(urllib.parse.quote(getpass.getpass(), safe=''))"
4. Match the variable names
Settings files often read one name while deployment sets another:
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": os.environ["DB_NAME"],
"USER": os.environ["DB_USER"],
"PASSWORD": os.environ.get("DB_PASSWORD", ""),
"HOST": os.environ["DB_HOST"],
}
}
Here os.environ.get(..., "") hides the mistake by defaulting to an empty string. Prefer os.environ["DB_PASSWORD"] so a missing variable fails with a clear KeyError at startup.
5. Use .pgpass for scripts
Backup scripts and psql sessions can read passwords from ~/.pgpass instead of the environment:
# ~/.pgpass (chmod 600)
db.internal:5432:appdb:app:the-password
libpq ignores the file if its permissions are wider than 0600. Django can use it too if PASSWORD is left out of settings, since psycopg passes the connection to libpq.
Explanation
When pg_hba.conf assigns a password method such as scram-sha-256 to the connection, the server sends an authentication request. libpq looks for a password in the connection parameters, then in PGPASSWORD, then in the password file. If none of them has one, it gives up with fe_sendauth: no password supplied without contacting the server again. Because Django builds the connection parameters from settings, an empty PASSWORD key is enough to trigger it.
Edge cases / notes
- Local development with trust auth. Projects that used
trustlocally never needed a password, so the setting was never wired up. The first deploy against a real server exposes it. - Secrets mounted as files. Docker and Kubernetes secrets are often files. Read them explicitly, for example with a
_FILEvariable convention, rather than expecting an environment variable. - PgBouncer. With
auth_type = scram-sha-256on the pooler, the same message can come from PgBouncer's side; check itsauth_file.
Next steps
- If the password is sent but rejected, read Fix PostgreSQL Password Authentication Failed in Django.
- Fix systemd loading with Fix systemd EnvironmentFile Not Loaded.
- Organize variables with Configure Django Environment Variables in Production.
FAQ
What does fe_sendauth mean?
It is a message from libpq, the PostgreSQL client library. "fe" stands for frontend. It means the client had no password to send when the server asked for one.
Is this the same as password authentication failed?
No. That error means a password was sent and rejected. This one means no password was sent at all.
Why does it work in my terminal but not in the service?
Your terminal has the variable exported; the service starts with its own environment. Load the variables in the unit file or container definition.
Can I put the password directly in settings.py?
It works but leaks the secret into git. Use environment variables or a secrets manager and fail fast when they are missing.