Databases

Install PostgreSQL 17 on Ubuntu 26.04 / 24.04

Ubuntu 26.04 ships PostgreSQL 18 in its archive and Ubuntu 24.04 ships 16, so neither release gives you PostgreSQL 17 from apt install postgresql. The only clean source on both is the PostgreSQL project’s own PGDG repository. It builds 17 for each Ubuntu codename and keeps it patched until the 17 line reaches end of life in November 2029. That makes 17 a sound choice when your application or extensions are certified on it, as long as you plan the jump to 18.

Original content from computingforgeeks.com - post 66534

This guide shows you how to install PostgreSQL 17 on Ubuntu 26.04 and 24.04 from PGDG, create an application role, open remote access over TLS, benchmark the box with pgbench and later move to 18 with pg_upgradecluster, which silently drops your conf.d settings. Every command below ran in October 2026 on Ubuntu 26.04.1 and 24.04.4 with PostgreSQL 17.11, including the upgrade to 18.6 on both releases.

Check which PostgreSQL your Ubuntu ships

Check before you add a repository, because the right answer depends on the release. If you just want the newest major on 26.04, the archive package is already 18 and our PostgreSQL 18 on Ubuntu 26.04 guide needs no third-party repository at all. This page is for the case where you need 17 specifically. This is what apt-cache policy reported on our two VMs:

Releasepostgresql in the Ubuntu archivepostgresql-17 from PGDGpostgresql-common in the archive
Ubuntu 26.04 (resolute)1817.11-1.pgdg26.04+2290ubuntu1
Ubuntu 24.04 (noble)1617.11-1.pgdg24.04+2257build1.1

PGDG publishes resolute-pgdg and noble-pgdg, so the same commands work on both releases. PGDG also ships its own, newer postgresql-common: on our 24.04 server, installing postgresql-18 later replaced the archive’s 257build1.1 with 293.pgdg24.04+1, and because the PGDG build is the candidate, a plain apt upgrade pulls it in too. The PostgreSQL versioning policy gives every major five years of fixes, which is why 17 is still a supported target a year after 18 shipped.

Prerequisites

Size the server from the workload, not from our VM. PostgreSQL relies on two caches: shared_buffers, usually about 25% of RAM, plus the kernel page cache for the rest, so RAM should cover your hot data with room to spare. Disk is the database plus WAL plus about 30% headroom, on storage with fast fsync, because every commit waits for the WAL flush. Most production boxes we build start at 8 GB of RAM and go up to 64 GB for busy OLTP. You need:

  • Ubuntu 26.04 or 24.04 server
  • a user with sudo rights
  • outbound HTTPS to apt.postgresql.org
  • TCP 5432 from your app subnet, only if remote clients connect

Our VMs had 2 vCPUs, 4 GB of RAM and 40 GB disks, which is a floor for following along, not a production recommendation. If you want to understand MVCC, vacuum and query plans before you tune anything, pick one of the PostgreSQL books we recommend and read it alongside this setup.

Set the shell variables

The database, role, password and client network appear in several commands, so export them once. APP_SUBNET is the network your application servers connect from, and you only need it for the remote access section:

export DB_NAME="appdb"
export DB_USER="appuser"
export DB_PASS="App#Pass2026x"
export APP_SUBNET="10.0.1.0/24"

Use your own password, and avoid a single quote in it because the role command wraps it in quotes. The variables vanish when you log out, so run the block again if you come back in a new shell.

Add the PGDG repository

The PostgreSQL project ships a setup script inside postgresql-common that detects your codename, installs the signing key and writes a deb822 source file. It’s the method the official Ubuntu download page lists first, and it replaces the old echo deb ... > pgdg.list plus trusted.gpg.d recipe, which trusted the key for every repository on the system:

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

The script names the codename it picked and refreshes the package lists. On 26.04 it reported this:

This script will enable the PostgreSQL APT repository on apt.postgresql.org on
your system. The distribution codename used will be resolute-pgdg.

Using keyring /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
Writing /etc/apt/sources.list.d/pgdg.sources ...

Confirm that apt now offers 17 from PGDG. The Ubuntu archive has no postgresql-17 package on either release, so any candidate here came from the new source.

apt-cache policy postgresql-17 | head -6

The candidate carries a pgdg suffix for your release. On 24.04 the version string ends in pgdg24.04+2 instead:

postgresql-17:
  Installed: (none)
  Candidate: 17.11-1.pgdg26.04+2
  Version table:
     17.11-1.pgdg26.04+2 500
        500 https://apt.postgresql.org/pub/repos/apt resolute-pgdg/main amd64 Packages

If the candidate line says (none), the script did not finish or your server cannot reach apt.postgresql.org over HTTPS, so fix that before going further.

Install PostgreSQL 17

Install the server package by its versioned name. Do not install the unversioned postgresql metapackage here: once PGDG is added it points at the newest major, 18, on both releases (and on 26.04 the archive’s own metapackage is 18 already), so it would pull in postgresql-18 as well.

sudo apt install -y postgresql-17

The package creates a cluster called 17/main and starts it during the install, so there is nothing to initialize by hand. Check the cluster and the listener.

pg_lsclusters
psql --version
sudo ss -tlnp | grep 5432

A healthy install is online on port 5432 and listens on loopback only:

Ver Cluster Port Status Owner    Data directory              Log file
17  main    5432 online postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
psql (PostgreSQL) 17.11 (Ubuntu 17.11-1.pgdg26.04+2)
LISTEN 0      200        127.0.0.1:5432      0.0.0.0:*    users:(("postgres",pid=32144,fd=7))
LISTEN 0      200            [::1]:5432         [::]:*    users:(("postgres",pid=32144,fd=6))                     

The process that actually runs is the postgresql@17-main unit, while postgresql.service is an umbrella that starts or stops every cluster on the box together. Use the per-cluster unit in systemctl status and journalctl when you are debugging, or you will be reading the wrong log.

Create a role and database

Leave the postgres superuser on peer authentication, which means only the postgres Linux account can use it, and give your application its own login role that owns one database. Older guides set a password on postgres and switched local authentication to trust, which lets any local user connect as any role without a password, so do not copy that. Create the role and the database:

sudo -u postgres psql -c "CREATE ROLE ${DB_USER} WITH LOGIN PASSWORD '${DB_PASS}';"
sudo -u postgres createdb -O "${DB_USER}" "${DB_NAME}"

Test the role over TCP on loopback, which is how most applications on the same box connect. The -h 127.0.0.1 matters, because it routes the login through the scram-sha-256 rule in pg_hba.conf rather than the peer rule for the Unix socket:

PGPASSWORD="${DB_PASS}" psql -h 127.0.0.1 -U "${DB_USER}" -d "${DB_NAME}" -c "SELECT current_user, current_database(), inet_server_addr();"

You should get the role, the database and the loopback address back:

 current_user | current_database | inet_server_addr
--------------+------------------+------------------
 appuser      | appdb            | 127.0.0.1
(1 row)

Passwords are stored as SCRAM hashes by default, and SHOW password_encryption; returned scram-sha-256 on our 26.04 server, so there is nothing to switch from MD5. If your application runs on this server, the core setup ends here. The next section is optional.

Allow remote connections over TLS (optional)

Remote access needs three changes, and each one produces a different error if it’s missing:

  1. PostgreSQL listens on the network
  2. a pg_hba.conf rule admits the client subnet
  3. UFW allows 5432 from that subnet

Ubuntu’s cluster config already includes every .conf file in a conf.d directory, so put the listener change in its own file rather than editing the 800-line postgresql.conf. It’s easier to audit, and it matters again when you upgrade. Create the file:

sudo vim /etc/postgresql/17/main/conf.d/listen.conf

It needs a single line. A star means every interface, and the firewall and pg_hba.conf do the filtering.

listen_addresses = '*'

Next, admit the application role to its own database from the app subnet. The hostssl type only matches encrypted sessions, and Ubuntu’s cluster already has ssl = on with the snakeoil certificate, so clients get TLS without extra work and plain-text logins are refused:

echo "hostssl ${DB_NAME} ${DB_USER} ${APP_SUBNET} scram-sha-256" | sudo tee -a /etc/postgresql/17/main/pg_hba.conf
sudo systemctl restart postgresql@17-main

A restart is needed because listen_addresses only changes at server start; a pg_hba.conf edit alone would need just a reload. Ask the server how it parsed the rules, which catches typos before a client does:

sudo -u postgres psql -c "SELECT line_number, type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE address IS NOT NULL;"

Your rule should be the last row. A row with an error value instead means the line did not parse and the server will ignore it:

 line_number |  type   |   database    | user_name |   address   |  auth_method
-------------+---------+---------------+-----------+-------------+---------------
         125 | host    | {all}         | {all}     | 127.0.0.1   | scram-sha-256
         127 | host    | {all}         | {all}     | ::1         | scram-sha-256
         131 | host    | {replication} | {all}     | 127.0.0.1   | scram-sha-256
         132 | host    | {replication} | {all}     | ::1         | scram-sha-256
         133 | hostssl | {appdb}       | {appuser} | 10.0.1.0    | scram-sha-256
(5 rows)

Now the firewall. Allow SSH before you enable UFW, otherwise enabling it on a remote server can cut your own session. The OpenSSH profile only covers port 22, so if your SSH daemon listens elsewhere, run sudo ufw allow 2222/tcp with your port instead. On a cloud VM, also open 5432 from the app subnet in the provider’s firewall or security group, because UFW can’t open what the provider blocks upstream. If UFW is already active, the first command is harmless, and our UFW command reference covers deleting or reordering rules later.

sudo ufw allow OpenSSH
sudo ufw allow from "${APP_SUBNET}" to any port 5432 proto tcp
sudo ufw --force enable

Test from a client in that subnet, with your server’s address in place of 10.0.1.50. We used a separate Ubuntu machine with the stock psql 18 client, which talks to a 17 server without trouble:

psql -h 10.0.1.50 -U appuser -d appdb -c "SELECT current_user, inet_server_addr(), version();" -c "SELECT ssl, version AS tls FROM pg_stat_ssl WHERE pid = pg_backend_pid();"

After the password prompt, the session reports the server and confirms TLS 1.3. This is from the 26.04 server; 24.04 returned the same with its own version string:

 current_user | inet_server_addr |                                                            version
--------------+------------------+--------------------------------------------------------------------------------------------------------------------------------
 appuser      | 10.0.1.50        | PostgreSQL 17.11 (Ubuntu 17.11-1.pgdg26.04+2) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 15.2.0-16ubuntu1) 15.2.0, 64-bit
(1 row)

 ssl |   tls
-----+---------
 t   | TLSv1.3
(1 row)

The snakeoil certificate encrypts the link but is not signed by anyone a client can verify, so treat it as protection against sniffing, not impersonation. Clients that need sslmode=verify-full require a real certificate in ssl_cert_file. The 24.04 server looked like this after the same steps.

PostgreSQL 17 cluster online on Ubuntu 24.04 from the PGDG repository with UFW allowing port 5432

If something fails, the error tells you which of the three changes is missing. These two are the ones we hit.

FATAL: no pg_hba.conf entry for host “10.0.1.1”, user “appuser”, database “appdb”, SSL encryption

The server is listening and the firewall let you in, but no rule matches. We got this with the listener and UFW in place and the hostssl line not yet added. The address in the message is the one the server saw, which may be a NAT gateway rather than your client, so build the rule from that address. You will also get it with a hostssl rule when the client forces sslmode=disable, and the message then ends in no encryption.

FATAL: Peer authentication failed for user “appuser”

This one shows up on the server itself when you run psql -U appuser -d appdb without -h. psql then uses the Unix socket, the local all all peer rule applies, and your Linux user is not called appuser. Add -h 127.0.0.1 as in the role test above rather than loosening the local rules.

Benchmark the server with pgbench

pgbench ships with the server, so you can measure the box before an application does. We initialized a scale 50 dataset, which came to 755 MB, about six times the default shared_buffers of 128 MB:

sudo -u postgres createdb bench
sudo -u postgres pgbench -i -s 50 -q bench

For each setting we restarted the cluster, ran a 30 second warm-up whose numbers we threw away, then measured the default TPC-B style read/write mix for 60 seconds with 4 clients on 2 threads:

sudo systemctl restart postgresql@17-main
sudo -u postgres pgbench -c 4 -j 2 -T 30 bench
sudo -u postgres pgbench -c 4 -j 2 -T 60 bench

The select-only variant adds -S. At 128MB it got its own restart and a 30 second -S warm-up; at 1GB it ran straight after the read/write run, on a cache that was already warm:

sudo -u postgres pgbench -c 4 -j 2 -T 60 -S bench

To test a larger cache we raised shared_buffers to 25% of the VM’s RAM with another drop-in file, then restarted the cluster:

sudo vim /etc/postgresql/17/main/conf.d/tuning.conf

The file holds one setting. It overrides postgresql.conf because the include_dir = 'conf.d' line sits near the end of that file (line 840 on our clusters), and the last value read wins. Settings made with ALTER SYSTEM land in postgresql.auto.conf, which is read after both and still overrides your drop-in.

shared_buffers = 1GB

These are the numbers from the 26.04 VM. Both rows ran on the same disk within the same hour.

shared_buffersRead/write TPSRead/write latencySelect-only TPSSelect-only latency
128MB (default)9024.434 ms25,5820.156 ms
1GB1,1073.614 ms26,4240.151 ms

The read/write mix gained 23% from the bigger cache, while select-only barely moved. That second result is the useful one. With 4 GB of RAM the whole 755 MB dataset already sat in the kernel page cache, so reads never touched disk at either setting. The gain on writes most likely comes from fewer dirty pages being evicted and written back between checkpoints. Raise shared_buffers toward 25% of RAM on a dedicated server, but do not expect read speed to follow if your data already fits in memory. The case for more RAM is a dataset that does not.

Upgrade the cluster to PostgreSQL 18 with pg_upgradecluster

When you are ready for 18, Ubuntu’s pg_upgradecluster moves data, port and pg_hba.conf to a new cluster in one command, but it does not copy the files in conf.d. We hit that on both releases: after a clean upgrade the 18 cluster came back with an empty conf.d and listen_addresses = localhost, and on 26.04 also with shared_buffers = 128MB, so our remote client got Connection refused. Take a backup before you start, then install the new server alongside the old one:

sudo apt install -y postgresql-18
pg_lsclusters

On both of our servers the package installed without creating an empty 18/main, so pg_lsclusters still listed only 17. If yours does show an 18 cluster, remove it with sudo pg_dropcluster 18 main --stop first, because pg_upgradecluster creates the target cluster itself. Then run the upgrade. The default method is a dump and restore; -m upgrade uses pg_upgrade and is much faster on a big database:

sudo pg_upgradecluster -m upgrade 17 main

With the 755 MB benchmark database and the application database on board, it finished in about 11 seconds on 26.04, including the statistics rebuild. The old cluster stays on disk, stopped on port 5433, and the new one takes 5432:

Success. Please check that the upgraded cluster works. If it does,
you can remove the old cluster with
    pg_dropcluster 17 main

Ver Cluster Port Status Owner    Data directory              Log file
17  main    5433 down   postgres /var/lib/postgresql/17/main /var/log/postgresql/postgresql-17-main.log
Ver Cluster Port Status Owner    Data directory              Log file
18  main    5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log

Now copy the drop-in files across and restart the new cluster, which brings back the listener and the memory setting. If you never created a drop-in, skip the copy, because the glob matches nothing and cp fails; tuning.conf only exists if you followed the benchmark section:

sudo cp -p /etc/postgresql/17/main/conf.d/*.conf /etc/postgresql/18/main/conf.d/
sudo systemctl restart postgresql@18-main
sudo -u postgres psql -Atc "SHOW listen_addresses;" -c "SHOW shared_buffers;"

Both values should match what you set for 17. If you still see localhost, the copy went to the wrong directory.

*
1GB

Our remote client reconnected straight away, and the 1,000 test rows we wrote to appdb before the upgrade were all there. The view after the upgrade on 26.04:

pg_upgradecluster moving PostgreSQL 17 to 18 on Ubuntu 26.04 with the old cluster down on port 5433

Keep the old cluster until the application has run on 18 for a while, since it’s your fastest rollback. Once you are confident, remove it with sudo pg_dropcluster 17 main and the postgresql-17 package. Our PostgreSQL guide for Debian 13 and 12 also uses a conf.d drop-in, so the same copy step applies there. For backups, WAL archiving and pgBouncer on the 17 line, the longer PostgreSQL configuration walkthrough picks up where this page stops, and on Rocky Linux the PGDG RPM install follows the same plan with different paths.

Keep reading

Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) Ubuntu Upgrade Ubuntu 24.04 to Ubuntu 26.04 LTS (Step by Step) UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Security UFW Firewall Commands with Examples on Ubuntu 26.04 / 24.04 Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Ubuntu Ubuntu 26.04 vs 24.04 LTS: Should You Upgrade Now? Upgrade a Single Package on Ubuntu / Debian with apt Debian Upgrade a Single Package on Ubuntu / Debian with apt APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian Cheat Sheets APT Cheat Sheet: apt and apt-get Commands for Ubuntu/Debian How To Set Up SSH Key Authentication on Ubuntu 26.04 LTS Security How To Set Up SSH Key Authentication on Ubuntu 26.04 LTS

Leave a Comment

Press ESC to close