Configuring PgBouncer Connection Pooling for PostgreSQL

Configuring PgBouncer Connection Pooling for PostgreSQL

What You’ll Need

  • A clean server instance running Ubuntu 22.04 LTS on a Hetzner VPS or Contabo VPS (or DigitalOcean as an alternative)
  • Namecheap if domain needed for SSL/TLS database hostnames
  • PostgreSQL 14 or higher installed on your target environment
  • Sudo or root privileges on the server terminal
  • n8n Cloud or self-hosted n8n if connecting external workflow automation tooling
  • Make.com only for comparisons regarding high-frequency database connection polling overhead

Table of Contents

Understanding PgBouncer and Connection Pooling

PostgreSQL relies on a process-based architecture. Whenever a client connects to the database, PostgreSQL forks a brand new backend process to handle that specific session. While this guarantees complete memory isolation and stability between client sessions, it introduces substantial overhead. Each forked process consumes somewhere between 2MB to 10MB of RAM immediately upon spawn, without having executed a single query yet.

When you run microservices, web application clusters, or high-throughput workflow engines, your total concurrent client connections can spike into the thousands. Opening and closing raw PostgreSQL connections creates massive CPU spikes due to repetitive connection handshakes, authentication checks, and process creation. If your backend infrastructure exhausts available physical memory under high connection loads, you should review our detailed guide on Configuring Swap Space on Ubuntu Linux VPS to prevent operating system kernel panics and out-of-memory crashes.

PgBouncer is a lightweight connection pooler designed to run in front of PostgreSQL. It intercepts incoming client connections and multiplexes them across a much smaller, static pool of persistent database backend connections. Instead of managing thousands of backend processes, PostgreSQL only maintains a lean set of active processes, keeping memory usage constant while throughput scales exponentially.

PgBouncer operates in three distinct pooling modes:

  1. Session Pooling: PgBouncer assigns a backend connection to the client when it logs in and holds it active until the client explicitly disconnects. This is the safest mode, but it offers the least memory savings for long-lived idle connections.
  2. Transaction Pooling: PgBouncer assigns a backend connection to the client only for the duration of a transaction. Once the transaction completes (after COMMIT or ROLLBACK), the connection returns to the pool. This mode delivers maximum performance and memory efficiency for modern web applications.
  3. Statement Pooling: PgBouncer assigns a backend connection for a single SQL query. Transactions containing multi-statement blocks are prohibited in this mode.

For almost all production web frameworks, transaction pooling provides the optimal balance between application compatibility and infrastructure scaling.

Step 1: Installing PgBouncer and PostgreSQL

Let us log into our server terminal to install the necessary packages using the official apt package manager. If you are setting up fresh server infrastructure for your databases, you can check out our walk-through on Deploying Open Source Workflow Systems On Hetzner to plan your virtual instance specifications. We will deploy our setup on a flexible Hetzner VPS instance.

Execute the following commands in your shell to update package repositories and install PostgreSQL, additional database utilities, and PgBouncer:

sudo apt update
sudo apt install -y postgresql postgresql-contrib pgbouncer

Verify that both services are running using systemctl commands:

sudo systemctl status postgresql
sudo systemctl status pgbouncer

Before configuring the pooler, let us create a dedicated production database and database user inside PostgreSQL. Switch to the default postgres system account and launch the interactive psql terminal:

sudo -u postgres psql

Run these SQL statements to create a target database called production_db and an application user named app_user with a secure password:

CREATE DATABASE production_db;
CREATE USER app_user WITH ENCRYPTED PASSWORD 'SuperSecurePassword123!';
GRANT ALL PRIVILEGES ON DATABASE production_db TO app_user;
\c production_db
GRANT ALL ON SCHEMA public TO app_user;
\q

Now that the database and user are present inside PostgreSQL, we can construct the connection pooling configuration.

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.

Step 2: Configuring pgbouncer.ini and userlist.txt

PgBouncer uses two primary configuration files: pgbouncer.ini for runtime settings and pool limits, and userlist.txt for client authentication credentials.

First, stop the default PgBouncer service so we can apply our configurations safely:

sudo systemctl stop pgbouncer

Now, replace the default configuration file located at /etc/pgbouncer/pgbouncer.ini with our optimized configuration. Open the file in your preferred text editor:

sudo nano /etc/pgbouncer/pgbouncer.ini

Write the complete configuration detailed below:

[databases]
production_db = host=127.0.0.1 port=5432 dbname=production_db user=app_user password=SuperSecurePassword123!
* = host=127.0.0.1 port=5432

[pgbouncer]
logfile = /var/log/postgresql/pgbouncer.log
pidfile = /var/run/postgresql/pgbouncer.pid
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = scram-sha-256
auth_file = /etc/pgbouncer/userlist.txt
admin_users = postgres, admin
stats_users = stats, postgres

pool_mode = transaction
server_reset_query = DISCARD ALL
max_client_conn = 5000
default_pool_size = 20
min_pool_size = 5
reserve_pool_size = 5
reserve_pool_timeout = 5
max_db_connections = 50
max_user_connections = 50

server_idle_timeout = 600
server_connect_timeout = 15
server_login_retry = 15
query_timeout = 0
client_idle_timeout = 0
client_login_timeout = 60

ignore_startup_parameters = extra_float_digits, application_name

Save and close the file.

Next, we must generate the user authentication credentials file located at /etc/pgbouncer/userlist.txt. PgBouncer needs this file to authenticate incoming database client requests without querying PostgreSQL for every plain request.

Open /etc/pgbouncer/userlist.txt:

sudo nano /etc/pgbouncer/userlist.txt

Insert the full user list and matching credentials. You can use raw text passwords enclosed in quotes or standard PostgreSQL password hashes:

"app_user" "SuperSecurePassword123!"
"postgres" "SuperSecurePassword123!"
"admin" "SuperSecurePassword123!"

Save the file and set strict file permission attributes so that unprivileged Linux users cannot read raw credentials off the disk:

sudo chown -R postgres:postgres /etc/pgbouncer
sudo chmod 0600 /etc/pgbouncer/userlist.txt
sudo chmod 0640 /etc/pgbouncer/pgbouncer.ini

If you manage web frontends or internal database consoles on your server, such as when Deploying Appsmith on Budget Hetzner Cloud VPS, you will configure your dashboard datasources to target host port 6432 instead of port 5432. This routes all GUI data queries cleanly through PgBouncer connection pools.

Step 3: Tuning PostgreSQL and Systemd Service

Because PgBouncer handles the thousands of incoming idle connections from clients, PostgreSQL no longer needs an unnecessarily large max_connections allocation. Restricting PostgreSQL to a modest pool of connections conserves system RAM and prevents CPU cache contention on the host system.

Open your main PostgreSQL configuration file:

sudo nano /etc/postgresql/14/main/postgresql.conf

Find the networking and resource configuration keys and adjust them to match these parameters:

listen_addresses = '127.0.0.1'
max_connections = 100
shared_buffers = 1GB
work_mem = 16MB
maintenance_work_mem = 256MB

Save the file. Next, grant local access permissions inside PostgreSQL by updating /etc/postgresql/14/main/pg_hba.conf so PgBouncer can connect over loopback TCP:

sudo nano /etc/postgresql/14/main/pg_hba.conf

Ensure the host TCP rules allow password authentication for host connections on loopback addresses:

# 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

Save and exit. Restart PostgreSQL to apply the parameter changes:

sudo systemctl restart postgresql

Now, tune the Linux OS system limits for PgBouncer so the daemon does not run out of available file descriptors when thousands of simultaneous client sockets connect to port 6432.

Create an override directory for the PgBouncer systemd service:

sudo mkdir -p /etc/systemd/system/pgbouncer.service.d
sudo nano /etc/systemd/system/pgbouncer.service.d/override.conf

Add these system limits to ensure high concurrent file handling capacity:

[Service]
LimitNOFILE=65536

Reload systemd daemon files and start PgBouncer:

sudo systemctl daemon-reload
sudo systemctl enable pgbouncer
sudo systemctl start pgbouncer

Check the health status of PgBouncer to confirm it is active and listening on port 6432:

sudo systemctl status pgbouncer

Step 4: Benchmarking Performance with Pgbench

To measure the efficiency gains of connection pooling, we can run a benchmark using pgbench, the standard PostgreSQL benchmarking utility.

First, initialize a benchmark database schema directly on PostgreSQL port 5432:

sudo -u postgres pgbench -i -s 20 production_db

Now, run a stress test sending 150 concurrent client connections directly against the unpooled PostgreSQL port 5432:

pgbench -c 150 -j 8 -T 30 -h 127.0.0.1 -p 5432 -U app_user production_db

You will observe higher connection latency, potential connection rejected errors, and noticeable system CPU stress as PostgreSQL struggles to fork individual backend processes for all 150 incoming clients.

Next, run the exact same load test pointed directly at PgBouncer on port 6432:

pgbench -c 150 -j 8 -T 30 -h 127.0.0.1 -p 6432 -U app_user production_db

The output will display a significant decrease in latency along with a substantial bump in total Transactions Per Second (TPS).

starting benchmark...
transaction type: <builtin: TCB-like>
scaling factor: 20
query mode: simple
number of clients: 150
number of threads: 8
duration: 30 s
number of transactions actually processed: 142850
latency average = 31.421 ms
tps = 4761.666667 (including connections establishing)
tps = 4812.333333 (excluding connections establishing)

You can inspect live connection metrics and pool statistics inside PgBouncer by connecting to the special administrative database named pgbouncer using your administrative database account:

psql -h 127.0.0.1 -p 6432 -U postgres pgbouncer

Run administrative commands inside the pgbouncer console:

SHOW POOLS;
SHOW STATS;
SHOW CLIENTS;
SHOW SERVERS;

To exit the administrative pooler console, type:

\q

Your PostgreSQL instance is now protected by PgBouncer transaction pooling, fully equipped to sustain thousands of concurrent connection bursts without degrading database server performance.

Getting Started

Implementing connection pooling is one of the most cost-effective performance upgrades you can make to a database architecture. By running PgBouncer on a performant host like a Hetzner VPS or Contabo VPS, you eliminate unnecessary connection overhead and unlock predictable database response times.

Outsource Your Automation

Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

Want this engine running on your own VPS?

This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.

Get System 3
system online