Databases Azure

pgAdmin 4 on Ubuntu 24.04 on Azure User Guide

| Product: pgAdmin 4 on Ubuntu 24.04 LTS on Azure

Overview

pgAdmin 4 is the most widely used open source administration and development platform for PostgreSQL. From an ordinary browser it gives you an object explorer over servers, databases, schemas, tables, functions and roles, a Query Tool with syntax highlighting, explain plans and result grids, a graphical ERD designer, import and export, backup, restore and maintenance jobs, and live dashboards showing sessions, locks, transactions and server activity.

This image ships pgAdmin on its own, in server mode, which is the multi user web deployment upstream provides for teams. It is not bundled with a database: you point it at the PostgreSQL instances you already run, whether that is Azure Database for PostgreSQL Flexible Server, a self managed cluster on your own virtual machines, or any reachable PostgreSQL endpoint from version 13 through 18. No PostgreSQL server runs on this VM, and no connection details ship in the image.

The cloudimg image ships pgAdmin 9.17 from the official pgAdmin package repository, served by Apache through mod_wsgi on a hardened, fully patched Ubuntu 24.04 LTS base. No administrator account and no configuration database exist in the image at all. A one shot service creates them, with secrets unique to that VM, on first boot. Backed by 24/7 cloudimg support.

What is included:

  • pgAdmin 4 version 9.17 in server mode, installed from the official pgAdmin APT repository, served by Apache with mod_wsgi and managed by systemd
  • PostgreSQL client tooling for every supported major version (13 through 18), wired into pgAdmin so backup, restore and maintenance jobs always run a version matched pg_dump, pg_restore and psql against whichever server you are connected to
  • A per VM administrator account and password generated on first boot and recorded in a root only file
  • Session signing and password salting secrets generated per VM, never baked into the image
  • A bootstrap gate: Apache refuses to serve until first boot has finished, so a partially initialised console is never exposed
  • No saved server connections, hostnames or database passwords anywhere in the image
  • An Apache configuration that is an explicit allowlist, so application source, configuration and the configuration database are never served
  • apache2.service and pgadmin-firstboot.service as enabled systemd units
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; move up to Standard_D2s_v3 if many people will use the console at once or you run large backup jobs from it. NSG inbound: allow 22/tcp from your management network and 80/tcp for the console, plus 443/tcp if you terminate TLS on the VM. You also need network reachability from this VM to whichever PostgreSQL servers you intend to manage, normally 5432/tcp, and those servers must allow connections from this VM's address.

Step 1 - Deploy from the Azure Marketplace

In the Azure portal choose Create a resource, search for pgAdmin 4 on Ubuntu 24.04 LTS by cloudimg, and select Create. Pick your subscription, resource group and region, choose the Standard_B2s size, select SSH public key authentication with the username azureuser, and allow inbound 22 and 80. pgAdmin keeps only its own small configuration database on this VM, so you do not need to attach any extra disk.

Step 2 - Deploy from the Azure CLI

The image is launched exactly like any other Marketplace VM. The two commands below create the VM and open the web ports; because they are specific to your subscription they are shown for reference.

az vm create \
  --resource-group <your-rg> \
  --name pgadmin \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name pgadmin --port 80,443

Step 3 - Confirm the service is running

SSH to the VM as azureuser and confirm Apache and the first boot service are both active. Apache serves on port 80 and nothing listens on 5432, because this appliance runs no local database.

systemctl is-active apache2 pgadmin-firstboot
curl -s -o /dev/null -w 'healthz: %{http_code}\n' http://127.0.0.1/healthz
curl -s -o /dev/null -w 'console: %{http_code}\n' http://127.0.0.1/pgadmin4/login

The version matched PostgreSQL client tooling that pgAdmin uses for backup, restore and maintenance is installed under /usr/lib/postgresql, one directory per supported server major version:

ls /usr/lib/postgresql

The apache2 and pgadmin-firstboot services reporting active, the cloudimg bootstrap gate drop in loaded, pgadmin4-server and pgadmin4-web at version 9.17, the wsgi and headers Apache modules loaded, Apache listening on port 80 with nothing on 5432, and PostgreSQL client tooling present for majors 13 through 18

Step 4 - Retrieve the per VM administrator password

The first boot service generates an administrator password unique to your VM and writes it to a root only file. Read it over SSH:

sudo cat /root/pgadmin-credentials.txt

The per VM pgAdmin credentials file showing the console URL, the administrator e mail address and the generated password masked, together with the configuration database and the three session signing and password salting secrets this VM generated for itself

PGADMIN_URL is the VM's own address as seen from inside the VM. Azure does not expose the public IP to the instance, so this is usually the private address. Browsing the public IP shown in the Azure portal works identically.

Step 5 - Sign in

Browse to http://<vm-ip>/, which redirects into the console at /pgadmin4/, and sign in with the e mail address and password from the credentials file.

The pgAdmin 4 sign in page with the administrator e mail address entered and the password masked, showing the language selector and the Login button

Change this password to one of your own from the user menu in the top right (admin@cloudimg.co.uk > Change Password) as soon as you sign in, and add named accounts for your colleagues as described in the multi user section below.

Step 6 - Register your first PostgreSQL server

pgAdmin manages servers you already run, so the object explorer starts empty. Choose Add New Server on the Welcome page, or Object > Register > Server from the menu. On the General tab give the connection a name. On the Connection tab enter the host name or address, the port (normally 5432), the maintenance database, and the user name and password pgAdmin should connect with.

The Register Server dialog on its Connection tab, with the host name or address, port 5432, maintenance database, user name and a masked password filled in, and the Save button ready

For Azure Database for PostgreSQL Flexible Server, use the server's fully qualified name as the host, your administrator login as the user name, and set SSL mode to require on the Parameters tab. Add this VM's address to the flexible server's firewall rules first, or place both in the same VNet.

Leave Save password off unless you want pgAdmin to store the database password. If you do turn it on, pgAdmin asks you to set a master password first; that master password encrypts any stored connection passwords and is never written to disk, so a stolen disk image does not yield your database credentials.

Once you save, pgAdmin connects and the server dashboard begins plotting live activity for that server: sessions, transactions per second, tuples in and out, and block I/O.

The pgAdmin dashboard for the newly connected server, showing live Server sessions, Transactions per second, Tuples in, Tuples out and Block I O charts plotting real activity, with the server listed in the object explorer

Step 7 - Browse objects and run SQL

Expand the tree to walk the server: Databases, then a database, then Schemas, public and Tables. Selecting an object shows its Properties, SQL, Statistics, Dependencies and Dependents in the panel on the right, all read live from the server.

The pgAdmin object explorer expanded through Databases, a database, Schemas, public and Tables with a table selected, and the Properties panel on the right showing the table name, OID, owner and tablespace read from the server

To run SQL, select a database and choose Tools > Query Tool. Type a statement and press F5 or the execute button. Results appear in the Data Output grid below, with the row count and elapsed time in the status bar, and can be exported to CSV from the grid toolbar.

The pgAdmin Query Tool with an aggregate SELECT statement in the editor and the Data Output grid below showing the returned rows with their column names and types, the row count and the query execution time

The Explain tab next to Data Output renders a graphical query plan for the statement in the editor, which is the fastest way to see why a query is slow.

Step 8 - Backup, restore and maintenance

Right clicking a database or table offers Backup..., Restore... and Maintenance..., which drive pg_dump, pg_restore and VACUUM or ANALYZE on your behalf. These tools must match the major version of the server being dumped, so this image installs the client tooling for every supported major version and wires each one into pgAdmin explicitly. You can see the mapping in the system configuration:

sudo grep -E "'pg-1[3-8]'" /etc/pgadmin/config_system.py

That means a backup of a PostgreSQL 18 server runs the version 18 pg_dump, and a backup of a version 15 server runs the version 15 one, with no configuration from you. Backup files are written to the pgAdmin storage area for your user and can be downloaded from the browser.

Step 9 - No known or default credentials

Nothing in this image is a shared secret. There is no administrator account and no configuration database in the image at all: pgAdmin keeps its session signing key, password salt and CSRF key inside the keys table of its configuration database and generates them when that database is first created, so shipping no database is what guarantees every VM gets its own.

pgadmin-firstboot.service runs once, before Apache is allowed to start. It creates the configuration database, which generates those three secrets, mints the administrator with a fresh random password, writes the root only credentials file, and only then creates the bootstrap ready marker that releases Apache. Apache carries a ConditionPathExists drop in for that marker, so on any boot where bootstrap has not completed the web server does not serve at all.

systemctl cat pgadmin-firstboot.service | sed -n '1,12p'
cat /etc/systemd/system/apache2.service.d/10-cloudimg-pgadmin-gate.conf

The Apache bootstrap gate drop in showing ConditionPathExists on the pgadmin ready marker, the pgadmin-firstboot unit ordered Before apache2.service and gated on its own sentinel, and both marker files present in /var/lib/cloudimg after a completed first boot

You can prove the per VM administrator really is the only login that works. The check below signs in through the real pgAdmin login form with the password from the credentials file, then confirms that a blank password, common weak guesses and unknown user names are all rejected:

sudo bash /usr/local/sbin/pgadmin-cred-roundtrip.sh

Step 10 - How the web server is locked down

The Apache virtual host is an explicit allowlist rather than a catch all. Exactly three URL spaces are served: / redirects into the console, /healthz is a static document for load balancer probes, and /pgadmin4 is the application. Everything else terminates in a 404, the document root is a permanently empty directory that denies everything, and the application directory denies everything except the single WSGI entry point. Application source, the system configuration and the configuration database are therefore not reachable over HTTP:

for p in /config.py /pgadmin4/config.py /etc/pgadmin/config_system.py /pgadmin4.db /server-status; do printf '%-34s ' "$p"; curl -s -o /dev/null -w '%{http_code}\n' "http://127.0.0.1$p"; done

Curl probes showing the health endpoint, the root redirect, the login page and a static asset all serving, while application source, the system configuration file, the configuration database and the Apache server status page all return 404, and the Server response header disclosing no version

The Server response header is set to Apache with no version or distribution, directory listings are off, and X-Content-Type-Options, X-Frame-Options and Referrer-Policy are set on every response.

Adding your own domain and TLS

The console ships on HTTP so it works the moment the VM boots. pgAdmin carries database credentials, so put it behind TLS before real use. Point a DNS record at the VM, open 443/tcp in the NSG, then install a certificate with certbot, which edits the shipped virtual host in place:

sudo apt-get update && sudo apt-get install -y certbot python3-certbot-apache
sudo certbot --apache -d pgadmin.your-domain.example

Alternatively terminate TLS on an Azure Application Gateway or Load Balancer in front of the VM and keep the VM itself on the private network, with the NSG allowing 80/tcp only from the gateway subnet.

Multi user access

pgAdmin is running in server mode, so it supports named accounts, each with their own registered servers and preferences. Add colleagues under File > Preferences, or from the user menu choose Users to open the user management screen, and give each person their own login with the Administrator or User role. Users can also be created from the command line:

sudo /usr/pgadmin4/venv/bin/python3 /usr/pgadmin4/web/setup.py get-users

Shared servers, which let one administrator publish a connection to everyone, are configured per server from the Register Server dialog's Shared? switch.

Backing up pgAdmin's own configuration

pgAdmin's registered servers, users and preferences live in one small SQLite file. Back it up with Apache stopped, or copy it with the SQLite backup API, and keep it somewhere outside the VM:

sudo ls -l /var/lib/pgadmin/pgadmin4.db

Restoring is the reverse: stop Apache, replace the file, restore its 0600 www-data:www-data ownership, and start Apache again. Your database contents are never on this VM, so this file is the only pgAdmin state worth backing up.

Server components

Component Version Notes
pgAdmin 4 9.17 Server mode, from the official pgAdmin APT repository
Apache HTTP Server 2.4.58 Serves the console through mod_wsgi at /pgadmin4
mod_wsgi Python 3 build Runs pgAdmin in a dedicated daemon process group
Python 3.12.3 Bundled pgAdmin virtual environment at /usr/pgadmin4/venv
PostgreSQL client tooling 13, 14, 15, 16, 17, 18 Version matched pg_dump, pg_restore and psql
Ubuntu 24.04 LTS Fully patched, unattended security upgrades enabled

Filesystem layout

Path Size Purpose
/ 29 GB Root filesystem
/boot 881 MB Operating system kernel files
/boot/efi 105 MB UEFI boot partition (Gen2 Hyper V)
/mnt varies Azure temporary resource disk

Key directories:

Path Purpose
/usr/pgadmin4/web The pgAdmin application and its WSGI entry point
/usr/pgadmin4/venv The bundled Python virtual environment
/etc/pgadmin/config_system.py System configuration, loaded last, survives package upgrades
/var/lib/pgadmin Configuration database, sessions and per user storage, mode 0700
/var/lib/pgadmin/pgadmin4.db The configuration database, mode 0600 www-data
/etc/apache2/sites-available/cloudimg-pgadmin.conf The allowlist virtual host
/usr/lib/postgresql/<major>/bin Version matched PostgreSQL client tooling

Scripts and log files

Path Purpose
/usr/local/sbin/pgadmin-firstboot.sh First boot bootstrap: config database, per VM secrets, administrator account
/usr/local/sbin/pgadmin-cred-roundtrip.sh Proves the per VM administrator authenticates and weak credentials do not
/root/pgadmin-credentials.txt The per VM administrator login, mode 0600 root:root
/var/lib/cloudimg/pgadmin-firstboot.done First boot sentinel, stops the bootstrap re running
/var/lib/cloudimg/pgadmin-ready Bootstrap ready marker that releases Apache
/var/log/pgadmin-firstboot.log First boot bootstrap log
/var/log/pgadmin/pgadmin4.log pgAdmin application log
/var/log/apache2/pgadmin-access.log Console access log
/var/log/apache2/pgadmin-error.log Console error log

Troubleshooting

The console does not answer at all and Apache is inactive. Bootstrap has not completed, so the gate is holding Apache back by design. Check the first boot log and the marker:

sudo tail -20 /var/log/pgadmin-firstboot.log
systemctl status pgadmin-firstboot --no-pager --lines=20

The login page loads but the password is rejected. Confirm you are using the e mail address from the credentials file, not a user name. pgAdmin locks an account after three failed attempts; the round trip script in Step 9 clears that counter as part of its check.

A server will not connect. The failure is almost always network or server side rather than pgAdmin. Confirm this VM can reach the database host and port, and that the database server accepts connections from this VM's address:

/usr/lib/postgresql/18/bin/psql --version

For Azure Database for PostgreSQL Flexible Server, check the firewall rules or VNet integration, and set SSL mode to require.

Backup or restore fails with a version mismatch. pgAdmin picks the client tooling from the mapping in /etc/pgadmin/config_system.py. If you connect to a server newer than version 18, add the matching client package and a corresponding pg-<major> entry there.

Security notes

  • Change the administrator password immediately, and create a named account per person rather than sharing one login.
  • Put the console behind TLS before real use, since it carries database credentials.
  • Restrict 80/tcp and 443/tcp in the NSG to the networks that need the console, and keep 22/tcp limited to your management network.
  • Leave Save password off, or set a master password so any stored connection passwords are encrypted at rest.
  • The image ships unattended security upgrades enabled, so the base stays patched.

Trademark

pgAdmin and PostgreSQL are trademarks of their respective holders. cloudimg is not affiliated with, endorsed by, or sponsored by the pgAdmin Development Team or the PostgreSQL Global Development Group. All product and company names are trademarks or registered trademarks of their respective holders.

Support

cloudimg provides 24/7 technical support for this image by e mail at support@cloudimg.co.uk and by live chat. We help with deployment, connecting to Azure Database for PostgreSQL and self managed clusters, network and firewall configuration, TLS setup, multi user administration, upgrades and troubleshooting. Critical issues receive a one hour average response time.