Database Advanced

Troubleshooting ‘PostgreSQL connection limit reached’ on Ubuntu 20.04 LTS: Database Pool Full Error

Resolve 'PostgreSQL connection limit reached' on Ubuntu 20.04 LTS. Diagnose, adjust max_connections, optimize pooling with PgBouncer, and manage sessions to restore database connectivity.

👨‍💻
Senior Systems Architect • Verified in Staging Labs

Resolve 'PostgreSQL connection limit reached' on Ubuntu 20.04 LTS. Diagnose, adjust max_connections, optimize pooling with PgBouncer, and manage sessions to restore database connectivity.

Troubleshooting 'PostgreSQL Connection Limit Reached' on Ubuntu 20.04 LTS

When your web application or services start exhibiting database connection errors, slowness, or complete unavailability, one of the most common culprits in a PostgreSQL environment is reaching the maximum allowed concurrent connections. This critical issue, often manifesting as a "database pool full" or "too many clients" error, indicates that PostgreSQL cannot accept new connections because all available slots are occupied. As an expert SysAdmin and DevOps engineer, understanding and resolving this issue swiftly is paramount to maintaining service uptime and performance.

Symptom & Error Signature

Users will typically experience service degradation or outages, seeing errors in their application logs, browser, or terminal output when attempting database operations. Common error messages you'll encounter include:

Application Log Example (e.g., from a Python/Django app using psycopg2):

psycopg2.OperationalError: connection to server at "your-db-host", port 5432 failed: FATAL:  remaining connection slots are reserved for non-replication superuser connections

PostgreSQL Server Log Example (/var/log/postgresql/postgresql-12-main.log):

2023-10-26 10:05:32 UTC [1234]: [2-1] user=app_user,db=my_database FATAL:  sorry, too many clients already
2023-10-26 10:05:32 UTC [1235]: [2-1] user=app_user,db=my_database FATAL:  remaining connection slots are reserved for non-replication superuser connections

Attempting to connect via psql as a regular user:

psql -h your-db-host -U app_user my_database

Output:

psql: error: could not connect to server: FATAL:  remaining connection slots are reserved for non-replication superuser connections

Superusers (like the postgres OS user) are typically still able to connect even when the limit is reached, as PostgreSQL reserves a few slots specifically for administrative purposes. This allows you to diagnose and fix the issue.

Root Cause Analysis

The "PostgreSQL connection limit reached" error fundamentally means the number of active and idle connections to your PostgreSQL server has exceeded the max_connections parameter defined in your postgresql.conf. Several factors can contribute to this:

  1. max_connections parameter is too low: The default max_connections (often 100) might be insufficient for your application's workload, especially on modern servers with ample RAM.
  2. Application connection leaks: Your application code might be opening database connections but failing to close them properly, leading to a gradual accumulation of idle but active connections. This is a common bug.
  3. Inefficient application connection pooling: Applications might be configured to open a large number of connections independently or might not be utilizing an efficient server-side connection pooler (like PgBouncer) correctly, or its client-side pool is misconfigured.
  4. Long-running or uncommitted transactions: Transactions that remain open for extended periods, or are aborted without releasing connections, can tie up connection slots. This often manifests as idle in transaction states.
  5. Sudden traffic spikes: An unexpected surge in user traffic or background jobs can rapidly consume all available connection slots.
  6. External tools/monitoring: Database management tools, monitoring agents, or development environments connecting to the database might be consuming a significant number of connections that were not accounted for.
  7. Inadequate system resources: While less direct, insufficient RAM can indirectly limit the practical max_connections you can set, as each connection consumes a certain amount of memory.

Step-by-Step Resolution

Follow these detailed steps to diagnose and resolve the PostgreSQL connection limit issue on your Ubuntu 20.04 LTS server.

1. Assess Current Connection Usage

First, connect to your PostgreSQL instance as a superuser (e.g., postgres) to inspect the current state.

sudo -u postgres psql

Once connected, run the following queries to gather crucial information:

  • Check max_connections:

    SHOW max_connections;
    

    This will show you the currently configured maximum allowed connections.

  • Check currently active and idle connections:

    SELECT count(*) FROM pg_stat_activity;
    

    Compare this total count to the max_connections value. If they are close, you're hitting the limit.

  • Inspect individual connections and their states:

    SELECT pid, datname, usename, client_addr, application_name, backend_start, state, state_change, query_start, query
    FROM pg_stat_activity
    ORDER BY backend_start DESC;
    

    This comprehensive query helps identify which Process IDs (PIDs), users (usename), databases (datname), client IPs (client_addr), application names (application_name), and specific queries (query) are consuming connections. Pay close attention to connections in idle in transaction state that have been open for a long time, as these are often indicative of application issues or uncommitted transactions. Also, look for repetitive queries from a single application_name indicating potential pooling problems.

2. Terminate Idle or Problematic Connections (Immediate Relief)

If you're facing a critical outage and need to free up connection slots immediately, you can terminate specific idle or problematic connections.

Terminating connections can abruptly disrupt application processes or transactions, potentially leading to data inconsistencies or application errors. Only perform this action if you understand the impact and are certain which connections can be safely killed. Prioritize idle in transaction connections if they appear to be stuck or illegitimate.

From the psql prompt, identify PIDs from the previous pg_stat_activity output.

To terminate all idle in transaction connections for a specific database and user:

SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = 'your_problematic_database' AND usename = 'your_problematic_user' AND state = 'idle in transaction';

Replace 'your_problematic_database' and 'your_problematic_user' with the actual values.

To terminate a specific connection by its PID:

SELECT pg_terminate_backend(12345); -- Replace 12345 with the actual PID from pg_stat_activity

3. Adjust max_connections in postgresql.conf

This is often the most direct solution, but it requires careful consideration of your server's resources. Each connection consumes a certain amount of memory (typically 10MB-20MB per connection, including shared buffers and session-specific parameters like work_mem). Increasing max_connections significantly without sufficient RAM can lead to out-of-memory issues, excessive swapping, and severe performance degradation.

  1. Edit the PostgreSQL configuration file: On Ubuntu 20.04 LTS with PostgreSQL 12, the primary configuration file is usually located at /etc/postgresql/12/main/postgresql.conf.

    sudo nano /etc/postgresql/12/main/postgresql.conf
    
  2. Locate and modify max_connections: Find the line beginning with max_connections and increase its value. A common starting point for a moderately busy web application might be 200-300, but this needs to be tailored to your specific workload and server resources. Consider the formula: (Total RAM - OS/App RAM - Shared Buffers) / (Est. Mem per connection) = Max Connections.

    #------------------------------------------------------------------------------
    # CONNECTIONS AND AUTHENTICATION
    #------------------------------------------------------------------------------
    
    # - Connection Settings -
    
    max_connections = 200             # (Default is often 100. Adjust based on RAM and workload, e.g., 300-500)
    

    Always ensure your server has enough physical RAM. A conservative estimate for memory per connection is around 10MB-20MB. Multiply your proposed max_connections by this figure to get a rough idea of the additional RAM PostgreSQL might consume. For example, 500 connections * 10MB/connection = 5GB. Monitor free -h and htop after the change.

  3. Save changes and restart PostgreSQL: After saving the postgresql.conf file, you must restart the PostgreSQL service for the changes to take effect.

    sudo systemctl restart postgresql
    sudo systemctl status postgresql
    

    Verify that the service restarted successfully. Check the logs for any errors.

4. Implement or Optimize Connection Pooling (Recommended for Production)

For robust and scalable production environments, relying solely on increasing max_connections is often not the most efficient or performant solution. A dedicated server-side connection pooler like PgBouncer is highly recommended. PgBouncer acts as a lightweight proxy between your application and PostgreSQL, managing a smaller, persistent pool of connections to the database. Your application connects to PgBouncer, which then efficiently reuses these database connections, significantly reducing the overhead of new connection establishment and preventing max_connections issues.

  1. Install PgBouncer:

    sudo apt update
    sudo apt install pgbouncer
    
  2. Configure PgBouncer: The main configuration file is typically /etc/pgbouncer/pgbouncer.ini.

    sudo nano /etc/pgbouncer/pgbouncer.ini
    
    • [databases] section: Define your database(s). The user and password here are for PgBouncer to connect to the actual PostgreSQL server.

      [databases]
      mydb = host=127.0.0.1 port=5432 dbname=my_database user=pgbouncer_db_user password=your_db_password
      

      For security, avoid hardcoding passwords directly in pgbouncer.ini in production. Consider using an auth_file or environment variables where possible for the [databases] section. Create a dedicated low-privilege user (e.g., pgbouncer_db_user) in PostgreSQL for PgBouncer's connection.

    • [pgbouncer] section: Adjust global pooling parameters.

      [pgbouncer]
      listen_addr = 0.0.0.0           ; Or 127.0.0.1 if only local apps connect
      listen_port = 6432              ; PgBouncer's listening port for clients
      auth_type = md5                 ; Or hba_file for more complex setups
      auth_file = /etc/pgbouncer/userlist.txt
      
      pool_mode = session             ; transaction or statement might be better depending on app
      default_pool_size = 20          ; Number of server connections to keep open per database
      min_pool_size = 5               ; Minimum server connections to keep open
      max_client_conn = 1000          ; Max connections *to* PgBouncer from clients
      # reserve_pool_size = 5         ; If pool is full, use these reserved connections
      # server_lifetime = 3600        ; Close server connection after this many seconds of usage
      # server_idle_timeout = 600     ; Close server connection if idle for this many seconds
      

      pool_mode explanation:

      • session: (Default) A server connection is assigned to a client for the entire duration of the client connection to PgBouncer. Suitable if your application opens a connection and holds it.
      • transaction: A server connection is assigned to a client only for the duration of a transaction. Once the transaction ends, the server connection is immediately returned to the pool. This is generally the most efficient for typical web applications with short, self-contained transactions.
      • statement: A server connection is assigned for a single statement. Extremely strict and rarely used for typical applications due to potential issues with multi-statement transactions.
  3. Create userlist.txt for authentication (for clients connecting to PgBouncer):

    sudo nano /etc/pgbouncer/userlist.txt
    

    Add your application user(s) and their MD5 hashed passwords:

    "my_app_user" "md5YOURMD5HASH"
    "postgres" "md5YOURPOSTGRESMD5HASH"
    

    You can generate the MD5 hash of a password using echo -n "your_password" | md5sum | awk '{print $1}'.

  4. Restart PgBouncer:

    sudo systemctl restart pgbouncer
    sudo systemctl status pgbouncer
    

    Ensure PgBouncer is running and listening on the configured port (e.g., 6432).

  5. Update your application configuration: Configure your application to connect to PgBouncer's listening port (e.g., 6432) and IP address, instead of directly to PostgreSQL (port 5432).

5. Review Application Code and Configuration

Even with increased max_connections or a server-side pooler like PgBouncer, inefficient application behavior can still cause issues, albeit less frequently.

  • Proper Connection Handling: Ensure your application always closes database connections or returns them to its client-side pool as soon as they are no longer needed. Use context managers (with statements in Python), try...finally blocks, or framework-provided methods for resource management.
  • Client-Side Pooling Integration: If your application framework (e.g., Django, Rails, Spring Boot) provides built-in client-side connection pooling, ensure it's configured correctly. When using PgBouncer, the client-side pool size should generally be modest (e.g., 10-20 connections) as PgBouncer handles the heavy lifting of server-side pooling. An overly large client-side pool connecting to PgBouncer can still exhaust PgBouncer's max_client_conn or overwhelm the database if pool_mode is session.
  • Transaction Management: Verify that transactions are committed or rolled back promptly. Long-running, idle transactions are a common source of idle in transaction states and connection exhaustion. Review code paths that initiate transactions to ensure they have definitive end points.

6. Monitor Database and System Resources

Ongoing, proactive monitoring is crucial to prevent recurrence of connection limit issues and maintain optimal performance.

  • Database Monitoring:
    • Set up alerts for the number of active connections (pg_stat_activity) nearing your max_connections or default_pool_size (for PgBouncer).
    • Monitor PostgreSQL log files for FATAL: too many clients already messages.
    • Track long-running queries and idle in transaction states using pg_stat_activity or dedicated monitoring tools.
    • Monitor PgBouncer statistics using SHOW STATS; or SHOW CLIENTS; by connecting to its administration port.
  • System Resource Monitoring:
    • Monitor CPU, RAM, and disk I/O usage of your database server using tools like atop, htop, netdata, Prometheus/Grafana, or cloud provider monitoring services.
    • High memory usage, especially for the postgres processes, might indicate that max_connections is too high for your available RAM or that work_mem and other per-session settings are too generous.
    • Set thresholds for these metrics to alert you to potential bottlenecks before they impact service.

By diligently following these steps, you can effectively diagnose, resolve, and prevent PostgreSQL connection limit issues, ensuring the stability and performance of your database-driven applications on Ubuntu 20.04 LTS.

👨‍💻

Johnathon Wheeler

Senior Systems Architect & DevOps Engineer • Austin, TX

Connect on LinkedIn →

Johnathon has over 16 years of hands-on experience designing, debugging, and scaling Linux web hosting stacks, container clusters, and high-availability database architectures. Every guide on ButItWorkedLocal is independently tested against Debian 12, Ubuntu 24.04/22.04 LTS, Rocky Linux, and Docker environments to guarantee reproducibility in production.

🛡️

Our Production Verification Guarantee

Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.