How to Install PostgreSQL on a VPS (Ubuntu 24.04, PostgreSQL 18)

Install PostgreSQL 16 or 18 on an Ubuntu 24.04 VPS with the official commands, then cover what most guides skip: safe remote access, memory sizing per plan, tested backups and upgrades.

Title card reading “PostgreSQL on Ubuntu 24.04” for the HourlyVPS guide to installing PostgreSQL on a VPS

To install PostgreSQL on a VPS running Ubuntu 24.04, run sudo apt install postgresql for Ubuntu’s PostgreSQL 16, or add the official PostgreSQL apt repository and install postgresql-18 for the current major version. Either way, the server starts listening only on localhost, so the work that matters comes next: a role and database for your app, an access path that keeps port 5432 off the open internet, memory settings sized to your plan, and a backup you have restored at least once.

Every command and setting below comes from the PostgreSQL 18 documentation, the PostgreSQL apt repository wiki and Ubuntu’s server documentation, checked on October 3, 2026. On that date the current releases were 18.6 and 16.15, and PostgreSQL 19 was in beta (Beta 4 shipped on September 24, 2026). You need a fresh Ubuntu 24.04 server and a sudo user. If you have not logged in yet, connect to your VPS over SSH first, then work through the new VPS security checklist: SSH keys, no root login, updates, and UFW with SSH allowed.

Key takeaways

  • Ubuntu 24.04 ships PostgreSQL 16; the official PGDG apt repository adds PostgreSQL 18 (18.6 as of October 2026), which is supported until November 14, 2030.
  • Install postgresql-18 by name from the PGDG repository, because its postgresql meta-package moves to PostgreSQL 19 when that ships (planned for October 2026).
  • PostgreSQL listens only on localhost by default: use an SSH tunnel, or allow one client IP with a hostssl scram-sha-256 line plus a matching UFW rule, never 0.0.0.0/0 with md5.
  • Start shared_buffers at 25% of RAM and effective_cache_size at 1/2 to 3/4 of RAM on a database-only VPS, and size work_mem against max_connections.
  • A nightly pg_dump plus pg_dumpall --globals-only, copied off the server and test-restored, covers most small databases; add pg_basebackup and WAL archiving for point-in-time recovery.

Ubuntu’s PostgreSQL 16 or PostgreSQL 18 from the official repository?

Ubuntu ships one PostgreSQL major version per release and, in the words of postgresql.org’s Ubuntu download page, supports it “throughout the lifetime of that Ubuntu version”. On 24.04 that version is 16. The PostgreSQL Global Development Group (PGDG) also runs its own apt repository, which carries every supported major version (14 through 18 as of October 2026), plus the end-of-life 13 and the 19 beta. Both are official builds of the same software; the difference is which version you get, and who delivers the updates. On Debian 12 the distribution package is PostgreSQL 15, and the repository steps below work the same way there.

Ubuntu archive: postgresqlPGDG repository: postgresql-18
Major version on 24.0416 (16.15, in noble-security and noble-updates since August 20, 2026)18 (18.6, released August 13, 2026)
Community support endsNovember 9, 2028November 14, 2030
Automatic security updatesYes, through Ubuntu’s security pocket and unattended-upgradesNot by default; you run apt upgrade (see minor updates)
Extra setupNoneOne script, or a sources file plus a signing key
What 18 adds over 16Not availableAsynchronous I/O, uuidv7(), data checksums on by default, MD5 passwords deprecated
Pick it whenYour app supports 16 and you want no third-party repositoryYou start a new project or want the longest support window
Support dates from postgresql.org’s versioning policy; package versions from Ubuntu’s archive and postgresql.org’s release notes, checked October 3, 2026.

Tip: The project’s roadmap plans PostgreSQL 19 for October 2026. The PGDG repository’s postgresql meta-package always points at the newest release, so install the versioned package, postgresql-18, if you want to stay on the major version you chose. The repository’s wiki gives the same advice.

How to install PostgreSQL on a VPS running Ubuntu 24.04

Pick one of the two options, run it as your sudo user, then check the result. The rest of this guide uses version 18 in paths and unit names; on Ubuntu’s package, replace 18 with 16.

Option A: Ubuntu’s package (PostgreSQL 16)

sudo apt update
sudo apt install postgresql

That is the whole install, and it is the command postgresql.org lists for Ubuntu. Many guides add postgresql-contrib. On 24.04 that name only points at postgresql-16, which already contains the contrib extensions such as pg_stat_statements.

Option B: The PostgreSQL apt repository (PostgreSQL 18)

These two commands are the automated setup from postgresql.org’s Ubuntu download page. The script reads your release codename (noble), installs the repository’s signing key, adds apt.postgresql.org to your apt sources and runs apt-get update. It waits for you to press Enter before it changes anything.

sudo apt install -y postgresql-common
sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh

Then install the versioned server package:

sudo apt install postgresql-18

If you would rather see every line, this is the manual setup from the PostgreSQL wiki. It does the same thing:

sudo apt install curl ca-certificates
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
. /etc/os-release
sudo tee /etc/apt/sources.list.d/pgdg.sources <<EOF
Types: deb deb-src
URIs: https://apt.postgresql.org/pub/repos/apt
Suites: $VERSION_CODENAME-pgdg
Architectures: $(dpkg --print-architecture)
Components: main
Signed-By: /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc
EOF
sudo apt update
sudo apt install postgresql-18

Check that the cluster is running

Debian and Ubuntu run PostgreSQL as clusters, each named by version and cluster name, and manage them with the tools from postgresql-common. pg_lsclusters should print one line for 18 main on port 5432 with the status online. Each cluster has its own systemd unit, postgresql@18-main.

pg_lsclusters
sudo systemctl status postgresql@18-main --no-pager
sudo -u postgres psql -c "SELECT version();"
sudo -u postgres psql -c "SHOW data_checksums;"

On 18, data_checksums reports on: from version 18, initdb enables page checksums by default, so PostgreSQL can detect pages that storage errors corrupted. A cluster created by Ubuntu’s 16 reports off.

WhatPath on Ubuntu (version 18)
Main configuration/etc/postgresql/18/main/postgresql.conf
Drop-in configuration files/etc/postgresql/18/main/conf.d/*.conf
Client authentication/etc/postgresql/18/main/pg_hba.conf
Data directory/var/lib/postgresql/18/main
Server log/var/log/postgresql/postgresql-18-main.log
systemd unitpostgresql@18-main.service
The Debian and Ubuntu layout created by postgresql-common. Inside psql, SHOW config_file; and SHOW hba_file; print the paths.

Create a database user and a database for your app

Ubuntu logs local users in through peer authentication: the Linux user postgres connects as the database superuser postgres, with no password. Keep it that way, and give each application its own login role that owns its own database:

sudo -u postgres createuser --pwprompt appuser
sudo -u postgres createdb --owner=appuser appdb

--pwprompt asks for the password twice and stores it as a SCRAM-SHA-256 hash, the default password_encryption. Use a long random password (for example from openssl rand -base64 30) and keep it in your password manager or your app’s secret store.

Making the role the database owner matters on PostgreSQL 15 and later. Since version 15, ordinary roles no longer get CREATE on the public schema; the schema belongs to the database owner instead. A role that does not own the database gets permission denied for schema public on its first CREATE TABLE.

Test a password login over TCP from the server itself. Peer authentication only applies to the Unix socket, so -h 127.0.0.1 makes psql use the host rules and the password:

psql -h 127.0.0.1 -U appuser -d appdb -c "SELECT current_user, current_database();"

An app on the same server then connects with postgresql://appuser:[email protected]:5432/appdb. Nothing has to listen on a public address for that.

How do you allow remote connections to PostgreSQL safely?

By default PostgreSQL listens only on localhost (listen_addresses = 'localhost'), so nothing outside the server can reach it. Choose the narrowest path that covers who needs access:

Who connectsPathChanges on the server
An app on the same VPS127.0.0.1 or the Unix socketNone
You, from a laptop (psql, pgAdmin, DBeaver)SSH tunnelNone
An app on another serverA persistent SSH tunnel, or TLS with a one-IP allowlistTunnel: none. TLS: listen_addresses, one hostssl line, one UFW rule
Anyone on the internetDon’tNone
Use the first row that fits.

Option 1: Reach PostgreSQL through an SSH tunnel

An SSH local forward carries the database connection inside your SSH session. Port 5432 never opens to the internet, and pg_hba.conf needs no new line, because the connection arrives on the server from 127.0.0.1. Run this on your own computer, with your server’s user and IP:

ssh -N -L 5433:127.0.0.1:5432 [email protected]

Leave it open and connect to local port 5433 in a second terminal. Using 5433 avoids a clash with a PostgreSQL server on your laptop:

psql "host=127.0.0.1 port=5433 dbname=appdb user=appuser"

pgAdmin and DBeaver can open the same tunnel from their connection settings. For an app server that needs the database around the clock, run the tunnel as a systemd service with a key that may only forward to 127.0.0.1:5432. Our SSH port forwarding guide shows both the service and the restricted tunnel key.

Option 2: Open port 5432 to one IP, with TLS and SCRAM

Use this when an app on another server must connect directly. The example lets only the app server, 198.51.100.7, reach appdb as appuser; your database server is 203.0.113.10.

Step 1: listen on the network. listen_addresses only changes at server start, so put it in a drop-in file and restart:

echo "listen_addresses = '*'" | sudo tee /etc/postgresql/18/main/conf.d/20-listen.conf
sudo systemctl restart postgresql@18-main
sudo ss -ltnp | grep 5432

* means every interface, IPv4 and IPv6. pg_hba.conf and the firewall decide who gets past it.

Step 2: allow exactly one client in pg_hba.conf. A hostssl line only matches TLS connections, and scram-sha-256 is, in the docs’ words, “the most secure of the currently provided methods”:

echo "hostssl appdb appuser 198.51.100.7/32 scram-sha-256" | sudo tee -a /etc/postgresql/18/main/pg_hba.conf
sudo systemctl reload postgresql@18-main
sudo -u postgres psql -c "SELECT line_number, type, database, user_name, address, auth_method, error FROM pg_hba_file_rules;"

PostgreSQL reads pg_hba.conf from the top, and the first line that matches the connection type, address, database and user decides. There is no fall-through: if that line’s authentication fails, later lines are not tried. The pg_hba_file_rules view shows how the server parsed the file; a value in the error column marks a broken line. If the app server connects over IPv6, add a second line with its /128 address.

Step 3: open the firewall for that IP only:

sudo ufw allow from 198.51.100.7 to any port 5432 proto tcp
sudo ufw status numbered

Step 4: connect from the app server with TLS required:

psql "host=203.0.113.10 port=5432 dbname=appdb user=appuser sslmode=require"

Inside that session, SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid(); should show t and TLS 1.2 or newer, the server’s default minimum. Ubuntu’s PostgreSQL has TLS on out of the box, with a self-signed certificate that Ubuntu’s documentation calls suitable for testing.

sslmode=require encrypts the connection but does not check which server answered. For sslmode=verify-full, which the libpq documentation recommends for most security-sensitive environments, install a certificate issued for the server’s hostname (ssl_cert_file and ssl_key_file) and give the client the CA certificate. Note that the default sslmode on the client, prefer, offers no protection against an attacker in the middle.

Warning: Many tutorials add host all all 0.0.0.0/0 md5. That line invites every address on the internet to guess passwords for every database, accepts unencrypted connections (host matches TLS and plain connections alike), and relies on MD5, which PostgreSQL 18 deprecates.

If PostgreSQL runs in Docker with -p 5432:5432, UFW does not filter that port at all. See why Docker bypasses UFW and bind it to 127.0.0.1 instead.

PostgreSQL tuning on a VPS: shared_buffers, effective_cache_size and work_mem

PostgreSQL’s defaults are deliberately small: shared_buffers 128 MB, work_mem 4 MB, maintenance_work_mem 64 MB and max_connections 100. On a VPS with a known amount of RAM, five settings do most of the work. The rules come from the PostgreSQL 18 resource documentation and the PostgreSQL wiki’s tuning guide:

  • shared_buffers: PostgreSQL’s own page cache. For a dedicated database server with 1 GB of RAM or more, the docs suggest 25% of RAM as a starting value, and say more than 40% is unlikely to help, because PostgreSQL also relies on the operating system’s cache. Changes need a restart.
  • effective_cache_size: not an allocation, only the planner’s estimate of how much data shared_buffers and the OS cache hold together. The wiki calls half of RAM a normal conservative setting and 3/4 “more aggressive but still reasonable”.
  • work_mem: memory for each sort or hash step before it spills to temporary files. One query can run several such steps, and every connection can do so at once, so the docs warn the total “could be many times the value”. Hash steps may use work_mem times hash_mem_multiplier (2.0 by default).
  • maintenance_work_mem: memory for VACUUM, CREATE INDEX and restores. Safe to set well above work_mem, but each autovacuum worker may use this much too.
  • max_connections: each connection is its own server process, and the work_mem worst case grows with it. The wiki points to connection pooling if you need thousands.

Starting values for each VPS size

The table applies those rules to each HourlyVPS memory size, for a VPS that runs only PostgreSQL. The last three columns are our own arithmetic, not PostgreSQL’s: max_connections times work_mem stays at or below a quarter of RAM, rounded down to a power of two and never below the 4 MB default, and maintenance_work_mem is 1/16 of RAM, capped at 1 GB.

RAM (plans)shared_bufferseffective_cache_sizework_memmaintenance_work_memmax_connections
1 GB (Q1)256MB768MB4MB64MB50
2 GB (Q2)512MB1536MB8MB128MB50
4 GB (Q4)1GB3GB8MB256MB100
8 GB (Q8, C8)2GB6GB16MB512MB100
16 GB (Q16, C16)4GB12GB32MB1GB100
32 GB (C32)8GB24GB64MB1GB100
Starting points for a database-only VPS: shared_buffers at 25% and effective_cache_size at 3/4 of RAM, from the PostgreSQL docs and wiki. Measure, then adjust.

If your app runs on the same VPS, it needs part of that RAM. Our rule of thumb for a shared server: about 15% of RAM for shared_buffers, half of RAM for effective_cache_size, and only as many connections as your app’s pool opens.

Apply the settings with a drop-in file

Ubuntu’s postgresql.conf has include_dir = 'conf.d' near its end, added by postgresql-common, so every .conf file in that directory is read after the settings above it and overrides them. One small file keeps your changes in a single place you can copy to the next server. For a 4 GB VPS:

sudo tee /etc/postgresql/18/main/conf.d/10-memory.conf <<'EOF'
# Starting values for a 4 GB VPS that runs only PostgreSQL
shared_buffers = 1GB
effective_cache_size = 3GB
work_mem = 8MB
maintenance_work_mem = 256MB
max_connections = 100
EOF
sudo systemctl restart postgresql@18-main
sudo -u postgres psql -c "SHOW shared_buffers;" -c "SHOW work_mem;"

shared_buffers and max_connections only change at a restart; the other three also apply after sudo systemctl reload postgresql@18-main. If the server does not come back, the reason is in /var/log/postgresql/postgresql-18-main.log. Avoid mixing methods: ALTER SYSTEM writes to postgresql.auto.conf, which is read last and wins over your drop-in.

What about random_page_cost? The wiki notes that many admins lower it on fast storage, but lists it last: check autovacuum, statistics and memory first, and change it only when EXPLAIN shows a plan you can prove is wrong.

Tip: On 1 GB and 2 GB plans, a swap file is a cheap safety net against the out-of-memory killer during a spike. Swap is not extra RAM: if the server swaps all day, move up a size.

How do you back up PostgreSQL on a VPS? pg_dump vs pg_basebackup

The PostgreSQL docs describe three approaches: SQL dumps, file-system-level copies and continuous archiving. On a single VPS, two bundled tools cover nearly every case:

pg_dump + pg_dumpall --globals-onlypg_basebackup
What it copiesOne database as a logical archive; roles come from pg_dumpallEvery file of the whole cluster (physical copy)
Restores into a newer major versionYes; the docs call this an important advantageNo; file-level backups are “extremely server-version-specific”
Point-in-time recoveryNo, only the moment of the dumpYes, together with WAL archiving
Restore a single tableYes, with pg_restoreNo, the whole cluster
NeedsRead access to what it dumps; runs while the app writes, without blocking readers or writersA REPLICATION role or superuser and a replication line in pg_hba.conf (Ubuntu’s default allows local)
FitsMost small VPS databases, nightlyLarger databases, PITR, building a replica
Compiled from the PostgreSQL 18 pg_dump, pg_basebackup and SQL Dump documentation.

The pg_dump reference adds a caveat: “except in simple cases, pg_dump is generally not the right choice for taking regular backups of production databases.” A small app database restored from last night’s dump is such a simple case. Once losing a day of writes is unacceptable, add pg_basebackup with continuous archiving for point-in-time recovery.

A nightly dump with a systemd timer

This script dumps every database in the compressed custom format plus the cluster-wide roles, and keeps a week of files on the server. It runs as the postgres user, so peer authentication logs it in without a password. First create a directory only postgres can read:

sudo install -d -o postgres -g postgres -m 700 /var/backups/postgresql
sudo tee /usr/local/bin/pg-nightly-dump <<'EOF'
#!/bin/bash
# Dump every database plus roles; keep 7 days. Runs as the postgres user.
set -euo pipefail
umask 077
dest=/var/backups/postgresql
stamp=$(date +%F-%H%M)
pg_dumpall --globals-only > "$dest/globals-$stamp.sql"
for db in $(psql -AtX -c "SELECT datname FROM pg_database WHERE datallowconn AND NOT datistemplate"); do
  pg_dump -Fc -f "$dest/$db-$stamp.dump" "$db"
done
find "$dest" -type f -mtime +7 -delete
EOF
sudo chmod 755 /usr/local/bin/pg-nightly-dump

Create a service that runs it as postgres, and a timer that starts the service at 03:15 every night:

sudo tee /etc/systemd/system/pg-nightly-dump.service <<'EOF'
[Unit]
Description=Dump all PostgreSQL databases

[Service]
Type=oneshot
User=postgres
ExecStart=/usr/local/bin/pg-nightly-dump
EOF
sudo tee /etc/systemd/system/pg-nightly-dump.timer <<'EOF'
[Unit]
Description=Nightly PostgreSQL dump

[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true

[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now pg-nightly-dump.timer

Run it once by hand, then check the files and the log:

sudo systemctl start pg-nightly-dump.service
sudo ls -lh /var/backups/postgresql
journalctl -u pg-nightly-dump.service -n 20 --no-pager

Persistent=true catches up on a run the server missed while it was off. The timer section of our systemd guide explains OnCalendar and Persistent in depth. The script assumes database names without spaces.

A dump on the same disk disappears with the server, so copy /var/backups/postgresql off the VPS every night, for example with restic to S3-compatible object storage, which adds encryption and retention. An Uptime Kuma push monitor can alert you when the nightly job stops reporting in.

A physical base backup with pg_basebackup

pg_basebackup copies the whole cluster over a replication connection. Ubuntu’s default pg_hba.conf already allows local replication connections for the postgres user, so this works as is. Give base backups their own directory, so the dump script’s seven-day cleanup does not touch them:

sudo install -d -o postgres -g postgres -m 700 /var/backups/pg-base
sudo -u postgres pg_basebackup -D /var/backups/pg-base/base-$(date +%F) -Ft -z -P

The result is a directory with base.tar.gz, pg_wal.tar.gz and a backup_manifest. It restores only into the same major version, and without WAL archiving it restores to the moment the backup finished, not to any point after it.

Prove the restore on an hourly server

A backup you have never restored is a guess. Deploy a Quartz Q2, billed by the hour at $0.02/hour, install the same PostgreSQL major version or a newer one as above, and copy last night’s globals file and dump into your home directory (with scp, or from your restic repository). Then restore:

sudo -u postgres psql -X < ~/globals-2026-10-03-0315.sql
sudo -u postgres pg_restore --create -d postgres < ~/appdb-2026-10-03-0315.dump
sudo -u postgres psql -d appdb -c "\dt"

The globals file reports that role postgres already exists; that error is harmless. pg_restore --create creates appdb and restores into it. Restore into a newer major version and you have also rehearsed your next upgrade.

A two-hour drill costs $0.04, paid from the initial credit the server is ordered with. Delete the server when you finish, rather than only stopping it. A stopped server is still billed, because its vCPU, memory, disk and IP addresses stay reserved for you; only deleting the server stops billing.

How do you upgrade PostgreSQL on Ubuntu?

Minor releases: apt, every quarter

Minor releases, such as 18.4 to 18.6 (18.5 was never released), fix bugs and security issues without changing the data format. The project aims to ship at least one each quarter, targeting the second Thursday of February, May, August and November; the next is due on November 12, 2026. Installing one means replacing the binaries and restarting, which apt does for you, so expect a brief disconnect:

sudo apt update
sudo apt upgrade

Ubuntu publishes its postgresql-16 updates to the security pocket (16.15 landed there on August 20, 2026), so unattended-upgrades installs them, because its default Allowed-Origins list holds Ubuntu’s own archives. PGDG packages are not on that list, so on Option B, run the upgrade yourself after each release date. Read the release notes first: the 18.6 notes, for example, say its first three security fixes may need configuration adjustments or data cleanups after you update.

Major upgrades: from 16 to 18 with pg_upgradecluster

A new major version changes the on-disk format, so the data must be dumped and reloaded or converted with pg_upgrade. Ubuntu’s postgresql-common wraps both in pg_upgradecluster, which copies your configuration to the new cluster and swaps the ports, so clients keep using 5432. Take a fresh dump, then:

1. Install the new version. Add the PGDG repository (Option B), then:

sudo apt install postgresql-18
pg_lsclusters

2. Remove an empty target cluster, if one appeared. postgresql-common before version 269 (Ubuntu 24.04 ships 257) creates an empty 18/main on port 5433 when postgresql-18 installs; newer versions, such as the PGDG repository’s, create one only on a server with no clusters. pg_upgradecluster stops with “target cluster 18/main already exists”, so only if pg_lsclusters lists an empty 18/main:

sudo pg_dropcluster 18 main --stop

3. Upgrade. The default method dumps and reloads, which is slow but safe:

sudo pg_upgradecluster -v 18 16 main

4. Check and test. pg_lsclusters should now show 18/main online on 5432 and 16/main down on 5433. Test your app, then refresh the planner statistics as the pg_upgrade docs recommend:

sudo -u postgres vacuumdb --all --analyze-in-stages --missing-stats-only
sudo -u postgres vacuumdb --all --analyze-only

5. Remove the old version once you are satisfied:

sudo pg_dropcluster 16 main
sudo apt purge postgresql-16 postgresql-client-16
MethodOptionSpeedExtra disk during the upgradeOld cluster afterwards
dump (default)noneSlowest; rewrites all data and indexesAbout one more copy of your dataKept on another port, set to manual start
upgrade-m upgradeFaster; pg_upgrade copies the filesAbout one more copy of your dataKept on another port, set to manual start
link-m linkFastest; hard links, no copyingLittleUnusable once the new cluster starts
From pg_upgradecluster(1) on Ubuntu 24.04 and the pg_upgrade documentation. Extra-disk figures are our estimate.

One detail for 18: with the PGDG repository’s pg_upgradecluster, the dump method gives the new cluster data checksums, the version 18 default. Upgrade and link keep the old cluster’s setting (off on a 16 cluster Ubuntu created), because pg_upgrade requires matching checksum settings. Rehearse the whole run on an hourly server first: a three-hour rehearsal on a Quartz Q4 costs $0.09.

How do you monitor PostgreSQL on a VPS?

On a small database server, watch four things: whether it runs, how many connections it holds, which queries are slow, and how full the disk is. You can check all four without a monitoring stack. From the shell:

pg_lsclusters
sudo tail -n 50 /var/log/postgresql/postgresql-18-main.log
df -h /var/lib/postgresql

Then, inside sudo -u postgres psql:

-- Connections by state (compare the total with max_connections)
SELECT state, count(*) FROM pg_stat_activity GROUP BY state;

-- Queries running for longer than a minute
SELECT pid, now() - query_start AS runtime, left(query, 60) AS query
FROM pg_stat_activity
WHERE state = 'active' AND now() - query_start > interval '1 minute'
ORDER BY runtime DESC;

-- Database sizes
SELECT datname, pg_size_pretty(pg_database_size(datname)) AS size
FROM pg_database ORDER BY pg_database_size(datname) DESC;

-- Share of reads served from shared_buffers
SELECT datname, round(100.0 * blks_hit / nullif(blks_hit + blks_read, 0), 1) AS hit_pct
FROM pg_stat_database WHERE datname IS NOT NULL;

The statistics docs note that blks_hit counts only PostgreSQL’s own buffer cache, not the operating system’s, so a lower hit_pct does not prove disk reads. Watch the trend instead: if it falls as the database grows, the hot data no longer fits in shared_buffers.

Find slow queries with pg_stat_statements

The pg_stat_statements extension ships with the server package. It needs shared memory, so load it at startup, restart, and create the extension in the database you want to inspect:

echo "shared_preload_libraries = 'pg_stat_statements'" | sudo tee /etc/postgresql/18/main/conf.d/30-pg-stat-statements.conf
sudo systemctl restart postgresql@18-main
sudo -u postgres psql -d appdb -c "CREATE EXTENSION IF NOT EXISTS pg_stat_statements;"

The five statements that cost the most total time:

SELECT calls, round(total_exec_time) AS total_ms, round(mean_exec_time::numeric, 1) AS mean_ms,
       left(query, 60) AS query
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;

To also see slow statements in the server log, set log_min_duration_statement. With 500ms, every statement that runs that long or longer is logged with its duration, and a reload applies it:

echo "log_min_duration_statement = 500ms" | sudo tee /etc/postgresql/18/main/conf.d/40-slow-log.conf
sudo systemctl reload postgresql@18-main

What size VPS does PostgreSQL need, and what does it cost?

PostgreSQL starts with small defaults (128 MB of shared_buffers), so the server itself rarely decides the plan. Your hot data does: the tables and indexes your queries touch all the time. Keep that in RAM and most reads never reach the disk.

Our sizing reasoning, not a PostgreSQL rule: on a database-only server, the hot data should fit in roughly 3/4 of RAM (shared_buffers plus the OS cache). Disk must hold your data, up to 1 GB of WAL with the default max_wal_size, a week of local dumps, and room for a second copy of the data during a dump-based major upgrade.

WorkloadHot data (our estimate)PlanHourlyMonthly cap
Learning, a dev database, a restore drillUnder 0.5 GBQuartz Q1 (1 vCPU, 1 GB, 25 GB NVMe)$0.01/hour$5.00
App and database on one serverUp to about 1 GBQuartz Q2 (1 vCPU, 2 GB, 50 GB NVMe)$0.02/hour$10.00
Dedicated database for a small production appUp to about 3 GBQuartz Q4 (2 vCPU, 4 GB, 80 GB NVMe)$0.03/hour$15.00
Busier app, more connections, reporting queriesUp to about 6 GBQuartz Q8 (4 vCPU, 8 GB, 160 GB NVMe)$0.05/hour$25.00
Heavy queries all day that need steady CPUUp to about 6 GBChrono C8 (2 dedicated vCPU, 8 GB, 100 GB NVMe)$0.07/hour$35.00
Larger working setUp to about 12 GBChrono C16 (4 dedicated vCPU, 16 GB, 200 GB NVMe)$0.13/hour$65.00
Hot-data limits are our arithmetic (about 3/4 of RAM on a database-only server). Prices render from the current price list. The monthly cap is the most one server is charged in a billing period; below it you pay by the hour.

To measure your own hot data, start with the database sizes from the monitoring queries: if the whole database fits in 3/4 of RAM, so does the hot part. Quartz plans share their vCPUs; Chrono plans pin dedicated vCPUs to your server, which suits a database that runs heavy queries around the clock.

A production database runs 24/7, and you do not pick a separate plan for that. The same server is billed by the hour, and its charges stop at the plan’s monthly price in each billing period (one month from your order date), which makes it a monthly VPS with no contract and no prepayment. Short jobs, such as restore drills, upgrade rehearsals and load tests, pay only for their hours. One plan across all four:

Quartz Q4 (2 vCPU, 4 GB): an upgrade rehearsal, a day, a week and a month of PostgreSQL, billed by the hour and capped at the monthly price
DurationHours on the meterCost $0.03/hour · cap $15.00/monthNote
3 hours3$0.09
1 day24$0.72
7 days168$5.04
30 days720$15.00Capped at the monthly price
Charges stop at $15.00 after 500 hours (about 20.8 days) in a billing period; the rest of that period is free.

The hourly vs monthly guide explains why a capped hourly bill never costs more than paying by the month, and the VPS cost calculator prices any other duration. Put the database in the same city as the app: every query is a network round trip, so an app in Europe talking to a database in North America pays the transatlantic delay on each one. The VPS location guide has the latency math. HourlyVPS deploys in Istanbul today, and New York is coming soon; see locations.

How billing works: Every server is billed by the hour: the plan’s hourly rate is deducted from the server’s prepaid balance for every hour it exists, powered on or off, until you delete it. Ordering a server takes an initial credit of $5 ($9 for Chrono C32, $16 for Chrono C64). It is prepaid credit, not a fee: the server’s hourly usage is deducted from it, and when it runs out you top up. For a database that must stay up, one rule matters most: When a server’s balance is down to about 24 hours of usage, you get a top-up invoice; at zero balance the server is suspended, and a suspended server is deleted 7 days later. Account top-ups start at $5. The hourly billing guide walks through the monthly cap with worked examples, and pricing lists every plan’s hourly price and cap.

PostgreSQL connection errors on a VPS and how to fix them

Error or symptomLikely causeFix
connection to server on socket "/var/run/postgresql/.s.PGSQL.5432" failed: No such file or directoryThe cluster is down, or runs on another port (5433 after an upgrade)pg_lsclusters, then sudo systemctl start postgresql@18-main and read the log
Peer authentication failed for user "appuser"psql used the Unix socket, where Ubuntu uses peer authentication, from a Linux user with a different nameAdd -h 127.0.0.1 to log in with the password
Connection refused with Is the server running on that host and accepting TCP/IP connections?listen_addresses is still localhost, or the server was not restarted after the changesudo ss -ltnp | grep 5432; fix the drop-in and restart
The connection times outUFW, or a firewall on the client side, drops the packetssudo ufw status; allow the client’s IP on 5432
no pg_hba.conf entry for host "198.51.100.7", user "appuser", database "appdb", no encryptionNo line matches. “no encryption” means the client did not use TLS, and your line is hostsslConnect with sslmode=require, or correct the address, database or user in the line, then reload
password authentication failed for user "appuser"Wrong password, or the role has noneSet it again with \password appuser in sudo -u postgres psql
permission denied for schema publicPostgreSQL 15 and later no longer let every role create objects in publicALTER DATABASE appdb OWNER TO appuser; or GRANT CREATE ON SCHEMA public TO appuser;
sorry, too many clients alreadymax_connections is used up, often by idle connectionsCheck pg_stat_activity; cap the app’s pool or add a pooler such as PgBouncer before raising max_connections
The server will not start after a config changeA typo, or a memory setting larger than the server can provideRead the last lines of /var/log/postgresql/postgresql-18-main.log, fix the drop-in, restart
Error texts as PostgreSQL 18 prints them; host, user and database names are examples.

Retiring a database server later? Work through the checklist before you delete a VPS first: a final dump copied off the server, and the firewall allowlists on other machines that still trust its IP.

Deploy this setup

Run PostgreSQL 18 for your app 24/7

Quartz Q4 · 2 shared vCPU · 4 GB RAM · 80 GB NVMe · Istanbul

  • Per hour$0.03/hour
  • Per day (24 h)$0.72/day
  • Monthly cap$15.00/monthFor this job
Deploy Quartz Q4

Starts with a $5 initial credit, which goes into the server’s balance and pays for its hours.

Billed by the hour, never more than $15.00 per billing period. Delete the server and billing stops.

FAQ

Which PostgreSQL version does Ubuntu 24.04 install?

PostgreSQL 16. As of October 2026, noble-updates carries 16.15, and Ubuntu keeps 24.04 on major version 16 for the life of the release. For PostgreSQL 17 or 18, add the official PGDG apt repository and install postgresql-17 or postgresql-18.

How much RAM does PostgreSQL need on a VPS?

The PostgreSQL documentation sets no minimum RAM, and the defaults (128 MB of shared_buffers) start on a 1 GB server. Size RAM to your hot data instead: the tables and indexes you query often should fit in roughly three quarters of RAM on a database-only VPS.

Should I run PostgreSQL in Docker or install it with apt?

Both run the same PostgreSQL. An apt install gives you systemd units, pg_upgradecluster and, with Ubuntu's package, automatic security updates; Docker suits a Compose stack where the app and database ship together. In Docker, never publish 5432 on all interfaces, because published ports bypass UFW.

Is it safe to open port 5432 to the internet?

Only to specific addresses. Allow one client IP with a hostssl line that uses scram-sha-256 in pg_hba.conf, plus a matching firewall rule. For your own access from a laptop, an SSH tunnel needs no open database port at all.

Where are postgresql.conf and pg_hba.conf on Ubuntu?

In /etc/postgresql//main/, for example /etc/postgresql/18/main/pg_hba.conf, while the data lives in /var/lib/postgresql//main. Inside psql, SHOW config_file; and SHOW hba_file; print the exact paths.

Should I wait for PostgreSQL 19?

PostgreSQL 19 is planned for October 2026 (Beta 4 shipped on September 24), but 18 is supported until November 14, 2030, and moving to 19 later is one pg_upgradecluster run. There is no need to wait before starting a new project.

How do I restart or reload PostgreSQL on Ubuntu?

Run sudo systemctl restart postgresql@18-main to restart one cluster, or reload instead of restart to apply pg_hba.conf and most settings without dropping connections. listen_addresses, shared_buffers and max_connections only change with a full restart.

Sources

  1. Linux downloads (Ubuntu)PostgreSQL Global Development Group · postgresql.org · checked
  2. Versioning PolicyPostgreSQL Global Development Group · postgresql.org · checked
  3. Apt: PostgreSQL packages for Debian and UbuntuPostgreSQL wiki · wiki.postgresql.org · checked
  4. Install and configure PostgreSQLUbuntu Server documentation · ubuntu.com · checked
  5. The pg_hba.conf File (PostgreSQL 18)PostgreSQL Documentation · postgresql.org · checked
  6. Resource Consumption (PostgreSQL 18)PostgreSQL Documentation · postgresql.org · checked
  7. Tuning Your PostgreSQL ServerPostgreSQL wiki · wiki.postgresql.org · checked
  8. SQL Dump (PostgreSQL 18)PostgreSQL Documentation · postgresql.org · checked
  9. pg_upgradecluster: upgrade an existing PostgreSQL cluster (Ubuntu 24.04)Ubuntu Manpages · manpages.ubuntu.com · checked
  10. PostgreSQL 18.0 release notesPostgreSQL Global Development Group · postgresql.org · checked
All posts