pgAdmin 4 on Ubuntu 24.04 on Azure User Guide
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_restoreandpsqlagainst 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.serviceandpgadmin-firstboot.serviceas enabled systemd units- An unauthenticated
/healthzendpoint 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

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

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.

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.

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.

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.

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 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

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

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/tcpand443/tcpin the NSG to the networks that need the console, and keep22/tcplimited 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.