pgAdmin TLS Configuration: Running the Web Interface Over HTTPS

pgAdmin 4 in server mode runs an internal web server (Gunicorn or built-in). By default that web server listens on HTTP — any credentials you type into the pgAdmin login form, any database passwords, any SQL you execute all travel in plaintext if you access pgAdmin over a network. This article covers enabling HTTPS on pgAdmin 4’s built-in server, setting up nginx as a TLS-terminating reverse proxy, and automating certificate renewal.

pgAdmin 4 deployment modes

pgAdmin 4 runs in two modes:

  • Desktop mode — launched locally, listens on 127.0.0.1 only. TLS is not needed here because traffic never leaves the local machine.
  • Server mode — usually installed via pip or package manager, runs as a system service accessible over the network. This is the mode that needs TLS.

Check which mode you are running:

# For pip/virtualenv installs
cat /etc/pgadmin/config_system.py | grep SERVER_MODE

# For package installs
cat /usr/pgadmin4/web/config.py | grep SERVER_MODE

SERVER_MODE = True means server mode. Everything below applies.

Option A: Enable HTTPS on pgAdmin’s built-in server

pgAdmin uses Gunicorn under the hood. TLS can be configured directly in config_local.py.

Step 1: Obtain a certificate

certbot certonly --standalone -d pgadmin.example.com

Copy and fix permissions:

mkdir -p /etc/pgadmin/certs
cp /etc/letsencrypt/live/pgadmin.example.com/fullchain.pem /etc/pgadmin/certs/pgadmin.crt
cp /etc/letsencrypt/live/pgadmin.example.com/privkey.pem   /etc/pgadmin/certs/pgadmin.key

# pgAdmin usually runs as the pgadmin user
chown pgadmin:pgadmin /etc/pgadmin/certs/pgadmin.crt /etc/pgadmin/certs/pgadmin.key
chmod 644 /etc/pgadmin/certs/pgadmin.crt
chmod 640 /etc/pgadmin/certs/pgadmin.key

Step 2: Create or edit config_local.py

The local configuration file overrides defaults without modifying the main config:

sudo nano /etc/pgadmin/config_local.py
# Bind to HTTPS
DEFAULT_SERVER = '0.0.0.0'
DEFAULT_SERVER_PORT = 443

# Certificate files
SSL_CERT = '/etc/pgadmin/certs/pgadmin.crt'
SSL_KEY  = '/etc/pgadmin/certs/pgadmin.key'

For non-privileged ports (not 443):

DEFAULT_SERVER_PORT = 5050

SSL_CERT = '/etc/pgadmin/certs/pgadmin.crt'
SSL_KEY  = '/etc/pgadmin/certs/pgadmin.key'

Step 3: Restart pgAdmin

For package-based installs:

sudo systemctl restart pgadmin4
sudo systemctl status pgadmin4

For pip/virtualenv installs started with the pgAdmin launch script:

sudo /usr/pgadmin4/bin/pgadmin4
# or restart the systemd service created during setup

Step 4: Verify

curl --cacert /etc/pgadmin/certs/pgadmin.crt https://pgadmin.example.com/login
# Should return the pgAdmin login page HTML
openssl s_client -connect pgadmin.example.com:443 -servername pgadmin.example.com </dev/null 2>&1 \
  | openssl x509 -noout -dates

Option B: nginx reverse proxy with TLS (recommended)

Running nginx in front of pgAdmin gives you better TLS control, HTTP/2 support, and a clean separation of concerns. pgAdmin listens on localhost only; nginx handles all TLS.

Step 1: Configure pgAdmin to listen on localhost only

In /etc/pgadmin/config_local.py:

DEFAULT_SERVER = '127.0.0.1'
DEFAULT_SERVER_PORT = 5050

# No SSL_ settings — nginx handles TLS

Restart pgAdmin.

Step 2: Configure nginx

Create /etc/nginx/sites-available/pgadmin:

server {
    listen 80;
    server_name pgadmin.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name pgadmin.example.com;

    ssl_certificate     /etc/letsencrypt/live/pgadmin.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/pgadmin.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 1d;

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;

    location / {
        proxy_pass         http://127.0.0.1:5050;
        proxy_set_header   Host              $host;
        proxy_set_header   X-Real-IP         $remote_addr;
        proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
        proxy_read_timeout 300s;
        proxy_connect_timeout 75s;

        # Required for pgAdmin's WebSocket support
        proxy_http_version 1.1;
        proxy_set_header   Upgrade    $http_upgrade;
        proxy_set_header   Connection "upgrade";
    }
}

Enable and test:

sudo ln -s /etc/nginx/sites-available/pgadmin /etc/nginx/sites-enabled/pgadmin
sudo nginx -t && sudo systemctl reload nginx

Step 3: Tell pgAdmin it is behind a proxy

In /etc/pgadmin/config_local.py, add:

# Tell pgAdmin the real scheme when behind a reverse proxy
PREFERRED_URL_SCHEME = 'https'
X_FRAME_OPTIONS = ''

This ensures pgAdmin generates correct HTTPS URLs for redirects, OAuth callbacks, and static assets.

Restart pgAdmin again.


pgAdmin Docker deployment with TLS

If you are running pgAdmin in Docker, the simplest TLS approach is a separate nginx container or a Traefik/Caddy reverse proxy. For the built-in server TLS with Docker:

version: '3.8'
services:
  pgadmin:
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin@example.com
      PGADMIN_DEFAULT_PASSWORD: "strong-password"
      PGADMIN_ENABLE_TLS: "True"
    volumes:
      - ./certs/pgadmin.crt:/certs/server.cert:ro
      - ./certs/pgadmin.key:/certs/server.key:ro
      - pgadmin_data:/var/lib/pgadmin
    ports:
      - "443:443"

The Docker image reads PGADMIN_ENABLE_TLS=True and expects the certificate at /certs/server.cert and the key at /certs/server.key.


Automating certificate renewal

For the built-in server TLS

sudo nano /etc/letsencrypt/renewal-hooks/deploy/pgadmin.sh
#!/bin/bash
DOMAIN=pgadmin.example.com
DEST=/etc/pgadmin/certs

cp /etc/letsencrypt/live/${DOMAIN}/fullchain.pem ${DEST}/pgadmin.crt
cp /etc/letsencrypt/live/${DOMAIN}/privkey.pem   ${DEST}/pgadmin.key
chown pgadmin:pgadmin ${DEST}/pgadmin.crt ${DEST}/pgadmin.key
chmod 644 ${DEST}/pgadmin.crt
chmod 640 ${DEST}/pgadmin.key

systemctl restart pgadmin4
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/pgadmin.sh

For the nginx reverse proxy

nginx reloads TLS certificates on nginx -s reload without dropping connections:

sudo nano /etc/letsencrypt/renewal-hooks/deploy/nginx-pgadmin.sh
#!/bin/bash
systemctl reload nginx
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-pgadmin.sh

Securing pgAdmin further

TLS encrypts the channel, but once connected, anyone who can reach the pgAdmin URL can attempt to log in. Additional hardening steps:

Restrict access by IP in nginx:

location / {
    allow 10.0.0.0/8;
    allow 192.168.0.0/16;
    deny all;
    proxy_pass http://127.0.0.1:5050;
}

Enable MFA in pgAdmin:

In pgAdmin’s interface: User Management → Edit User → Enable MFA.

Use pgAdmin’s session cookie security settings (in config_local.py):

SESSION_COOKIE_SECURE   = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
ENHANCED_COOKIE_PROTECTION = True

Troubleshooting

ProblemCauseFix
pgAdmin redirects to HTTP after loginPREFERRED_URL_SCHEME not setAdd PREFERRED_URL_SCHEME = 'https' to config_local.py
SSL: CERTIFICATE_VERIFY_FAILED from pgAdmin to PostgreSQLpgAdmin connecting to a PostgreSQL server with a self-signed certIn pgAdmin connection settings, provide the PostgreSQL server’s CA cert
502 Bad Gateway from nginxpgAdmin not running or wrong upstream portCheck systemctl status pgadmin4 and the port in nginx config
pgAdmin shows login form over HTTP on port 5050Browser using http:// directlyForce HTTPS via redirect or firewall rule
OSError: [Errno 13] Permission denied on port 443Non-root process cannot bind to port 443Use port 5050 with nginx on 443, or run with cap_net_bind_service

Summary

pgAdmin 4 server mode can serve HTTPS directly via SSL_CERT and SSL_KEY in config_local.py, or you can put nginx in front and handle TLS there. The nginx reverse proxy approach is more flexible — it enables HTTP/2, better header control, IP restriction, and certificate reloads without restarting pgAdmin. Set PREFERRED_URL_SCHEME = 'https' so pgAdmin knows it is behind HTTPS. Automate certificate renewal with a Certbot deploy hook and systemctl reload nginx.

Scroll to Top