Troubleshooting
PostgreSQL & databases

Fix PostgreSQL "Peer authentication failed" for Django

Fix Django FATAL Peer authentication failed for user when connecting to local PostgreSQL: understand peer versus password auth in pg_hba.conf, connect over TCP with HOST=localhost, or map the Linux user correctly.

Problem statement

On a server with PostgreSQL installed locally, Django fails to connect:

django.db.utils.OperationalError: connection to server on socket
"/var/run/postgresql/.s.PGSQL.5432" failed: FATAL:  Peer authentication failed for user "myapp"

The password is correct and the role exists. The problem is the authentication method. Debian and Ubuntu configure PostgreSQL to use peer authentication for local Unix-socket connections: the server asks the kernel which Linux user owns the connecting process and only allows it if that name matches the database role. Gunicorn running as www-data or deploy cannot log in as role myapp this way, no matter what password you send.

How peer authentication decidesDjango connectsvia Unix socketpg_hba.conflocal = peerKernel reportsOS user www-dataMismatchrole myapp refusedPeer auth compares the Linux user with the role name and ignores the password completely.
  1. Easiest fix: set HOST to localhost (or 127.0.0.1) in Django settings so it connects over TCP, which uses scram-sha-256 password auth.
  2. Alternatively, change the local line for your database in pg_hba.conf to scram-sha-256 and reload PostgreSQL.
  3. Or run Django as a Linux user with the same name as the role (or add an ident map).
  4. Reload, do not restart: sudo systemctl reload postgresql.

Step-by-step solution

1. Confirm the connection method

An empty HOST in Django means "connect via the default Unix socket". Check what is configured:

python manage.py shell -c "from django.db import connection; print(repr(connection.settings_dict['HOST']))"

'' or a path like /var/run/postgresql means a socket connection, governed by local lines in pg_hba.conf. localhost or an IP means TCP, governed by host lines.

2. Option A: connect over TCP

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "myapp",
        "USER": "myapp",
        "PASSWORD": env("DB_PASSWORD"),
        "HOST": "127.0.0.1",
        "PORT": "5432",
    }
}

With a DATABASE_URL, use postgres://myapp:secret@127.0.0.1:5432/myapp. Default Debian and Ubuntu pg_hba.conf already has:

host    all   all   127.0.0.1/32   scram-sha-256
host    all   all   ::1/128        scram-sha-256

so password authentication works immediately. The TCP overhead on localhost is negligible for a Django app.

3. Option B: password auth on the socket

If you prefer the socket (for example, to keep PostgreSQL off the network entirely), add a line above the generic local all all peer line:

# /etc/postgresql/16/main/pg_hba.conf
# TYPE  DATABASE  USER   METHOD
local   myapp     myapp  scram-sha-256
local   all       all    peer
sudo systemctl reload postgresql

pg_hba.conf is read top to bottom and the first matching line wins, so order matters.

HOST decides which pg_hba line appliesConnectionDefault methodHOST emptyUnix socket!peer: OS user = roleHOST = /var/run/postgresqlUnix socket!peer: OS user = roleHOST = 127.0.0.1TCP loopback✓scram-sha-256 passwordPeer auth ignores the password; TCP on localhost checks itdjango-deployment.com
Changing HOST to 127.0.0.1 is often the whole fix on Debian and Ubuntu.

4. Option C: keep peer, match the user

Peer authentication is secure and needs no password. To use it, run Gunicorn as a Linux user named like the role (User=myapp in the systemd unit, with USER set and PASSWORD empty in Django), or map users explicitly:

# pg_ident.conf
# MAPNAME  SYSTEM-USERNAME  PG-USERNAME
djangomap  www-data         myapp
# pg_hba.conf
local   myapp   myapp   peer map=djangomap

5. Check the logs if it still fails

sudo tail -n 20 /var/log/postgresql/postgresql-16-main.log

The server log names the pg_hba.conf line that matched (Connection matched file "/etc/postgresql/16/main/pg_hba.conf" line 118), which shows whether your new line is being used or an earlier one wins.

three ways to fix peer authenticationHow should Django log in to local PostgreSQL?simplestTCP to 127.0.0.1password, no config changesocket + passwordscram line in pg_hbaabove the peer lineno password at allPeer with matching useror pg_ident mapAll three are secure; TCP on loopback needs no server changedjango-deployment.com
Pick one approach and use it consistently for web, workers, and migrations.

Verification check:

sudo -u www-data psql "host=127.0.0.1 dbname=myapp user=myapp" -c "SELECT current_user;"
python manage.py migrate --check

Run the check as the same Linux user Gunicorn uses, because peer results depend on who runs the command.

Explanation

pg_hba.conf maps each connection type, database, user, and address to an authentication method. Debian's packaging chooses peer for local sockets because it lets the postgres OS user administer the server without a password, and keeps local access tied to Unix accounts. Django deployments usually run under a service account whose name does not match the database role, so the default fails. Moving to TCP on loopback swaps in password authentication with no change to PostgreSQL itself.

Edge cases / notes

  • "Ident authentication failed" is the same idea for TCP connections on some RHEL-family defaults; change the host line to scram-sha-256.
  • sudo -u postgres psql works because the postgres OS user matches the postgres role under peer auth. That does not mean your app's role can connect the same way.
  • Docker images of PostgreSQL use trust or password auth by default; peer errors there usually mean you connected to a host installation instead.
  • Password hashing. If you switch an old md5 setup to scram-sha-256, reset the role's password so it is stored as SCRAM.

Next steps

FAQ

Why does psql work but Django fails?

You probably run psql as a user whose name matches the role, or connect with -h localhost. Django connects via the socket as a different OS user.

Is peer authentication insecure?

No. It is very secure for local access. It just requires the OS user to match the role.

Do I need to restart PostgreSQL after editing pg_hba.conf?

No. A reload is enough: sudo systemctl reload postgresql.

Is connecting over TCP to localhost slower?

Marginally, and not measurably for a typical Django app.

Keep going