0

Migrating the Pyramid Database from PostgreSQL 9.6 to PostgreSQL 18 (Linux)

CRITICAL: Required Before Upgrading to Pyramid 2025.11 or Later

IMPORTANT: This migration from PostgreSQL 9.6 to PostgreSQL 18 MUST be completed BEFORE upgrading Pyramid to version 2025.11 or any later version. If you attempt to upgrade Pyramid without completing this database migration first, it will fail.

This article only applies to you, if you installed Pyramid using the "New Internal Repository" option.https://help.pyramidanalytics.com/Content/Root/Guides/installation/Main/Database%20Repository.htm

Migrate → verify for 24 hours → then upgrade Pyramid.

Supported platforms: Ubuntu, Debian, RHEL, Oracle Linux, Amazon Linux.

Before You Start

Pyramid’s bundled PostgreSQL always runs on port 12130.

Two values are specific to your install — you get both in Step 5:

Placeholder Replace with
<YOUR_DATABASE> Your database name from config.ini
<YOUR_PASSWORD> The password you derive in Step 5

The database service is pyramidPG.service. Stopping it before the backup is the most common way to break this migration.

See what’s on your box:

systemctl list-units --type=service --all | grep -i pyramid

Paths by Platform

Item Ubuntu / Debian RHEL / Oracle / Amazon
pg_hba.conf /etc/postgresql/18/main/pg_hba.conf /var/lib/pgsql/18/data/pg_hba.conf
postgresql.conf /etc/postgresql/18/main/postgresql.conf /var/lib/pgsql/18/data/postgresql.conf

By default Pyramid installs to /opt/Pyramid with config.ini inside it.

Prerequisites

  • sudo access
  • 2x the current database size free on the backup filesystem
  • Pyramid offline during the migration

Step 1: Install PostgreSQL 18

Ubuntu / Debian

sudo apt install -y 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
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] \
https://apt.postgresql.org/pub/repos/apt ${VERSION_CODENAME}-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt update
sudo apt install -y postgresql-18 postgresql-client-18

Initialises and starts automatically — no initdb.

RHEL / Oracle Linux

sudo dnf install -y \
https://download.postgresql.org/pub/repos/yum/reporpms/EL-$(rpm -E %{rhel})-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql
sudo dnf install -y postgresql18-server postgresql18-contrib
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb
sudo systemctl enable --now postgresql-18

If Oracle’s own PostgreSQL packages conflict:

sudo dnf install -y postgresql18-server --disablerepo=ol\* --enablerepo=pgdg18

Amazon Linux 2023

rpm -E %{rhel} doesn’t resolve here, so pin EL9:
sudo dnf install -y \
https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo dnf -qy module disable postgresql 2>/dev/null || true
sudo dnf install -y postgresql18-server postgresql18-contrib
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb
sudo systemctl enable --now postgresql-18

GPG key complaints:

sudo rpm --import https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-RHEL

Verify

sudo -u postgres psql -c "SELECT version();"

Do Step 2 before anything else.

Step 2: Configure PostgreSQL 18

2a. postgresql.conf

Ubuntu / Debian:

sudo nano /etc/postgresql/18/main/postgresql.conf

RHEL / Oracle / Amazon:

sudo nano /var/lib/pgsql/18/data/postgresql.conf

In the CONNECTIONS AND AUTHENTICATION section:

listen_addresses ships commented out, which means it defaults to localhost and refuses remote connections. Remove the # and set:
listen_addresses = '*'

Single-server installs can leave this at the default. Multi-server must change it.

port should already read:

port = 5432
max_connections — 100 is too low for Pyramid’s service pools:
max_connections = 1000

2b. pg_hba.conf

Ubuntu / Debian:

sudo nano /etc/postgresql/18/main/pg_hba.conf

RHEL / Oracle / Amazon:

sudo nano /var/lib/pgsql/18/data/pg_hba.conf

Pick one based on your security requirements.

Allow all (simplest, least secure):

# TYPE  DATABASE  USER  ADDRESS         METHOD
local   all       all                   peer
host    all       all   127.0.0.1/32    scram-sha-256
host    all       all   0.0.0.0/0       scram-sha-256
host    all       all   ::1/128         scram-sha-256
host    all       all   ::/0            scram-sha-256

Allow a subnet — replace with your own:

# TYPE  DATABASE  USER  ADDRESS            METHOD
local   all       all                      peer
host    all       all   127.0.0.1/32       scram-sha-256
host    all       all   192.168.1.0/24     scram-sha-256
host    all       all   ::1/128            scram-sha-256

Keep the local ... peer line — later steps need it.

2c. Restart and verify

Ubuntu / Debian:

sudo systemctl restart postgresql@18-main

RHEL / Oracle / Amazon:

sudo systemctl restart postgresql-18

Then:

sudo -u postgres psql -c "SHOW listen_addresses;"
sudo -u postgres psql -c "SHOW max_connections;"
sudo ss -lntp | grep 5432

2d. Firewall

Ubuntu / Debian — replace with your subnet:

sudo ufw allow from 192.168.1.0/24 to any port 5432 proto tcp

RHEL / Oracle Linux:

sudo firewall-cmd --permanent --add-port=5432/tcp
sudo firewall-cmd --reload

Amazon Linux / EC2 has no local firewall by default — add an inbound TCP 5432 rule to the instance’s Security Group.

RHEL / Oracle with SELinux issues:

sudo ausearch -m avc -ts recent
sudo setsebool -P httpd_can_network_connect_db on

Step 3: Create the Backup Directory

Own it yourself, or later steps hit permission errors:

sudo mkdir -p /var/backups/pyramid
sudo chown -R $(whoami):$(whoami) /var/backups/pyramid
chmod 700 /var/backups/pyramid
ls -ld /var/backups/pyramid

The output should show your username, not root. If you created it root-owned earlier, the chown -R above fixes it.

Step 4: Confirm the Pyramid Path

ls -l /opt/Pyramid/config.ini

If it’s elsewhere:

sudo find / -name config.ini -path '*yramid*' 2>/dev/null

Step 5: Get Your Database Name and Password

sudo sed -n '/\[data\]/,/^\[/p' /opt/Pyramid/config.ini

Output looks like:

[data]
password=encG_0_KsIojX/E13v+ylAocWuRpaTALBicK864V7YWlmGCU/UuCPGd5n/ZpTMI/y4=
type=postgres
port=12130
username=pyramid
database=pyramidb3e0d8e8
dbhost=localhost

The username is always pyramid, the port always 12130. You need the database= value.

Derive the password. The config.ini value is encrypted, but the real password is built from the database name — you are not decrypting anything. The rule is pyramid! plus everything after pyramid in the database name:

database name:   pyramidb3e0d8e8
password:        pyramid!b3e0d8e8

Print your actual values:

DB=$(sudo sed -n '/\[data\]/,/^\[/p' /opt/Pyramid/config.ini | grep '^database=' | cut -d= -f2)
if [[ "$DB" == pyramid* ]]; then
echo "Database name: $DB"
echo "Password:      pyramid!${DB#pyramid}"
else
echo "Database '$DB' does not start with 'pyramid' - the rule does not apply."
echo "Get the password from whoever built this environment."
fi

Verify them:

PGPASSWORD='<YOUR_PASSWORD>' psql -h 127.0.0.1 -p 12130 -U pyramid \
-d <YOUR_DATABASE> -c "\dt" | head

You should see a table list. If not, stop and fix it — the backup will fail the same way.

Use 127.0.0.1, not localhost. On some hosts localhost resolves to IPv6 first and the bundled PostgreSQL isn’t bound to it.

Step 6: Check Disk Space

df -h /var/backups/pyramid
PGPASSWORD='<YOUR_PASSWORD>' psql -h 127.0.0.1 -p 12130 -U pyramid -d postgres \
-c "SELECT pg_size_pretty(pg_database_size('<YOUR_DATABASE>'));"

Step 7: Stop the Pyramid Services — Leave pyramidPG Running

pyramidPG is the source database. The backup fails without it.

This stops every Pyramid service except the database, and skips anything not installed on this server:

for u in $(systemctl list-unit-files --type=service --no-legend \
| awk '{print $1}' | grep -i pyramid | grep -vi pyramidPG); do
echo "Stopping $u"
sudo systemctl stop "$u"
done

Confirm the database is still up — this must print active:

systemctl is-active pyramidPG.service

If it prints inactive:

sudo systemctl start pyramidPG.service
sleep 5
systemctl is-active pyramidPG.service

Step 8: Back Up config.ini

cp -a /opt/Pyramid/config.ini \
/var/backups/pyramid/config.ini.backup_$(date +%Y%m%d_%H%M%S)

Step 9: Back Up the Database. -- replace 'your_password' and 'yourdatabase' with your details.

PostgreSQL 18’s pg_dump can dump from a 9.6 server, so no 9.6 binaries are needed.

Do not use sudo — it creates a root-owned file that breaks Step 12.

Ubuntu / Debian

BACKUP=/var/backups/pyramid/pyramid_backup_$(date +%Y%m%d_%H%M%S).backup
PGPASSWORD='<YOUR_PASSWORD>' /usr/lib/postgresql/18/bin/pg_dump \
-h 127.0.0.1 -p 12130 -U pyramid \
-Fc -b -v -f "$BACKUP" <YOUR_DATABASE>
ls -lh "$BACKUP"

RHEL / Oracle Linux / Amazon Linux

BACKUP=/var/backups/pyramid/pyramid_backup_$(date +%Y%m%d_%H%M%S).backup
PGPASSWORD='<YOUR_PASSWORD>' /usr/pgsql-18/bin/pg_dump \
-h 127.0.0.1 -p 12130 -U pyramid \
-Fc -b -v -f "$BACKUP" <YOUR_DATABASE>
ls -lh "$BACKUP"

-Fc custom format (required for pg_restore), -b include blobs, -v verbose, -f output file. The database name goes at the end with no flag — that’s normal for pg_dump.

Verify before continuing. ls -lh must show a real size owned by your user. If it’s 0 bytes or missing, the dump failed — do not go to Step 10.

In a new shell, recover the variable with:

BACKUP=$(ls -t /var/backups/pyramid/*.backup | head -1)

Step 10: Disable pyramidPG

sudo systemctl disable --now pyramidPG.service
systemctl is-enabled pyramidPG.service

Should print disabled.

Step 11: Create the pyramid Role in PostgreSQL 18

sudo -u postgres psql -c "CREATE USER pyramid WITH PASSWORD '<YOUR_PASSWORD>';"
sudo -u postgres psql -c "ALTER USER pyramid WITH SUPERUSER CREATEDB LOGIN;"
If the role already exists, use ALTER USER pyramid WITH PASSWORD '<YOUR_PASSWORD>'; instead of CREATE.

Step 12: Restore into PostgreSQL 18

Create the database — the name must match the original exactly:

sudo -u postgres psql -c 'CREATE DATABASE "<YOUR_DATABASE>" OWNER pyramid;'

Restore as pyramid over TCP, not as the postgres OS user. The postgres user can’t read into your 700 backup directory; connecting over TCP means the file is read with your permissions.

Ubuntu / Debian

PGPASSWORD='<YOUR_PASSWORD>' /usr/lib/postgresql/18/bin/pg_restore \
-h 127.0.0.1 -p 5432 -U pyramid \
-d <YOUR_DATABASE> --exit-on-error -v "$BACKUP"

RHEL / Oracle Linux / Amazon Linux

PGPASSWORD='<YOUR_PASSWORD>' /usr/pgsql-18/bin/pg_restore \
-h 127.0.0.1 -p 5432 -U pyramid \
-d <YOUR_DATABASE> --exit-on-error -v "$BACKUP"

--exit-on-error matters — without it pg_restore exits successfully even when every object failed.

To start over:

sudo -u postgres psql -c 'DROP DATABASE "<YOUR_DATABASE>";'
sudo -u postgres psql -c 'CREATE DATABASE "<YOUR_DATABASE>" OWNER pyramid;'

Step 13: Verify the Restore

All three must pass before you touch config.ini.

Table count must be greater than zero:

sudo -u postgres psql -d <YOUR_DATABASE> -t -A \
-c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';"

Size should be comparable to the original:

sudo -u postgres psql -t -A \
-c "SELECT pg_size_pretty(pg_database_size('<YOUR_DATABASE>'));"

This is how Pyramid itself will connect:

PGPASSWORD='<YOUR_PASSWORD>' psql -h 127.0.0.1 -p 5432 \
-U pyramid -d <YOUR_DATABASE> -c "\dt" | head

config.ini is still untouched at this point, so Pyramid can be brought back on the old database if anything’s wrong.

Step 14: Update config.ini

Change only the port:

sudo sed -i 's/^port=12130$/port=5432/' /opt/Pyramid/config.ini
sudo grep '^port=' /opt/Pyramid/config.ini

Leave everything else alone, including the encrypted password= field.

Step 15: Start the Pyramid Services

pyramidPG stays disabled.

for u in $(systemctl list-unit-files --type=service --no-legend \
| awk '{print $1}' | grep -i pyramid | grep -vi pyramidPG); do
echo "Starting $u"
sudo systemctl start "$u"
sleep 3
done
systemctl list-units --type=service --all | grep -i pyramid

Wait 60–90 seconds. Investigate anything that failed:

journalctl -u <unit-name> -n 50 --no-pager

Step 16: Update the Data Source in Admin Hub

  1. Admin → Data → Data Source
  2. Select Local PG
  3. Change Port from 12130 to 5432
  4. Test — wait for the green check
  5. Apply

Step 17: Other Servers in the Cluster

On each remaining Pyramid server, stop the services, change the port, start them again:

for u in $(systemctl list-unit-files --type=service --no-legend \
| awk '{print $1}' | grep -i pyramid | grep -vi pyramidPG); do
sudo systemctl stop "$u"
done
sudo sed -i 's/^port=12130$/port=5432/' /opt/Pyramid/config.ini
sudo grep -E '^(port|dbhost)=' /opt/Pyramid/config.ini
for u in $(systemctl list-unit-files --type=service --no-legend \
| awk '{print $1}' | grep -i pyramid | grep -vi pyramidPG); do
sudo systemctl start "$u"
sleep 3
done

Check that dbhost= points at the server running PostgreSQL 18 — not localhost.

Step 18: Verify

Log into Pyramid and confirm that it is working as it should.

Step 19: Upgrade Pyramid

After 24 hours of clean operation, proceed with the 2025.11+ upgrade.
Rollback

Checklist

  • PostgreSQL 18 installed (RHEL / Oracle / Amazon: initdb run)
  • listen_addresses uncommented and set (multi-server)
  • max_connections = 300
  • pg_hba.conf configured, local all all peer kept
  • PG18 restarted, settings confirmed with SHOW
  • Firewall / Security Group allows 5432
  • Backup directory owned by your user, not root
  • Database name and password verified against port 12130
  • config.ini backed up
  • All Pyramid services stopped except pyramidPG
  • systemctl is-active pyramidPG.service printed active before the backup
  • pg_dump run without sudo, file has a real size
  • pyramidPG disabled after the backup
  • Restore run over TCP with --exit-on-error
  • Table count greater than zero
  • pyramid connects over TCP on 5432
  • config.ini port updated on every server
  • Admin Hub → Local PG on 5432, Test passes
  • 24 hours clean before upgrading Pyramid

Leave pyramidPG disabled — it costs nothing and keeps rollback available.

Reply

null