Configuring Remote Access
By default, PostgreSQL on Ubuntu only listens for connections coming from the same machine it runs on (this is called localhost, the loopback address 127.0.0.1). That is the safest setting, but sometimes your application server, your laptop, or a backup tool lives on a different machine and needs to talk to the database. To allow that, you have to change two settings and tell PostgreSQL who is allowed in. This page walks through exactly how to do that, and just as importantly, how to do it without leaving your database wide open to the internet.
The two files you will edit
PostgreSQL controls remote access with two configuration files. You need to understand what each one does before touching them.
| File | What it controls | Plain-English meaning |
|---|---|---|
postgresql.conf | listen_addresses | Which network cards (IP addresses) the server listens on. Think of it as deciding which doors exist. |
pg_hba.conf | Host-Based Authentication rules | Who is allowed through each door, from which IP, to which database, and how they must prove who they are. HBA stands for Host-Based Authentication. |
On Ubuntu 22.04/24.04 LTS these files live under a version-specific path. PostgreSQL 16 is the current default, so the path is:
ls /etc/postgresql/16/main/
Output:
conf.d environment pg_ctl.conf pg_hba.conf pg_ident.conf postgresql.conf
If you installed a different major version, swap 16 for your version number. You can confirm it with pg_lsclusters.
Step 1: Set listen_addresses
A network interface is just a connection point your server has to a network (for example, one for the public internet and one for a private internal network). listen_addresses tells PostgreSQL which of those it should accept connections on. Open the config file:
sudo nano /etc/postgresql/16/main/postgresql.conf
Find the line that starts with #listen_addresses. It looks like this by default:
#listen_addresses = 'localhost' # what IP address(es) to listen on;
The # means the line is commented out (ignored), and the default behaviour is localhost only. Change it to listen on the specific private IP address of your server. Suppose your server’s private IP is 10.0.0.5:
listen_addresses = 'localhost,10.0.0.5'
Security warning: You will often see tutorials tell you to use
listen_addresses = '*', which means “listen on every interface, including the public internet.” Do NOT do this unless a firewall is protecting the port. Binding to one specific private IP is far safer than*. Treat*as a last resort.
When to use which value:
| Value | Meaning | When to use |
|---|---|---|
localhost | Same machine only | App and DB on the same server. The safe default. |
'localhost,10.0.0.5' | Loopback plus one private IP | App is on another machine in the same private network. Recommended for remote access. |
'*' | Every interface | Only with a strict firewall in front. Risky. |
Step 2: Add a host rule in pg_hba.conf
Now open the authentication file:
sudo nano /etc/postgresql/16/main/pg_hba.conf
Each rule is one line with these columns: TYPE DATABASE USER ADDRESS METHOD. We will add a host rule that lets a specific user connect to a specific database from a specific IP, using an encrypted password method called scram-sha-256 (Salted Challenge Response Authentication Mechanism — the modern, secure way PostgreSQL verifies passwords; it never sends the raw password over the wire).
Add this line, scoped as tightly as you can. Here we allow the user appuser to reach the database appdb only from the single application server IP 10.0.0.20:
# TYPE DATABASE USER ADDRESS METHOD
host appdb appuser 10.0.0.20/32 scram-sha-256
The /32 at the end is a CIDR netmask meaning “exactly this one IP address, nothing else” (CIDR is just a notation for describing a range of IPs). To allow a whole small private subnet instead, you could write 10.0.0.0/24, which covers 10.0.0.0 through 10.0.0.255.
Gotcha: Never write
0.0.0.0/0here. That means “any IP address on Earth.” Combined with an open port, it invites every bot on the internet to brute-force your passwords. Always scope to a single host or a tight private subnet.
Make sure passwords use scram-sha-256
For the scram-sha-256 method to work, the password must be stored in that format. Check and, if needed, set the default:
sudo -u postgres psql -c "SHOW password_encryption;"
Output:
password_encryption
----------------------
scram-sha-256
(1 row)
If it shows md5 (an older, weaker method), set it to scram-sha-256 in postgresql.conf, restart, then re-set the user’s password with \password appuser inside psql so it is re-hashed.
Step 3: Restart PostgreSQL
Changes to listen_addresses require a full restart (a reload is not enough). Changes to pg_hba.conf only need a reload, but restarting covers both:
sudo systemctl restart postgresql
sudo systemctl status postgresql
Output:
● postgresql.service - PostgreSQL RDBMS
Loaded: loaded (/usr/lib/systemd/system/postgresql.service; enabled)
Active: active (exited) since Mon 2026-06-15 10:12:04 UTC; 2s ago
Confirm PostgreSQL is now listening on the private IP and port 5432:
sudo ss -tlnp | grep 5432
Output:
LISTEN 0 244 127.0.0.1:5432 0.0.0.0:* users:(("postgres",pid=2410,fd=7))
LISTEN 0 244 10.0.0.5:5432 0.0.0.0:* users:(("postgres",pid=2410,fd=7))
Step 4: Lock the port down with a firewall
Opening the port in PostgreSQL is not the same as opening it on the operating system. Ubuntu ships with ufw (Uncomplicated Firewall). Allow port 5432 only from the trusted application server, and deny everything else:
sudo ufw allow from 10.0.0.20 to any port 5432 proto tcp
sudo ufw status
Output:
To Action From
-- ------ ----
5432/tcp ALLOW 10.0.0.20
This way, even if a rule were misconfigured, the firewall still blocks strangers.
Step 5: Test the connection
From the application server, test that you can reach the database. The psql client must be installed there too (sudo apt install postgresql-client):
psql "host=10.0.0.5 port=5432 dbname=appdb user=appuser sslmode=require"
Output:
Password for user appuser:
psql (16.3)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384)
Type "help" for help.
appdb=>
The sslmode=require part forces the connection to be encrypted in transit, so nobody on the network can read your traffic.
Prefer an SSH tunnel or private network
If your database does not truly need a public-facing port, the safest pattern is to not open one at all. Instead, use an SSH tunnel (a secure encrypted pipe through your existing SSH login). From your laptop:
ssh -L 5433:localhost:5432 user@your-server-ip
Now connect to localhost:5433 locally, and traffic is forwarded securely to PostgreSQL’s localhost:5432 on the server. PostgreSQL never needs listen_addresses changed and no extra port is exposed. Use this for admin access and one-off queries; use real remote access (Steps 1-4) only for app servers that connect constantly.
Best practices
- Bind
listen_addressesto a specific private IP, never'*'unless a firewall strictly fences the port. - Scope every
pg_hba.confrule to a single host (/32) or a tight private subnet. Never use0.0.0.0/0. - Always use
scram-sha-256, nevertrustormd5, for authentication. - Layer a
ufwrule so the OS firewall also restricts port5432to known IPs. - Require SSL (
sslmode=requireor stronger) so credentials and data are encrypted in transit. - For occasional admin access, use an SSH tunnel instead of opening the database port to the network.
- Keep a backup before changing config, and watch
/var/log/postgresql/for connection or authentication errors.