In this tutorial, you’ll install PostgreSQL 18 on a Raff Ubuntu 24.04 VM, create an application database and role, configure local password authentication, and verify the setup with authenticated CRUD operations.
PostgreSQL is an open-source relational database system used for applications, analytics, and transactional workloads. This tutorial installs PostgreSQL 18 from the official PostgreSQL APT repository instead of the default Ubuntu package snapshot, then keeps the database local to the VM with firewall protection. Raff Technologies has supported 10,000+ VM deployments across its compute platform, and Raff Linux VMs can be provisioned in 60 seconds with NVMe storage and unmetered bandwidth.
This tutorial is designed for a Raff Ubuntu 24.04 VM with at least 2 vCPU, 4 GB RAM, and 80 GB NVMe storage.
Tested on Raff infrastructure by Aybars Altınyay, platform engineer at Raff Technologies.
Prerequisites:
- A Raff Ubuntu 24.04 VM
- SSH access with sudo privileges
- A terminal connected to the VM
- Basic familiarity with Linux package management and SQL commands
📌 Note: This tutorial keeps PostgreSQL bound to localhost. Applications running on the same VM can connect to PostgreSQL, while direct public access to port
5432remains blocked.
Step 1 — Update system packages
Update the Ubuntu package index and install the tools required for the PostgreSQL APT repository:
sudo apt update sudo apt upgrade -y sudo apt install -y curl ca-certificates gnupg lsb-release ufw
Verify that package upgrades are complete:
sudo apt list --upgradable
Expected output:
Listing... Done
Step 2 — Add the official PostgreSQL APT repository
Create the directory for the PostgreSQL repository signing key:
sudo install -d /usr/share/postgresql-common/pgdg
Download the PostgreSQL repository signing key:
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc \ --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
Create the PostgreSQL APT source file for your Ubuntu release:
. /etc/os-release cat <<EOF | sudo tee /etc/apt/sources.list.d/pgdg.sources Types: deb 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
Reload the package index:
sudo apt update
Verify that PostgreSQL 18 is available from the PGDG repository:
apt-cache policy postgresql-18
Expected output includes:
postgresql-18: Candidate: 18. Version table: 18. https://apt.postgresql.org/pub/repos/apt noble-pgdg/main
Step 3 — Install PostgreSQL 18
Install PostgreSQL 18 and the matching client package:
sudo apt install -y postgresql-18 postgresql-client-18
Verify the installed client version:
psql --version
Expected output includes:
psql (PostgreSQL) 18.
Verify the PostgreSQL cluster:
pg_lsclusters
Expected output includes:
Ver Cluster Port Status Owner Data directory 18 main 5432 online postgres /var/lib/postgresql/18/main
Step 4 — Start and verify PostgreSQL
PostgreSQL usually starts automatically after installation. Start and enable it explicitly:
sudo systemctl enable --now postgresql
Verify that PostgreSQL is active:
systemctl status postgresql --no-pager
Expected output includes:
Active: active
Verify that the server responds locally:
sudo -u postgres psql -c "SELECT version();"
Expected output includes:
PostgreSQL 18
Verify the default PostgreSQL port:
sudo ss -lntp | grep 5432
Expected output includes:
127.0.0.1:5432
Step 5 — Create an application database and role
Create a dedicated application role and database. Replace the password before using this setup for real application data.
sudo -u postgres psql
Inside the PostgreSQL shell, run:
CREATE ROLE raffappuser WITH LOGIN PASSWORD 'ChangeThisStrongAppPassword!'; CREATE DATABASE raffapp OWNER raffappuser; \q
Verify that the database exists:
sudo -u postgres psql -lqt | cut -d '|' -f 1 | grep -w raffapp
Expected output:
raffapp
Verify that the role exists:
sudo -u postgres psql -tAc "SELECT rolname FROM pg_roles WHERE rolname = 'raffappuser';"
Expected output:
raffappuser
⚠️ Warning: Do not publish or reuse the tutorial password. Use a unique password for each real application.
Step 6 — Configure password authentication
PostgreSQL uses the pg_hba.conf file to control local and network authentication rules. Confirm that localhost TCP connections use password authentication:
sudo grep -nE '^host\s+all\s+all\s+127\.0\.0\.1/32|^host\s+all\s+all\s+::1/128' /etc/postgresql/18/main/pg_hba.conf
Expected output includes:
127.0.0.1/32 scram-sha-256 ::1/128 scram-sha-256
If the lines use another method, set localhost host authentication to scram-sha-256:
sudo sed -i 's/^host\s\+all\s\+all\s\+127\.0\.0\.1\/32\s\+.*/host all all 127.0.0.1\/32 scram-sha-256/' /etc/postgresql/18/main/pg_hba.conf sudo sed -i 's/^host\s\+all\s\+all\s\+::1\/128\s\+.*/host all all ::1\/128 scram-sha-256/' /etc/postgresql/18/main/pg_hba.conf
Reload PostgreSQL so authentication changes take effect:
sudo systemctl reload postgresql
Verify authenticated access as the application user:
PGPASSWORD='ChangeThisStrongAppPassword!' psql \ -h 127.0.0.1 \ -U raffappuser \ -d raffapp \ -c "SELECT current_user, current_database();"
Expected output includes:
raffappuser | raffapp
Step 7 — Configure local-only access and firewall rules
PostgreSQL listens on localhost by default. Confirm the current bind address:
sudo -u postgres psql -tAc "SHOW listen_addresses;"
Expected output:
localhost
Verify that PostgreSQL is listening locally on port 5432:
sudo ss -lntp | grep 5432
Expected output includes:
127.0.0.1:5432
Allow SSH and block public PostgreSQL access:
sudo ufw allow 22/tcp sudo ufw deny 5432/tcp sudo ufw --force enable
Verify the firewall status:
sudo ufw status numbered
Expected output includes:
22/tcp ALLOW IN 5432/tcp DENY IN
Step 8 — Verify PostgreSQL end to end
Run an authenticated CRUD test as the application user:
PGPASSWORD='ChangeThisStrongAppPassword!' psql \ -h 127.0.0.1 \ -U raffappuser \ -d raffapp <<'SQL' CREATE TABLE tutorial_check ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, status TEXT NOT NULL, created_at TIMESTAMPTZ DEFAULT now() ); INSERT INTO tutorial_check (name, status) VALUES ('raff-postgresql-test', 'created'); UPDATE tutorial_check SET status = 'verified' WHERE name = 'raff-postgresql-test'; SELECT name, status FROM tutorial_check WHERE name = 'raff-postgresql-test'; DELETE FROM tutorial_check WHERE name = 'raff-postgresql-test'; DROP TABLE tutorial_check; SQL
Expected output includes:
CREATE TABLE INSERT 0 1 UPDATE 1 name | status ----------------------+---------- raff-postgresql-test | verified DELETE 1 DROP TABLE
Verify the service one final time:
systemctl is-active postgresql pg_lsclusters sudo ss -lntp | grep 5432 sudo ufw status numbered
End-to-end verification is complete when:
- PostgreSQL 18 is installed
- The PostgreSQL cluster is online
- The
raffappdatabase exists - The
raffappuserrole can authenticate with a password - The application user can create, read, update, and delete data
- PostgreSQL listens on localhost
- Public access to port
5432is blocked
Cleanup (Optional)
Use this section only when you want to remove PostgreSQL, its application database, users, service files, repository files, logs, and data directory from the VM.
⚠️ Warning: The following commands permanently delete PostgreSQL databases and local PostgreSQL data. Back up any data you need before proceeding.
Drop the tutorial database and role:
sudo -u postgres psql <<'SQL' DROP DATABASE IF EXISTS raffapp; DROP ROLE IF EXISTS raffappuser; SQL
Stop and disable PostgreSQL:
sudo systemctl stop postgresql sudo systemctl disable postgresql
Remove PostgreSQL packages:
sudo apt purge "postgresql-18*" "postgresql-client-18*" -y sudo apt autoremove -y
Remove PostgreSQL data, configuration, logs, repository files, and signing key:
sudo rm -rf /var/lib/postgresql sudo rm -rf /etc/postgresql sudo rm -rf /var/log/postgresql sudo rm -f /etc/apt/sources.list.d/pgdg.sources sudo rm -rf /usr/share/postgresql-common/pgdg
Remove the PostgreSQL firewall rule:
sudo ufw delete deny 5432/tcp sudo ufw status numbered
Verify that PostgreSQL is removed:
systemctl status postgresql --no-pager
Expected output includes:
Unit postgresql.service could not be found.
Troubleshooting
PostgreSQL 18 does not appear in APT
Cause: The PGDG repository file is missing, malformed, or using the wrong Ubuntu codename.
Fix:
cat /etc/apt/sources.list.d/pgdg.sources . /etc/os-release echo "$VERSION_CODENAME" apt-cache policy postgresql-18
Expected checks:
Suites: noble-pgdg Candidate: 18.
If the repository file is wrong, recreate it:
. /etc/os-release cat <<EOF | sudo tee /etc/apt/sources.list.d/pgdg.sources Types: deb 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
PostgreSQL service is not active
Cause: The service failed to start, or the cluster is not online.
Fix:
systemctl status postgresql --no-pager pg_lsclusters sudo journalctl -u postgresql --no-pager -n 80
Restart PostgreSQL:
sudo systemctl restart postgresql
Verify again:
systemctl is-active postgresql pg_lsclusters
Expected output includes:
active online
Password authentication fails for the application user
Cause: The password is wrong, the role was not created with LOGIN, or pg_hba.conf does not use password authentication for localhost TCP connections.
Fix:
Reset the role password:
sudo -u postgres psql -c "ALTER ROLE raffappuser WITH LOGIN PASSWORD 'ChangeThisStrongAppPassword!';"
Confirm localhost authentication lines:
sudo grep -nE '^host\s+all\s+all\s+127\.0\.0\.1/32|^host\s+all\s+all\s+::1/128' /etc/postgresql/18/main/pg_hba.conf
Reload PostgreSQL:
sudo systemctl reload postgresql
Test again:
PGPASSWORD='ChangeThisStrongAppPassword!' psql \ -h 127.0.0.1 \ -U raffappuser \ -d raffapp \ -c "SELECT current_user;"
Expected output includes:
raffappuser
The application user cannot create tables
Cause: The database is not owned by the application role, or schema privileges are missing.
Fix:
Set the database owner and grant schema privileges:
sudo -u postgres psql <<'SQL' ALTER DATABASE raffapp OWNER TO raffappuser; \c raffapp GRANT CREATE ON SCHEMA public TO raffappuser; SQL
Verify table creation:
PGPASSWORD='ChangeThisStrongAppPassword!' psql \ -h 127.0.0.1 \ -U raffappuser \ -d raffapp \ -c "CREATE TABLE permission_check (id int); DROP TABLE permission_check;"
Expected output:
CREATE TABLE DROP TABLE
PostgreSQL listens on a public interface
Cause: listen_addresses was changed from localhost to * or a public IP address.
Fix:
Open the PostgreSQL configuration file:
sudo nano /etc/postgresql/18/main/postgresql.conf
Set:
listen_addresses = 'localhost'
Restart PostgreSQL:
sudo systemctl restart postgresql
Verify the listening address:
sudo ss -lntp | grep 5432
Expected output includes:
127.0.0.1:5432
Conclusion and next steps
You now have PostgreSQL 18 installed on a Raff Ubuntu 24.04 VM from the official PostgreSQL APT repository. You also created a dedicated application database and role, verified authenticated CRUD operations, confirmed local-only binding, and blocked public database access with UFW. If you have not deployed your Raff VM yet, you can spin one up in 60 seconds at rafftechnologies.com.
Next: How to Secure Your Ubuntu 24.04 Server
Related: How to Install MongoDB Community Edition on Ubuntu 24.04
