KeepAlive tuning in Nginx controls how long idle client connections stay open for reuse, reducing handshake overhead and connection churn for sites that serve many small assets. Better reuse usually means lower latency and less CPU spent on new TCP/TLS setup.

HTTP keep-alive works by allowing multiple requests to flow over a single TCP connection instead of closing after each response. In Nginx, the client-side behavior is primarily governed by directives like keepalive_timeout and keepalive_requests, and the practical ceiling is shaped by per-worker limits such as worker_connections and the process file-descriptor limit.

Timeouts set too high can strand a large number of idle sockets and starve workers under high concurrency, while timeouts set too low can increase reconnect rates and TIME_WAIT pressure on clients and intermediaries. Reverse proxy deployments add a second dimension because upstream connection reuse to backends is configured separately from client keep-alive, and values should be aligned with any upstream proxy or load balancer idle timeout to avoid unexpected resets.

Steps to tune keepalive in Nginx:

  1. Set client keep-alive limits inside the http block.
    http {
        keepalive_timeout 15s 15s;
        keepalive_requests 1000;
        ##### snipped #####
    }

    The second value in keepalive_timeout adds a Keep-Alive: timeout= header for easier verification with curl.

    Excessively long timeouts can consume file descriptors and worker_connections with idle ESTAB sockets under many concurrent clients.

  2. Enable upstream connection reuse when proxying to HTTP backends.
    upstream app_backend {
        server 127.0.0.1:8080;
        keepalive 32;
    }
    
    server {
        ##### snipped #####
        location /api/ {
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            proxy_pass http://app_backend;
        }
    }

    Upstream keepalive reuses connections from Nginx to the backend and is independent of client keep-alive settings.

  3. Test the updated configuration for syntax errors.
    $ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful
  4. Reload Nginx to apply the updated keep-alive settings.
    $ sudo systemctl reload nginx

    A reload applies configuration changes without dropping established connections.

  5. Confirm nginx.service is active after the reload.
    $ sudo systemctl status nginx --no-pager
    ● nginx.service - A high performance web server and a reverse proxy server
         Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
        Drop-In: /etc/systemd/system/nginx.service.d
                 └─limits.conf
         Active: active (running) since Mon 2025-12-29 22:09:39 UTC; 1min 4s ago
           Docs: man:nginx(8)
        Process: 1738 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
        Process: 1739 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
        Process: 1847 ExecReload=/usr/sbin/nginx -g daemon on; master_process on; -s reload (code=exited, status=0/SUCCESS)
       Main PID: 1741 (nginx)
          Tasks: 11 (limit: 9396)
         Memory: 71.1M (peak: 143.3M)
            CPU: 158ms
         CGroup: /system.slice/nginx.service
    ##### snipped #####
  6. Inspect the status page counters to confirm idle keep-alive connections appear as Waiting.
    $ curl -s http://127.0.0.1/nginx_status
    Active connections: 1 
    server accepts handled requests
     1 1 1 
    Reading: 0 Writing: 1 Waiting: 0 

    Higher Waiting usually indicates more idle keep-alive client connections ready for reuse.

  7. Check response headers to confirm the advertised keep-alive timeout when a header timeout is configured.
    $ curl -I --silent http://127.0.0.1/
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Date: Mon, 29 Dec 2025 22:10:58 GMT
    Content-Type: text/html
    Content-Length: 615
    Last-Modified: Tue, 11 Apr 2023 01:45:34 GMT
    Connection: keep-alive
    Keep-Alive: timeout=15
    ETag: "6434bbbe-267"
    Accept-Ranges: bytes

    If the Keep-Alive header is missing, set the second parameter in keepalive_timeout or rely on Waiting and connection-state monitoring instead.