Running kernel seems to be up-to-date.No services need to be restarted.No containers need to be restarted.No user sessions are running outdated binaries.No VM guests are running outdated hypervisor (qemu) binaries on this host.
2. Review pg_hba.conf
Open pg_hba.conf, run:
sudo nano /etc/postgresql/17/main/pg_hba.conf
Make sure you have the following lines:
# "local" is for Unix domain socket connections onlylocal all all peer# IPv4 local connections:host all all 127.0.0.1/32 scram-sha-256# IPv6 local connections:host all all ::1/128 scram-sha-256
Explanation:
local all all peer means any OS user who is also a Postgres role with the same name can connect without a password via the local Unix socket.
Lets you run sudo -u postgres psql for admin tasks without typing a password.
host all all 127.0.0.1/32 scram-sha-256 means:
any TCP connection from 127.0.0.1 (IPv4 localhost) must use password authentication with SCRAM-SHA-256.
host all all ::1/128 scram-sha-256 means:
Same as above but for IPv6 localhost.
3. Create pgBouncer auth file
Open/create pgBouncer auth file, run:
sudo nano /etc/pgbouncer/userlist.txt
Note: You won't see anything at all in this txt file if you open it for the first time.
Give pgBouncer a list of users:
Give pgBouncer a list of users and passwords so it can use this list to authenticate the connection from a client.
While in userlist.txt, paste the following code to the end of the file:
"test_payload_auth_admin_v3" "your_user_password"
Add the following code (right after the above code) to create pgBouncer admin user:
"pgbouncer" "this_user_password"
Note: pgbouncer is a special reserved username that gives the user administrative privileges within pgBouncer's console.
Exit & Save (ctrl + X, yes, and confirm (enter))
When a client tries to connect to a PostgreSQL database through pgBouncer,
it first connects to the pgBouncer service. pgBouncer then checks its userlist.txt file for the provided username and password.
If a match is found, pgBouncer authenticates the client's connection to its own service. After this initial authentication,
pgBouncer will then establish a connection to the actual PostgreSQL database using the credentials from its configuration,
or in some cases, the same credentials provided by the client, depending on the authentication method configured.
When you create a new database for a new project and want to connect via pgBouncer, be sure to add its owner and password here.
Lock down the permission on the userlist.txt, run:
Note: This command changes the ownership of the file to the postgres user and group.
The reason for setting the ownership to postgres is that the pgBouncer service itself runs as the postgres user on most standard Linux systems.
Even though pgbouncer is a separate piece of software, its process is typically started and managed by the postgres user
to ensure it has the necessary permissions to communicate with the underlying PostgreSQL database files and
to manage its own configuration.
As long as it doesn't show error, you're good to continue.
Set permission, run:
sudo chmod 600 /etc/pgbouncer/userlist.txt
Explanation:
This command sets the permissions on the file. The 600 permission code breaks down as follows:
First digit represents the owner and number 6 means can read and write
Second digit represents the group and 0 means no access
Third digit represents others and 0 means no access
In short, for security files like userlist.txt, 600 is used so only the owner can touch it.
Verify permission, run:
ls -l /etc/pgbouncer/userlist.txt
This command lists the file's permissions and ownership, allowing you to verify that your changes were applied correctly.
The output should look similar to rw------- 1 pgbouncer pgbouncer.
-rw------- means only the owner can read/write, no one else can access.
This means your userlist.txt is now secure and usable.
4. Configure pgbouncer.ini
First let's create a backup for pgbouncer.ini in case we make a mistake and pgBouncer fails to start, run:
Tell PgBouncer where to find the certificate and key files. Open the pgbouncer.ini, run:
sudo nano /etc/pgbouncer/pgbouncer.ini
You need to add or modify several key settings to configure PgBouncer to work with your PostgreSQL database
and enable secure TLS connections. Reference:
Most of these fields already existed in pgbouncer.ini, make sure you modify them and don't deplicate extra of the same field
The [databases] section of your pgbouncer.ini file can and should have multiple databases listed
if you want pgBouncer to manage connection pooling for them.
So when you have a new database for a different project, be sure to add it here.
Restart pgBouncer so it loads the new config, run:
Check pgBouncer status to make sure it's running without error, run:
sudo systemctl status pgbouncer
Expected outcome:
● pgbouncer.service - connection pooler for PostgreSQL Loaded: loaded (/usr/lib/systemd/system/pgbouncer.service; enabled; preset: enabled) Active: active (running) since Sun 2025-08-10 16:09:04 UTC; 42s ago
Press q to exist the status viewing
Try a local connection through pgBouncer (no firewall needed), run:
From the result, this has proven pgBouncer works locally on the droplet (psql from 127.0.0.1:6432).
But your laptop still can’t reach it, because UFW blocks all traffic to port 6432 from the outside. So let's make it works from the laptop.
Note that if we do this for the second time (adding a second database), this check would give you SSL error.
Because by the end of this tutorial, we'll change to mTLS authentication. So you can skip this check.
5. Simulate remote connection
Set firewall rule to allow your current public IP to reach pgBouncer (port 6432), run:
(We will set mTLS for security later)
MYIP=$(curl -4 ifconfig.me)echo "Your IP is: $MYIP"sudo ufw allow from $MYIP/32 to any port 6432 proto tcpsudo ufw status | grep 6432
Expected outcome:
% Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed100 15 100 15 0 0 69 0 --:--:-- --:--:-- --:--:-- 69Your IP is: <your_droplet_ip>Rules updated
Explanation:
MYIP=$(curl -4 ifconfig.me)
Runs curl to query ifconfig.me, which returns your current public IPv4 address.
Stores it in the shell variable MYIP.
The -4 ensures it uses IPv4 (avoids IPv6 complications).
echo 'Your IP is: $MYIP'
Prints the IP we just detected, so you can confirm it’s correct before making firewall changes.
sudo ufw allow from $MYIP/32 to any port 6432 proto tcp
Updates the firewall (UFW) to allow incoming TCP connections on port 6432 (pgBouncer)
Only from the specific IP $MYIP.
/32 means “just this one IP,” so it’s tightly restricted.
sudo ufw status | grep 6432
Checks the firewall status list and filters for lines containing 6432 so you see the rule you just added.
6. Prepare remote connection
Install psql command-line client (if not already)
Download the latest version for your operating system from PSQL Command Client
During setup, uncheck everything except Command Line Tools.
Add to Windows PATH environment variable
Add the bin folder of the PostgreSQL installation (e.g., C:\Program Files\PostgreSQL\17\bin) to your Windows PATH environment variable.
Windows -> search "Edit System Environment Variables"
Click "Environment Variables" button
In User variables for your PC username, select Path.
Click Edit → New.
Paste: C:\Program Files\PostgreSQL\17\bin
Click OK on all windows.
Reopen PowerShell and test:
psql --version
Check your ip address (IPv4), run:
curl.exe -s -4 ifconfig.me
Allow your PC ip: Tell Droplet (Ubuntu firewall) to allow it. SSH into your Droplet and run:
sudo ufw allow from <your_pc_ip_address>/32 to any port 6432 proto tcp
sudo ufw allow OpenSSH
sudo ufw enable
Type y + enter to confirm
Check if both rules (OpenSSH and ufw) are active, run:
sudo ufw status numbered
Expected outcome:
To Action From -- ------ ----[ 1] 6432/tcp ALLOW IN <your_droplet_ip>[ 2] 6432/tcp ALLOW IN <your_pc_ip_address>[ 3] OpenSSH ALLOW IN Anywhere[ 4] OpenSSH (v6) ALLOW IN Anywhere (v6)
Now we've confirmed that UFW is allowing traffic on port 6432, but that doesn't guarantee a remote connection will work.
7. Test remote connection
To actually test if remote access works, you need to try connecting from outside the Droplet (from your own machine) to that port.
First, check if pgBouncer is listening to public interface, run: