pgAdmin 4 runs on Debian as a web application behind Apache. The official APT repository ships the same build for Debian 13 (Trixie) and Debian 12 (Bookworm), so you add one repository, install one package, and let a bundled script wire it into Apache with mod_wsgi.
This guide installs pgAdmin 4 in server (web) mode, puts Nginx with a Let’s Encrypt certificate in front of it, and connects it to a local PostgreSQL server. It also covers the change most upgrade guides skip, because free-text password exec commands no longer run in server mode. Every command below was run in October 2026 on Debian 13 and Debian 12 virtual machines with pgAdmin 4 9.19. If you still need the database itself, set it up with our PostgreSQL on Debian guide first.
Prerequisites
The pgAdmin repository only publishes x86_64 packages for Debian, so an ARM server (Raspberry Pi, Graviton, Ampere) needs the pgAdmin container image instead. Here is what the steps assume:
- Debian 13 (Trixie) or Debian 12 (Bookworm), amd64
- PostgreSQL installed and running on the same host or reachable over the network
- A user with sudo rights
- A DNS name pointing at the server, for the Let’s Encrypt certificate
The test VMs had 2 GB of RAM and 2 vCPUs each, and that was enough for PostgreSQL, Apache, Nginx and pgAdmin together on one box. pgAdmin itself is light because it only holds web sessions and a small SQLite configuration database. The PostgreSQL server you point it at sets the real sizing, so plan that host around its data and connection count, and treat the numbers here as a floor for a single admin.
Add the pgAdmin Repository
Debian’s own archive does not carry pgAdmin 4, so the packages come from the pgAdmin team’s APT repository. Start with the tools the repository setup needs. Minimal and container images sometimes lack lsb-release, and the repository line below depends on it, so install it even if you think it’s already there.
sudo apt update
sudo apt install -y curl ca-certificates gnupg lsb-release
Next, import the signing key into /etc/apt/keyrings, the path the pgAdmin download page now uses. The deprecated apt-key method is gone from both releases, so a dedicated keyring file that the repository line references by path is the only route that works.
curl -fsS https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo gpg --dearmor -o /etc/apt/keyrings/packages-pgadmin-org.gpg
Now add the repository. $(lsb_release -cs) expands to trixie on Debian 13 and bookworm on Debian 12, so the same line works on both:
echo "deb [signed-by=/etc/apt/keyrings/packages-pgadmin-org.gpg] https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" | sudo tee /etc/apt/sources.list.d/pgadmin4.list
Refresh the package index and ask APT which pgAdmin builds it can see:
sudo apt update
apt-cache policy pgadmin4-web
On Debian 13 the candidate came from the trixie suite, with every release back to 9.15 still available if you ever need to pin one:
pgadmin4-web:
Installed: (none)
Candidate: 9.19
Version table:
9.19 500
500 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/trixie pgadmin4/main all Packages
9.18 500
500 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/trixie pgadmin4/main all Packages
9.17 500
500 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/trixie pgadmin4/main all Packages
9.16 500
500 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/trixie pgadmin4/main all Packages
9.15 500
500 https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/trixie pgadmin4/main all Packages
Debian 12 shows the same candidate from the bookworm suite, because the pgAdmin team builds both suites from the same release on the same day. If the Candidate line reads (none) instead, the repository line or the keyring path has a typo, and sudo apt update will have printed a NO_PUBKEY or 404 error above it. Fix that before you continue, since the install step below would otherwise fail with “Unable to locate package”.
Install pgAdmin 4
The repository splits pgAdmin into three packages. Pick the one that matches how you’ll use it:
pgadmin4-web: web mode under Apache, for serverspgadmin4-desktop: the desktop application onlypgadmin4: both of the above
A headless server only needs the web package. It pulls in Apache, mod_wsgi and the pgadmin4-server core package as dependencies:
sudo apt install -y pgadmin4-web
Confirm which version landed:
dpkg -l | grep pgadmin4
Both the web package and the server core it depends on should report the same version, 9.19 at the time of testing:
ii pgadmin4-server 9.19 amd64 The core server package for pgAdmin. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
ii pgadmin4-web 9.19 all The web interface for pgAdmin, hosted under Apache HTTPD. pgAdmin is the most popular and feature rich Open Source administration and development platform for PostgreSQL, the most advanced Open Source database in the world.
The two version columns must match. The web package is only the Apache glue, while pgadmin4-server holds the application and its Python environment, so a mismatch means a partial upgrade and the two halves come from different releases. If you ever see one, run sudo apt install -y pgadmin4-web pgadmin4-server to bring them back in step before going further.
Configure pgAdmin 4 Web Mode
The package installs files, but it does not create the configuration database or your first login. setup-web.sh does both, enables the Apache config and restarts Apache. Passing the email and password as environment variables with --yes keeps it non-interactive, which also makes it safe to script:
sudo PGADMIN_SETUP_EMAIL='[email protected]' PGADMIN_SETUP_PASSWORD='YourStrongPassword' /usr/pgadmin4/bin/setup-web.sh --yes
Swap in your own email and a strong password, since these become the pgAdmin administrator login. On Debian 13 the script finished like this:
Setting up pgAdmin 4 in web mode on a Debian based platform...
Running in non-interactive mode...
Creating configuration database...
/usr/pgadmin4/venv/lib/python3.13/site-packages/alembic/operations/batch.py:298: UserWarning: Unnamed CHECK constraint on reflected table 'sharedserver' is being omitted from the table recreate; pass the constraint explicitly using batch_alter_table.table_args. Naming CHECK constraints avoids this going forward.
util.warn(
NOTE: Configuring authentication for SERVER mode.
pgAdmin 4 - Application Initialisation
======================================
Creating storage and log directories...
Apache successfully restarted. You can now start using pgAdmin 4 in web mode at http://127.0.0.1/pgadmin4
The Alembic UserWarning comes from a schema migration while the configuration database is created, and it is harmless, since your login and every later step worked with it in place. The last line is the one that matters. At this point pgAdmin answers on plain HTTP port 80, which is fine for a quick check from the server itself but not for a login form you reach over a network.
Set Up Nginx Reverse Proxy with SSL
Rather than bolting TLS onto the Apache vhost that setup-web.sh created, we keep Apache untouched on a local port and let Nginx own ports 80 and 443. The pgAdmin package owns the Apache side of the config, so leaving it alone means a package upgrade never fights your TLS settings. Nginx then holds the certificate, the redirect and the HTTP/2 listener, and you only have one web server to harden.
Move Apache to Port 8080
Nginx cannot bind port 80 while Apache holds it, so move Apache first. The two sed edits change the listener and the default vhost, and the pgAdmin config that setup-web.sh enabled follows along because /etc/apache2/conf-enabled/pgadmin4.conf is a server-wide WSGIScriptAlias, not tied to a port.
sudo sed -i 's/Listen 80/Listen 8080/' /etc/apache2/ports.conf
sudo sed -i 's/<VirtualHost \*:80>/<VirtualHost *:8080>/' /etc/apache2/sites-enabled/000-default.conf
sudo systemctl restart apache2
Check that Apache moved before you install Nginx. The Debian nginx package starts its service as soon as it installs, so Apache still holding port 80 at that point leaves you with a failed unit to clean up.
sudo ss -tlnp | grep 8080
Apache’s worker processes should now hold port 8080.
LISTEN 0 511 *:8080 *:* users:(("apache2",pid=5290,fd=4),("apache2",pid=5289,fd=4),("apache2",pid=5286,fd=4))
If port 80 still shows up for Apache instead, the Listen line in ports.conf had different spacing and the first sed missed it, so fix that line by hand.
Install Nginx and Certbot
Both packages come from the Debian archive. Our Nginx guide for Debian and Ubuntu covers the server itself in more depth:
sudo apt install -y nginx certbot
Request the certificate with certbot’s standalone authenticator, replacing pgadmin.example.com and the email with your own. The two hooks matter more than they look. Standalone mode needs port 80 to itself, and the original version of this guide stopped Nginx by hand once, which works for the first certificate but leaves every automatic renewal failing while Nginx is running. With --pre-hook and --post-hook, certbot stores the stop and start commands in the renewal config and runs them on its own:
sudo certbot certonly --standalone -d pgadmin.example.com --non-interactive --agree-tos -m [email protected] --pre-hook 'systemctl stop nginx' --post-hook 'systemctl start nginx'
The lab VMs had no public DNS name, so we pointed certbot at a local Pebble ACME test server with an extra --server flag. You leave that flag off and Let’s Encrypt issues the certificate. Below is our output without the donation banner and Pebble’s five-year test expiry line; a Let’s Encrypt certificate shows its much shorter expiry date there instead:
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for pgadmin.example.com
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/pgadmin.example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/pgadmin.example.com/privkey.pem
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
The hooks are now part of the renewal configuration, which you can confirm before trusting the timer with it:
sudo grep hook /etc/letsencrypt/renewal/pgadmin.example.com.conf
Both lines should be there:
pre_hook = systemctl stop nginx
post_hook = systemctl start nginx
If the grep prints nothing, renewals will fail with “Could not bind TCP port 80” (covered under troubleshooting below). Run the certonly command again with the hooks and certbot updates the stored settings.
Create the Nginx Configuration
Open a new server block file. You’ll keep it under sites-available and link it in, which is the Debian layout the default site already uses.
sudo vim /etc/nginx/sites-available/pgadmin
Paste the configuration below. It redirects HTTP to HTTPS, proxies /pgadmin4/ to Apache on 8080, and passes X-Script-Name so pgAdmin builds its links under the right path. It’s written for Debian 13, whose Nginx takes HTTP/2 as a separate directive. On Debian 12, remove the http2 line and add http2 to the end of the 443 listen line instead, because the older Nginx there rejects the separate directive.
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;
location /pgadmin4/ {
proxy_pass http://127.0.0.1:8080/pgadmin4/;
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_set_header X-Script-Name /pgadmin4;
}
location / {
return 301 https://$host/pgadmin4/;
}
}
Enable the site and drop the default one, which would otherwise also claim port 80:
sudo ln -sf /etc/nginx/sites-available/pgadmin /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
Test the syntax before you reload anything, because a failed restart takes the redirect and the TLS listener down together.
sudo nginx -t
A clean config prints two lines:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Restart Nginx and enable it at boot:
sudo systemctl restart nginx
sudo systemctl enable nginx
From the server, a request to the HTTPS URL should come back as a redirect to the pgAdmin login page over HTTP/2:
curl -s -o /dev/null -w '%{http_code} %{http_version} %{redirect_url}\n' https://pgadmin.example.com/pgadmin4/
Both Debian releases returned the same line, Debian 12 included, once its listen line used the combined syntax:
302 2 https://pgadmin.example.com/pgadmin4/login?next=/pgadmin4/
Finish with a renewal dry run. This is the test the stopped-by-hand setup used to fail, because it goes through the same stop, renew and start sequence the timer will:
sudo certbot renew --dry-run
You want the success line, and Nginx should be back up afterwards (systemctl is-active nginx printed active on both VMs):
Processing /etc/letsencrypt/renewal/pgadmin.example.com.conf
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Simulating renewal of an existing certificate for pgadmin.example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations, all simulated renewals succeeded:
/etc/letsencrypt/live/pgadmin.example.com/fullchain.pem (success)
The Debian certbot package installs certbot.timer, which runs the real renewal twice a day, so there is no cron job for you to add.
Configure the Firewall
Debian ships without an active firewall, so install ufw and open SSH, HTTP and HTTPS. Allow SSH before you enable it, or you’ll cut off your own session. ufw allow ssh opens port 22 only, so allow your custom port instead if sshd listens elsewhere. Port 8080 stays closed, which keeps Apache reachable only through Nginx:
sudo apt install -y ufw
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Answer y at the prompt, then list the rules:
sudo ufw status
Ports 22, 80 and 443 should be allowed for IPv4 and IPv6:
Status: active
To Action From
-- ------ ----
22/tcp ALLOW Anywhere
80/tcp ALLOW Anywhere
443/tcp ALLOW Anywhere
22/tcp (v6) ALLOW Anywhere (v6)
80/tcp (v6) ALLOW Anywhere (v6)
443/tcp (v6) ALLOW Anywhere (v6)
Port 80 has to stay open even though Nginx only redirects it, because certbot’s standalone renewal answers the HTTP-01 challenge on that port. Closing it looks tidy, but it breaks the next automatic renewal, and you only find out from an expiry warning. Apache’s 8080 is the port to keep shut, so nobody can reach pgAdmin over plain HTTP around the proxy.
Access the pgAdmin Web Interface
Browse to https://pgadmin.example.com/pgadmin4/. The login page asks for the email and password you passed to setup-web.sh:

Once you’re in, the welcome dashboard opens with an empty Servers group in the Object Explorer on the left:

pgAdmin does not discover servers on its own, even one on the same host, so the next job is registering one. Each pgAdmin login keeps its own server list, which means a second admin you add later starts with an empty tree as well.
Connect to a PostgreSQL Server
pgAdmin connects over TCP, so the postgres role needs a password. Debian’s default pg_hba.conf uses peer authentication only for local socket connections, and that’s why a fresh install has no password set:
sudo -u postgres psql -c "ALTER USER postgres PASSWORD 'YourSecurePassword';"
Older guides, this one included, told you to edit pg_hba.conf next. Check the file before you touch it, because on both releases TCP connections to 127.0.0.1 already use scram-sha-256. The path is /etc/postgresql/17/main/ on Debian 13 and /etc/postgresql/15/main/ on Debian 12 with the distribution packages, so adjust the version in the command if you run Debian 12 or the PGDG builds.
sudo grep -E '^(local|host)' /etc/postgresql/17/main/pg_hba.conf
The host lines for 127.0.0.1 and ::1 are the ones pgAdmin uses:
local all postgres peer
local all all peer
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
local replication all peer
host replication all 127.0.0.1/32 scram-sha-256
host replication all ::1/128 scram-sha-256
So a local pgAdmin needs no pg_hba.conf change and no PostgreSQL restart. Switching the local postgres line from peer to a password method, as the old steps did, only changes socket logins and makes sudo -u postgres psql ask for a password for no gain. Prove the password works over TCP from the shell before you blame pgAdmin for anything.
psql -h 127.0.0.1 -U postgres -c 'select version();'
After the password prompt, the server version comes back:
version
---------------------------------------------------------------------------------------------------------------------
PostgreSQL 17.11 (Debian 17.11-0+deb13u1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 14.2.0-19) 14.2.0, 64-bit
(1 row)
A PostgreSQL server on another host is different: it needs listen_addresses and a host line for the pgAdmin server’s IP in its own pg_hba.conf, plus port 5432 open in its firewall.
In pgAdmin, click Add New Server on the dashboard. Give it a name such as “Local PostgreSQL” on the General tab, then fill the Connection tab with host 127.0.0.1, port 5432, maintenance database postgres and username postgres:

Type the password you set above into the Password field, or leave it empty if you’ll use a password exec command from the next section. Click Save, and pgAdmin connects and opens the server’s activity dashboard:

Expand the server in the Object Explorer to browse databases, schemas and tables. For a structured way into the database side, our list of PostgreSQL books covers everything from first queries to replication.
Allow a Password Exec Command (Optional)
A password exec command lets pgAdmin fetch a database password from a script (a vault client, a secrets file) instead of storing it. Starting with 9.19, users in server mode can no longer type their own command into the server dialog. The administrator defines named commands in SERVER_PASSEXEC_COMMANDS, users pick a name from a dropdown, and pgAdmin runs it without a shell and with a reduced environment. Until you define at least one, the feature is off. Skip this section if every user types or saves their password.
The setting belongs in /etc/pgadmin/config_system.py. pgAdmin reads that file at startup on Linux, and it lives outside /usr/pgadmin4, so package upgrades leave it alone. As a small example, store the password in a file only the web server can read:
sudo mkdir -p /etc/pgadmin
echo 'YourSecurePassword' | sudo tee /etc/pgadmin/postgres.pass > /dev/null
sudo chown root:www-data /etc/pgadmin/postgres.pass
sudo chmod 0640 /etc/pgadmin/postgres.pass
Create the script pgAdmin will run:
sudo vim /usr/local/bin/pg-pass
Add these two lines. A real setup would call your vault CLI here and check the PGADMIN_PASSEXEC_PGADMIN_USER variable pgAdmin passes in:
#!/bin/sh
exec cat /etc/pgadmin/postgres.pass
Restrict it to root and the www-data group that Apache runs pgAdmin as, then run it as that user to prove the permissions line up:
sudo chown root:www-data /usr/local/bin/pg-pass
sudo chmod 0750 /usr/local/bin/pg-pass
sudo -u www-data /usr/local/bin/pg-pass
It should print the password and nothing else. Now open the pgAdmin system config:
sudo vim /etc/pgadmin/config_system.py
Each entry maps a name to a list holding the executable and its arguments. Arguments are not passed through a shell, so pipes and $VARS you put in them will not expand.
SERVER_PASSEXEC_COMMANDS = {
'local-postgres': ['/usr/local/bin/pg-pass']
}
Make the file world readable before restarting Apache. In our lab the file was created with mode 0600, and every pgAdmin page then returned HTTP 500 because www-data couldn’t import it:
sudo chmod 0644 /etc/pgadmin/config_system.py
sudo systemctl restart apache2
Open the server’s properties (or the Register Server dialog for a new one) and the name you defined is now offered:

- 1Advanced tab
- 2Password exec command
- Open the Advanced tab of the server dialog.
- Pick
local-postgresfrom Password exec command and leave the password on the Connection tab empty.
We saved the server this way with no password typed anywhere, and pgAdmin connected straight to the dashboard shown in the previous section, which proves the script ran as www-data. Anything you don’t list in the config never appears in that dropdown, which is the point of the change: a pgAdmin user can no longer make the server run a command of their choosing.
Upgrade an Existing pgAdmin Install
Upgrades come through APT like any other package. Upgrade both packages together, then restart Apache so the WSGI process loads the new code:
sudo apt update
sudo apt install -y --only-upgrade pgadmin4-web pgadmin4-server
sudo systemctl restart apache2
A plain install with saved passwords needs nothing else. Server-mode installs should still read the release notes, because 9.19 tightened several other server-mode defaults, including these:
- SSH tunnels ignore the SSH agent and the service account’s
~/.ssh - No fallback to the service account’s password file or client certificate
- Users of a shared server no longer inherit the owner’s password file
- Identity files, certificates and password files must sit in the user’s storage directory or SHARED_STORAGE
A tunnel that relied on the agent now needs an identity file or password set on the server. The change most likely to bite is password exec commands, which 9.19 stopped running as free text. On the first start where SERVER_PASSEXEC_COMMANDS has at least one entry, pgAdmin converts every stored command that exactly matches an entry into that entry’s name and clears the rest, and the clearing can’t be undone. So build the full allowlist before that start, not one entry at a time.
We tested the upgrade on Debian 12 from 9.18, with a server that had /usr/local/bin/pg-pass stored as its command. Two things show up in /var/log/pgadmin/pgadmin4.log, so check it right after the restart:
sudo grep WARNING /var/log/pgadmin/pgadmin4.log
With ENABLE_SERVER_PASS_EXEC_CMD = True still in the old config_system.py and no allowlist yet, the first start on 9.19 logged both warnings:
2026-10-31 06:10:03,214: WARNING pgadmin: ENABLE_SERVER_PASS_EXEC_CMD is deprecated and no longer has any effect; define the permitted commands in SERVER_PASSEXEC_COMMANDS instead.
2026-10-31 06:10:03,233: WARNING pgadmin: SERVER_PASSEXEC_COMMANDS is not configured, but 1 row(s) in the server table still hold a password exec command from before the upgrade. They are not run in server mode. Configure SERVER_PASSEXEC_COMMANDS before the next start: commands that match a configured entry are then converted to its name, and the rest are cleared.
The second warning is your safety net. While the allowlist is empty, the old commands stay in the configuration database and pgAdmin does not run them. Add an entry whose arguments, joined by single spaces, match the stored command exactly ('local-postgres': ['/usr/local/bin/pg-pass'] matched /usr/local/bin/pg-pass), drop the deprecated ENABLE_SERVER_PASS_EXEC_CMD line, and restart Apache. On our VM the stored command became local-postgres on that restart and the warning stopped. A command that matches no entry, say one with extra flags, is wiped at that point, so copy each existing command into the allowlist before the restart, not after.
The full list of security fixes behind this change is in the pgAdmin 9.19 release notes. The same release also added SERVER_MODE_ALLOWED_SERVICES, an allowlist for libpq service names that is empty by default, so a server set to use a pg_service.conf entry won’t connect until you list that service in config_system.py. Listing a service shares its credentials with every pgAdmin user, so only list services meant for everyone.
Troubleshooting
These three errors all came up during the lab runs, and each has a one-line cause.
Could not bind TCP port 80 because it is already in use
certbot’s standalone mode tried to answer the challenge while Nginx held port 80. This is what a certificate requested without the hooks hits on every renewal:
Could not bind TCP port 80 because it is already in use by another process on this system (such as a web server). Please stop the program in question and then try again.
The first certificate usually works, because people stop Nginx by hand before requesting it. Renewal is where it breaks, since the timer runs months later with Nginx up and nobody there to stop it. Rerun the certbot certonly command from the SSL section with both hooks, then confirm with sudo certbot renew --dry-run while Nginx is running.
unknown directive “http2” on Debian 12
The Debian 13 config was copied to Debian 12 unchanged. Nginx 1.22 does not know the separate directive, and nginx -t fails like this:
2026/10/31 05:43:58 [emerg] 7406#7406: unknown directive "http2" in /etc/nginx/sites-enabled/pgadmin:9
nginx: configuration file /etc/nginx/nginx.conf test failed
Nginx only learned the separate HTTP/2 directive in the 1.25 line, and Debian 12 ships 1.22. Delete the http2 line and append http2 to the 443 listen line, so it reads listen 443 ssl http2 followed by the usual semicolon. The test then passes and the site still negotiates HTTP/2.
HTTP 500 and PermissionError on config_system.py
Every pgAdmin URL returns 500 after you add /etc/pgadmin/config_system.py, and the Apache error log ends with:
PermissionError: [Errno 13] Permission denied: '/etc/pgadmin/config_system.py'
Apache runs pgAdmin as www-data, which cannot read a 0600 file owned by root, so the config import fails before pgAdmin can even render its error page. Running sudo chmod 0644 /etc/pgadmin/config_system.py and restarting Apache fixed it on our VM. Keep secrets out of that file for the same reason, because once it’s world readable anyone with a shell on the box can read it.
Debian 13 vs Debian 12 Differences
The pgAdmin side is identical on both releases. What changes is the stack around it, mostly the PostgreSQL version and the Nginx HTTP/2 syntax:
| Item | Debian 13 (Trixie) | Debian 12 (Bookworm) |
|---|---|---|
| pgAdmin version tested | 9.19 | 9.19 (fresh and upgraded from 9.18) |
| Repository suite | trixie (from pgAdmin 9.8) | bookworm (from pgAdmin 7.0) |
| Default PostgreSQL | 17.11 | 15.19 |
| Apache | 2.4.69 | 2.4.68 |
| Nginx | 1.26.3, http2 on; | 1.22.1, listen 443 ssl http2; |
| certbot | 4.0.0 | 2.1.0 |
| pg_hba.conf path | /etc/postgresql/17/main/ | /etc/postgresql/15/main/ |
| Install and setup-web.sh steps | Identical | Identical |
If you’re starting fresh, take Debian 13. It gets the newer PostgreSQL by default and Nginx’s current HTTP/2 syntax, and both releases receive pgAdmin builds on the same day. Debian 12 remains a fine host until its own end of life, but every config you copy from newer guides will need the HTTP/2 edit above. For the same setup on Ubuntu, see our pgAdmin 4 on Ubuntu guide.
Thanks for this tutorial
Thanks and always welcome!
The first curl command to ‘…/packages_pgadmin_org’ led to a 404 response, could be temporary I suppose
never mind the previous comment, I realized it was a copy and paste error sorry