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
| Problem | Cause | Fix |
|---|---|---|
| pgAdmin redirects to HTTP after login | PREFERRED_URL_SCHEME not set | Add PREFERRED_URL_SCHEME = 'https' to config_local.py |
SSL: CERTIFICATE_VERIFY_FAILED from pgAdmin to PostgreSQL | pgAdmin connecting to a PostgreSQL server with a self-signed cert | In pgAdmin connection settings, provide the PostgreSQL server’s CA cert |
| 502 Bad Gateway from nginx | pgAdmin not running or wrong upstream port | Check systemctl status pgadmin4 and the port in nginx config |
| pgAdmin shows login form over HTTP on port 5050 | Browser using http:// directly | Force HTTPS via redirect or firewall rule |
OSError: [Errno 13] Permission denied on port 443 | Non-root process cannot bind to port 443 | Use 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.