PostgreSQL 18 Installation on Oracle Linux 9
This instruction installs PostgreSQL 18.3 on Oracle Linux 9 from the PGDG repository, configures SSL/TLS for encrypted client connections, enables the pgcrypto extension for column-level encryption, and applies security hardening with SCRAM-SHA-256 authentication.
This is the first document in a four-part series. Follow-up documents cover pgBackRest backup configuration, streaming replication with two standbys, and pgBackRest with replication.
Assumptions
This instruction assumes:
- Oracle Linux 9.x (x86_64) is installed with a minimal or server profile
- Root access or sudo privileges are available
- The server has internet access to reach
download.postgresql.organdyum.postgresql.org - Firewalld is the active firewall manager (default on OL9)
- SELinux is in enforcing mode (default on OL9)
- No prior PostgreSQL installation exists on the server
- The default PostgreSQL port (5432) is used
Prerequisites
Automatic setup
The PGDG repository RPM configures the YUM/DNF repository for PostgreSQL packages:
sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-9-x86_64/pgdg-redhat-repo-latest.noarch.rpm
This package installs the PGDG repository configuration files under /etc/yum.repos.d/, enabling access to PostgreSQL 18 and all supported versions.
Manual setup
No manual setup is required. The PGDG RPM is the only supported method for configuring the repository.
Additional setup
- Disable the built-in PostgreSQL module in Oracle Linux 9
The OL9 AppStream ships older PostgreSQL versions (13, 15, 16) as DNF modules. These conflict with PGDG packages and must be disabled.
sudo dnf -qy module disable postgresql
Expected output:
(no output on success)
- Verify the PGDG repository is active
sudo dnf repolist | grep pgdg
Expected output:
pgdg-common PostgreSQL common RPMs for RHEL / Rocky / AlmaLi
pgdg18 PostgreSQL 18 for RHEL / Rocky / AlmaLinux 9 - x
Installation
Step 1: Install PostgreSQL 18 packages
Install the server, client, and contrib packages. The contrib package includes pgcrypto and other useful extensions.
sudo dnf install -y postgresql18-server postgresql18 postgresql18-contrib
Expected output:
...
Installed:
postgresql18-18.3-1PGDG.rhel9.x86_64
postgresql18-contrib-18.3-1PGDG.rhel9.x86_64
postgresql18-libs-18.3-1PGDG.rhel9.x86_64
postgresql18-server-18.3-1PGDG.rhel9.x86_64
Complete!
Step 2: Initialize the database cluster
The setup script creates the initial database cluster in /var/lib/pgsql/18/data.
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb
Expected output:
Initializing database ... OK
The setup script calls initdb internally. It does not accept authentication method parameters. Authentication is configured in pg_hba.conf in the next steps.
Step 3: Verify SELinux configuration
The PGDG packages install correct SELinux file contexts for the default data directory. Verify that SELinux is enforcing and the PostgreSQL port label is set.
getenforce
Expected output:
Enforcing
Verify the port label for 5432:
sudo semanage port -l | grep postgresql
Expected output:
postgresql_port_t tcp 5432, 9898
Verify the data directory context:
ls -dZ /var/lib/pgsql/18/data
Expected output:
system_u:object_r:postgresql_db_t:s0 /var/lib/pgsql/18/data
If the data directory context is wrong (e.g., after restoring from backup to a different path), fix it:
sudo semanage fcontext -a -t postgresql_db_t "/var/lib/pgsql/18/data(/.*)?"
sudo restorecon -Rv /var/lib/pgsql/18/data
Step 4: Generate SSL certificates
Generate a self-signed certificate and private key for SSL/TLS connections. Do this before configuring ssl = on in postgresql.conf.
sudo -u postgres openssl req -new -x509 -days 365 -nodes -text \
-out /var/lib/pgsql/18/data/server.crt \
-keyout /var/lib/pgsql/18/data/server.key \
-subj "/CN={HOSTNAME}"
{HOSTNAME} -- the server's fully qualified domain name (e.g., pgdb01.example.com). Use the output of hostname -f if unsure.
Set restrictive permissions on the private key. PostgreSQL refuses to start if the key file is world-readable.
sudo -u postgres chmod 0600 /var/lib/pgsql/18/data/server.key
Verify the certificate:
sudo -u postgres openssl x509 -in /var/lib/pgsql/18/data/server.crt -noout -subject -dates
Expected output:
subject=CN = pgdb01.example.com
notBefore=Feb 28 ... 2026 GMT
notAfter=Feb 28 ... 2027 GMT
Step 5: Configure postgresql.conf
Edit the PostgreSQL configuration to enable SSL, network listening, and SCRAM-SHA-256 authentication.
sudo -u postgres vi /var/lib/pgsql/18/data/postgresql.conf
Set the following parameters:
listen_addresses = '*'
port = 5432
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
password_encryption = scram-sha-256
logging_collector = on
log_directory = 'log'
log_filename = 'postgresql-%a.log'
log_truncate_on_rotation = on
log_rotation_age = 1d
log_line_prefix = '%m [%p] %q%u@%d '
log_connections = on
log_disconnections = on
listen_addresses = '*' -- binds to all network interfaces. Restrict to specific IPs if the server has multiple interfaces and only some should accept PostgreSQL connections.
Step 6: Configure client authentication (pg_hba.conf)
Replace the default pg_hba.conf with a hardened configuration that uses SCRAM-SHA-256 for all password-based connections and requires SSL for remote connections.
sudo -u postgres vi /var/lib/pgsql/18/data/pg_hba.conf
Replace the contents with:
# TYPE DATABASE USER ADDRESS METHOD
# Local connections: peer authentication for OS user mapping
local all all peer
# Localhost connections: SCRAM-SHA-256 (IPv4 and IPv6)
host all all 127.0.0.1/32 scram-sha-256
host all all ::1/128 scram-sha-256
# Remote connections: SSL required with SCRAM-SHA-256
hostssl all all 0.0.0.0/0 scram-sha-256
hostssl all all ::/0 scram-sha-256
This configuration:
- Allows local Unix socket connections via
peerauthentication (OS user must match database user) - Allows localhost TCP connections with SCRAM-SHA-256 passwords
- Requires SSL for all remote TCP connections with SCRAM-SHA-256 passwords
- Blocks all unencrypted remote connections (no
hostentries for remote addresses)
Step 7: Start and enable the PostgreSQL service
sudo systemctl enable --now postgresql-18
Expected output:
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql-18.service → /usr/lib/systemd/system/postgresql-18.service.
Verify the service is running:
sudo systemctl status postgresql-18 --no-pager
Expected output:
● postgresql-18.service - PostgreSQL 18 database server
Loaded: loaded (/usr/lib/systemd/system/postgresql-18.service; enabled; preset: disabled)
Active: active (running) since ...
Step 8: Configure firewall
Open port 5432 for PostgreSQL connections through firewalld.
sudo firewall-cmd --permanent --add-service=postgresql
sudo firewall-cmd --reload
Expected output:
success
success
Verify the rule:
sudo firewall-cmd --list-services | grep postgresql
Expected output:
... postgresql ...
To restrict access to specific networks instead of allowing all sources, use a rich rule. Replace {CLIENT_NETWORK} with the allowed CIDR block (e.g., 10.0.1.0/24):
sudo firewall-cmd --permanent --remove-service=postgresql
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="{CLIENT_NETWORK}" service name="postgresql" accept'
sudo firewall-cmd --reload
Step 9: Create application database and user
Connect as the postgres superuser and create an application database and user.
sudo -u postgres psql
CREATE USER appuser WITH PASSWORD '{APP_PASSWORD}';
CREATE DATABASE appdb OWNER appuser;
GRANT ALL PRIVILEGES ON DATABASE appdb TO appuser;
\q
{APP_PASSWORD} -- a strong password for the application user. Use at least 16 characters with mixed case, numbers, and symbols.
Verify the password is stored with SCRAM-SHA-256:
sudo -u postgres psql -c "SELECT rolname, rolpassword ~ '^SCRAM-SHA-256' AS is_scram FROM pg_authid WHERE rolname = 'appuser';"
Expected output:
rolname | is_scram
---------+----------
appuser | t
(1 row)
Step 10: Enable pgcrypto extension
Enable the pgcrypto extension in the application database for column-level encryption.
sudo -u postgres psql -d appdb -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;"
Expected output:
CREATE EXTENSION
Verify the extension is installed:
sudo -u postgres psql -d appdb -c "SELECT extname, extversion FROM pg_extension WHERE extname = 'pgcrypto';"
Expected output:
extname | extversion
----------+------------
pgcrypto | 1.3
(1 row)
Test symmetric encryption and decryption:
sudo -u postgres psql -d appdb -c "SELECT pgp_sym_decrypt(pgp_sym_encrypt('sensitive data', 'encryption-key'), 'encryption-key') AS decrypted;"
Expected output:
decrypted
----------------
sensitive data
(1 row)
Validation
Quick check
sudo -u postgres psql -c "SELECT version();" -c "SHOW ssl;" -c "SHOW password_encryption;"
Expected output:
version
-----------------------------------------------------------------------------------------------------------
PostgreSQL 18.3 on x86_64-pc-linux-gnu, compiled by gcc ...
(1 row)
ssl
-----
on
(1 row)
password_encryption
---------------------
scram-sha-256
(1 row)
Full validation
- Verify service status
sudo systemctl is-active postgresql-18
Expected output:
active
- Verify listening address and port
sudo ss -tlnp | grep 5432
Expected output:
LISTEN 0 244 0.0.0.0:5432 0.0.0.0:* users:(("postmaster",pid=...,fd=...))
LISTEN 0 244 [::]:5432 [::]:* users:(("postmaster",pid=...,fd=...))
- Verify SSL is used on a connection
sudo -u postgres psql "host=localhost dbname=appdb sslmode=require" -c "SELECT ssl FROM pg_stat_ssl WHERE pid = pg_backend_pid();"
Expected output:
ssl
-----
t
(1 row)
- Verify pg_hba.conf has no weak authentication methods
sudo -u postgres psql -c "SELECT type, database, user_name, address, auth_method FROM pg_hba_file_rules WHERE auth_method NOT IN ('peer', 'scram-sha-256');"
Expected output:
type | database | user_name | address | auth_method
------+----------+-----------+---------+-------------
(0 rows)
- Verify data directory location
sudo -u postgres psql -c "SHOW data_directory;"
Expected output:
data_directory
--------------------------
/var/lib/pgsql/18/data
(1 row)
- Verify pgcrypto extension in application database
sudo -u postgres psql -d appdb -c "SELECT extname FROM pg_extension WHERE extname = 'pgcrypto';"
Expected output:
extname
----------
pgcrypto
(1 row)
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
could not access file "server.key": Permission denied |
Private key file permissions are too open | sudo -u postgres chmod 0600 /var/lib/pgsql/18/data/server.key |
FATAL: no pg_hba.conf entry for host |
Client IP or auth method not in pg_hba.conf | Add a hostssl entry for the client network in /var/lib/pgsql/18/data/pg_hba.conf, then sudo systemctl reload postgresql-18 |
Service fails to start with Address already in use |
Another process is using port 5432 | Run ss -tlnp | grep 5432 to identify the process, stop it, then start PostgreSQL |
password authentication failed after setting SCRAM |
Client library too old for SCRAM-SHA-256 | Upgrade the client to PostgreSQL 10+ or change the pg_hba.conf entry to md5 for that client (less secure) |
dnf module conflicts when installing PGDG packages |
OL9 PostgreSQL module not disabled | sudo dnf -qy module disable postgresql then retry the install |
| SELinux denying PostgreSQL access to data directory | Wrong SELinux context on data files | sudo semanage fcontext -a -t postgresql_db_t "/var/lib/pgsql/18/data(/.*)?" then sudo restorecon -Rv /var/lib/pgsql/18/data |
| SELinux denying PostgreSQL on a non-default port | Port not labeled for PostgreSQL | sudo semanage port -a -t postgresql_port_t -p tcp {PORT} |
pg_stat_ssl shows ssl = f on localhost |
Connecting via Unix socket or sslmode=prefer |
Use psql "host=localhost sslmode=require" to force a TCP+SSL connection |
| Firewall blocks remote connections | Port 5432 not opened in firewalld | sudo firewall-cmd --permanent --add-service=postgresql && sudo firewall-cmd --reload |
FATAL: password authentication failed for user "postgres" |
Trying password auth for postgres superuser on local connection | Use sudo -u postgres psql (peer auth) for local superuser access. Do not set a password for the postgres OS-level superuser connection. |