Install PostgreSQL on AlmaLinux 10: PGDG vs AppStream, pg_hba.conf and Remote Access

PostgreSQL is the default choice for a lot of new applications, and AlmaLinux 10 gives you two good ways to install it: the packages that ship in the AlmaLinux AppStream repository, or the upstream PostgreSQL Global Development Group (PGDG) repository. This guide shows how to install PostgreSQL on AlmaLinux 10 both ways, initialize the cluster, lock down pg_hba.conf, open remote access through firewalld, and keep SELinux enforcing the whole time.

This is part 1 of a PostgreSQL series on AlmaLinux. Later parts cover roles and privileges, backups, streaming replication, tuning, major-version upgrades, and TLS with pgBouncer. The commands work the same on RHEL 10, Rocky Linux 10 and Oracle Linux 10. If you are still on an older EL release, the earlier post Install PostgreSQL on RHEL/CentOS/Oracle/Rocky/Alma covers those.

Lab layout and prerequisites

  • db1.example.com 192.168.1.40, AlmaLinux 10 (10.2 at the time of writing), the PostgreSQL server
  • app1.example.com 192.168.1.30, an application server that needs to connect remotely
  • Clients live in 192.168.1.0/24
  • A user with sudo, SELinux in enforcing mode and firewalld running (the AlmaLinux defaults)
  • At least 2 GB of RAM and a separate disk or LV for the data directory if this is going to production
cat /etc/almalinux-release
getenforce
sudo systemctl is-active firewalld
sudo dnf -y update

AppStream or PGDG: which one should you use?

  • AppStream packages come from AlmaLinux (rebuilt from RHEL). The default postgresql-server package is PostgreSQL 16, and AlmaLinux 10.1 and later also ship PostgreSQL 18 as the postgresql18-* packages. You get the distribution life cycle, the standard postgresql.service, the data directory in /var/lib/pgsql/data, and only one PostgreSQL version per host.
  • PGDG packages come straight from the PostgreSQL project. You get every supported major version (14 through 18 today), minor releases as soon as they are published, extensions such as PostGIS and pgvector, and side-by-side installs: each version lives in /usr/pgsql-<version> with its own postgresql-<version>.service and /var/lib/pgsql/<version>/data.

Rule of thumb: if you want the distribution-supported build and only need one version, use AppStream. If you need a specific major version, the newest minor release on day one, PGDG extensions, or several versions on one host (which makes pg_upgrade much easier), use PGDG. Do not mix the two on the same server. Pick one option below and skip the other.

Option A: install PostgreSQL from AppStream

See which server versions the enabled repositories offer:

dnf list --available 'postgresql*-server' 

Install PostgreSQL 18 and the contrib extensions (pg_stat_statements, pgcrypto and friends). If you would rather stay on the default PostgreSQL 16 stream, install postgresql-server postgresql-contrib instead. The two sets conflict, so pick one.

sudo dnf install -y postgresql18-server postgresql18-contrib

Initialize the cluster with the postgresql-setup helper. It runs initdb as the postgres user and writes to /var/lib/pgsql/data:

sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo systemctl status postgresql --no-pager

With AppStream, the data directory is /var/lib/pgsql/data, the service is postgresql, and the binaries are in /usr/bin.

Option B: install PostgreSQL from the PGDG repository

Install the repository RPM for EL 10 from the official PostgreSQL download page for Red Hat family:

sudo dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-10-x86_64/pgdg-redhat-repo-latest.noarch.rpm

AlmaLinux 10 has no DNF modules, so there is no dnf -qy module disable postgresql step like on EL8 and EL9. There is a different catch, though: AppStream now ships packages named postgresql18-server, the same names PGDG uses. Exclude PostgreSQL packages from AppStream so DNF always takes them from PGDG:

sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --save --setopt=appstream.excludepkgs='postgresql*'
dnf info postgresql18-server | grep -E '^(Version|Repository|From repo)' 

The repository should show pgdg18. Now install the server and contrib packages, initialize the cluster, and start the versioned service:

sudo dnf install -y postgresql18-server postgresql18-contrib
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb
sudo systemctl enable --now postgresql-18
sudo systemctl status postgresql-18 --no-pager

The first time DNF uses the PGDG repository it asks you to accept the PGDG signing key. Check the fingerprint against the download page before you say yes. With PGDG, the data directory is /var/lib/pgsql/18/data, the service is postgresql-18, and the binaries are in /usr/pgsql-18/bin (psql and the other client tools are also linked into /usr/bin through alternatives).

Verify the installation

In the rest of this guide, $PGDATA means /var/lib/pgsql/data for AppStream or /var/lib/pgsql/18/data for PGDG, and SERVICE means postgresql or postgresql-18. Set them once so you can paste the commands as they are:

# AppStream
PGDATA=/var/lib/pgsql/data; SERVICE=postgresql
# PGDG
PGDATA=/var/lib/pgsql/18/data; SERVICE=postgresql-18

Connect as the postgres operating system user. Local socket connections use peer authentication, so no password is needed yet:

sudo -u postgres psql -c 'SELECT version();'
sudo -u postgres psql -c 'SHOW data_directory;'
sudo -u postgres psql -c 'SHOW data_checksums;'
sudo ss -ltnp | grep 5432

PostgreSQL 18 turns on data checksums by default when initdb creates a cluster, so data_checksums should report on. Right after installation, ss shows the server listening only on 127.0.0.1:5432 and [::1]:5432. That is the safe default, and the next sections open it up on purpose.

Set a password and create an application database

Give the postgres superuser a password (you need it once remote password logins are on), then create a login role and a database owned by it. Part 2 of this series goes much deeper into roles and privileges.

sudo -u postgres psql
\password postgres
CREATE ROLE appuser WITH LOGIN;
\password appuser
CREATE DATABASE appdb OWNER appuser;
SHOW password_encryption;
\q

Using \password keeps the clear-text password out of your shell history and the server log. password_encryption should be scram-sha-256, the default since PostgreSQL 14. Don’t use md5 password hashes on new servers.

Allow remote connections in postgresql.conf

Tell PostgreSQL which addresses to listen on. Use the server IP rather than * when you can:

sudo -u postgres psql -c "ALTER SYSTEM SET listen_addresses = 'localhost,192.168.1.40';"
sudo systemctl restart $SERVICE
sudo ss -ltnp | grep 5432

ALTER SYSTEM writes to $PGDATA/postgresql.auto.conf, which overrides postgresql.conf. If you prefer to manage the setting by hand (or with Ansible), edit listen_addresses in $PGDATA/postgresql.conf instead, and don’t set it in both places. Changing listen_addresses needs a full restart, not a reload.

Configure client authentication in pg_hba.conf

pg_hba.conf decides who may connect, to which database, from where, and how they authenticate. PostgreSQL reads it from top to bottom and uses the first line that matches, so order matters. Look at the active lines:

sudo grep -Ev '^\s*(#|$)' $PGDATA/pg_hba.conf

On RHEL-family packages, local socket connections normally use peer, and TCP connections from localhost use ident or scram-sha-256, depending on the package. ident needs an identd service on the client and is almost never what you want for TCP, so switch localhost lines to scram-sha-256 and add a rule for the application subnet. Edit the file as root:

sudo cp -a $PGDATA/pg_hba.conf $PGDATA/pg_hba.conf.orig
sudo vi $PGDATA/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             all                                     peer
host    all             all             127.0.0.1/32            scram-sha-256
host    all             all             ::1/128                 scram-sha-256
local   replication     all                                     peer
host    replication     all             127.0.0.1/32            scram-sha-256
host    replication     all             ::1/128                 scram-sha-256
# application servers: only appuser, only appdb, only this subnet
host    appdb           appuser         192.168.1.0/24          scram-sha-256

Keep the rules as narrow as you can. Avoid host all all 0.0.0.0/0, and never use trust on a network line. Reload and check for parse errors. A bad line is reported here instead of locking you out at the next restart:

sudo systemctl reload $SERVICE
sudo -u postgres psql -c "SELECT line_number, type, database, user_name, address, auth_method, error FROM pg_hba_file_rules;"

Any row with a value in the error column means that line was rejected. Fix it and reload again.

Open the firewall

firewalld ships a predefined postgresql service (TCP 5432). Allow it only from the application subnet with a rich rule:

sudo firewall-cmd --permanent --zone=public \
  --add-rich-rule='rule family="ipv4" source address="192.168.1.0/24" service name="postgresql" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --zone=public --list-rich-rules

On a lab box you can use sudo firewall-cmd --permanent --add-service=postgresql instead, which opens 5432 to everyone who can reach the zone. For zones, sources and rich-rule syntax, see the firewalld cheat sheet.

Keep SELinux enforcing

With the default port and data directory, the targeted SELinux policy already covers PostgreSQL and nothing needs changing. You only have to step in when you move things. Install the tools first:

sudo dnf install -y policycoreutils-python-utils
sudo semanage port -l | grep postgresql

Non-default port

If you change port to 5433, label the port before you restart, and open it in firewalld by port number instead of by service name:

sudo semanage port -a -t postgresql_port_t -p tcp 5433
sudo firewall-cmd --permanent --add-port=5433/tcp && sudo firewall-cmd --reload

Data directory on a separate disk

To keep the cluster on /data/pgsql, label the path, point the service at it with a systemd drop-in, then initialize it. This example uses the PGDG service. For AppStream, use postgresql and postgresql-setup --initdb, which reads PGDATA from the unit.

sudo mkdir -p /data/pgsql/18/data
sudo chown -R postgres:postgres /data/pgsql
sudo chmod 700 /data/pgsql/18/data
sudo semanage fcontext -a -t postgresql_db_t "/data/pgsql(/.*)?"
sudo restorecon -Rv /data/pgsql
sudo systemctl edit postgresql-18
[Service]
Environment=PGDATA=/data/pgsql/18/data
sudo systemctl daemon-reload
sudo /usr/pgsql-18/bin/postgresql-18-setup initdb
sudo systemctl enable --now postgresql-18

Do this before you load any data. Moving an existing cluster means stopping the service, copying with rsync -a, and then running the same labeling steps.

Web applications on the same or another host

If the client is a web application running under Apache or PHP-FPM with SELinux enforcing, it also needs permission to open database connections:

sudo setsebool -P httpd_can_network_connect_db 1

Test a remote connection

On app1, install only the client package and connect to the new database:

sudo dnf install -y postgresql
psql "host=192.168.1.40 port=5432 dbname=appdb user=appuser" \
  -c 'SELECT current_user, inet_server_addr(), version();' 

The AppStream postgresql client is fine for talking to a newer server for everyday queries. For pg_dump, use a client version at least as new as the server (for example postgresql18 from AppStream or PGDG). On the server, confirm the session and its authentication method:

sudo -u postgres psql -c "SELECT a.usename, a.client_addr, s.ssl, a.backend_type FROM pg_stat_activity a LEFT JOIN pg_stat_ssl s USING (pid) WHERE a.usename = 'appuser';"
sudo tail -n 20 $PGDATA/log/postgresql-*.log

Troubleshooting

  • Connection refused from the client: the server is not listening on that address, or the firewall is rejecting the connection. Check sudo ss -ltnp | grep 5432 on the server (should show 192.168.1.40, not only 127.0.0.1), confirm you restarted after changing listen_addresses, then test the port with nc -vz 192.168.1.40 5432 from the client and firewall-cmd --list-all on the server.
  • no pg_hba.conf entry for host "192.168.1.30", user "appuser", database "appdb": the network path works but no rule matches. Check the database, user and address columns, remember that the first match wins, and reload.
  • Ident authentication failed for user: a host line still uses ident. Change it to scram-sha-256 and reload.
  • Peer authentication failed for user "appuser": you connected over the local socket as a Linux user whose name doesn’t match the role. Use psql -h localhost -U appuser appdb to go over TCP with a password, or add a local line with scram-sha-256 for that role.
  • password authentication failed: wrong password, or a role whose password was stored as MD5 before you switched to scram. Reset it with \password appuser.
  • Service fails to start: read journalctl -u $SERVICE -b --no-pager and the newest file in $PGDATA/log/. Typical causes are a typo in postgresql.conf, a port already in use, or wrong ownership or permissions on the data directory (it must be owned by postgres with mode 0700).
  • Data directory is not empty during setup: the cluster is already initialized. Don’t remove it unless you are sure it holds nothing you need.
  • SELinux denials (permission denied although file permissions look right): run sudo ausearch -m AVC -ts recent. Fix labels with semanage and restorecon as shown above. Don’t switch SELinux to permissive.
  • Wrong packages or mixed repositories: rpm -qa 'postgresql*' | sort and dnf list --installed 'postgresql*' show which repository each package came from. AppStream and PGDG packages on the same host are a common source of strange behavior.

Next steps

You now have a PostgreSQL server on AlmaLinux 10 that accepts password logins from one subnet, with firewalld and SELinux both doing their jobs. Part 2 of this series covers users, roles, databases and privileges, and part 3 covers backups with pg_dump, pg_dumpall and pg_basebackup. If you would rather run PostgreSQL in a container for development, the Docker examples on AlmaLinux 10 post includes a Compose stack with postgres:18. The reference for everything above is the PostgreSQL documentation on the pg_hba.conf file and connection settings.

Leave a Reply

Your email address will not be published. Required fields are marked *