Neon’s Postgres connections are ephemeral, and spinning them up and tearing them down constantly is a huge performance drag. PgBouncer is the standard battle-tested solution to keep those connections alive and ready, acting as a lightweight connection pooler sitting between your app and Neon’s Postgres instances.
Let’s see it in action. Imagine your application needs to talk to a Neon database. Without PgBouncer, your app would do this for every single query:
- Establish connection:
psql -h <neon_host> -U <user> -d <db> -c "SELECT 1;"(This takes hundreds of milliseconds, sometimes seconds, especially on a cold start). - Execute query:
psql -h <neon_host> -U <user> -d <db> -c "SELECT * FROM users WHERE id = 1;" - Close connection: (Implicitly happens when the app process dies or explicitly).
Now, with PgBouncer running locally (or on a nearby VM), your app connects to PgBouncer instead:
- App connects to PgBouncer:
psql -h localhost -p 6432 -U <user> -d <db> -c "SELECT 1;"(This is instantaneous). - PgBouncer finds/creates a pooled connection to Neon: If a Neon connection is available, PgBouncer hands it off to your app. If not, PgBouncer establishes a new connection to Neon.
- Execute query:
psql -h localhost -p 6432 -U <user> -d <db> -c "SELECT * FROM users WHERE id = 1;"(This query now travels through PgBouncer to the already open Neon connection). - Close connection:
psql -h localhost -p 6432 -U <user> -d <db> -c "DISCONNECT"(PgBouncer marks the Neon connection as available for the next app request).
The core problem PgBouncer solves is the high latency and resource cost associated with establishing new, long-lived connections to a cloud-native database like Neon, especially when your application has many short-lived processes or threads that need database access. Each individual connection to Neon involves network round trips, authentication, and initialization that add up. PgBouncer amortizes this cost by maintaining a set of ready-to-go connections to Neon. When your application requests a database connection, PgBouncer immediately hands over one of its existing, already-established connections. When your application disconnects, PgBouncer doesn’t close the Neon connection; it simply returns it to its pool for the next application request.
To set this up, you’ll need a PgBouncer configuration file, typically named pgbouncer.ini. Here’s a breakdown of a common configuration for scaling with Neon:
[databases]
mydatabase = host=<neon_connection_string_without_password> port=5432 user=<neon_user> password=<neon_password> dbname=<neon_db_name>
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = session
max_client_conn = 1000
default_pool_size = 20
max_pool_size = 100
Let’s dissect these key parameters:
[databases]section: This defines the actual connection strings to your Neon database. Replace<neon_connection_string_without_password>,<neon_user>,<neon_password>, and<neon_db_name>with your Neon credentials. Note that you don’t include thesslmodehere; PgBouncer can be configured to handle SSL separately if needed, but for basic setups, it’s often omitted here.listen_addrandlisten_port:0.0.0.0means PgBouncer will listen on all network interfaces on the machine it’s running on.6432is the default Postgres port, but using a different one for PgBouncer is common practice to avoid confusion. Your application will connect to this address and port.auth_type = md5: This specifies the authentication method.md5is a common choice when you’re managing user credentials in PgBouncer’suserlist.txt.auth_file = /etc/pgbouncer/userlist.txt: This points to the file containing the usernames and passwords that PgBouncer users will use to connect to PgBouncer itself. A typicaluserlist.txtmight look like this:
Make sure the password here is the MD5-hashed version of your application’s database user password."your_app_user" "md5$(echo -n 'your_app_password' | md5sum | awk '{print $1}')"pool_mode = session: This is crucial. Insessionpooling mode, a client connects to PgBouncer, gets a connection from the pool for the duration of its session (until the client disconnects), and that connection is then returned to the pool. This is generally the safest and most compatible mode for most applications, especially those that use transactions or temporarily set session parameters.max_client_conn = 1000: This is the maximum number of connections PgBouncer will accept from your applications. This should be set high enough to accommodate your application’s peak load.default_pool_size = 20: For each database defined in the[databases]section, this sets the number of connections PgBouncer will try to maintain at all times. If fewer thandefault_pool_sizeconnections are in use, PgBouncer will establish new ones up to this limit.max_pool_size = 100: This is the maximum number of connections PgBouncer will open to a single backend database (Neon in this case). Even ifmax_client_connis high, you don’t want to overwhelm your Neon instance with too many connections. This should be tuned based on your Neon project’s connection limits and your database workload.
When using session pooling, PgBouncer is smart enough to prevent multiple clients from sharing a single backend connection simultaneously. However, it does allow a single client to reuse the same backend connection across multiple distinct queries within its session. This is where the performance gain comes from: the expensive connection establishment and teardown to Neon is skipped for each query, and instead, the client gets a ready connection from PgBouncer.
A common pitfall with PgBouncer, especially in session mode, is assuming it handles all connection-related state. If your application performs operations that alter session-level settings (like SET search_path = 'my_schema'; or SET TIME ZONE 'UTC';) and then disconnects, those settings are lost. When a new client session acquires that same backend connection from the pool, it will inherit any prior session settings that weren’t explicitly reset. To mitigate this, ensure your application either re-applies necessary session settings at the start of each logical operation or that your code is idempotent with respect to session configuration.
The next logical step after getting PgBouncer configured is understanding how to monitor its pool usage and tune default_pool_size and max_pool_size based on observed application behavior and Neon connection limits.