# Fix Nginx keepalive_timeout Too Low for Mobile Clients
Mobile users report that your website loads slowly on cellular connections, with each page taking 3-5 seconds even though server response times are under 100ms. Desktop users on the same network have no issues. The problem is likely keepalive_timeout.
Understanding Keepalive Connections
HTTP keepalive allows a single TCP connection to serve multiple HTTP requests. Without keepalive, every image, stylesheet, and script file requires a new TCP connection -- including the three-way handshake and TLS negotiation.
On mobile networks with high latency (200ms+ round-trip time), establishing a new TCP + TLS connection takes 600-900ms. Desktop connections on WiFi typically have 10-30ms latency, making the overhead barely noticeable.
The Problem
The default Nginx keepalive_timeout is 75 seconds. This seems generous, but some mobile carriers and intermediate proxies aggressively close idle connections after 30 seconds or less. If Nginx thinks the connection is alive but the carrier has already closed it, the next request fails and must be retried on a new connection.
Check your current settings:
nginx -T 2>/dev/null | grep keepaliveIf you see:
keepalive_timeout 10s;A 10-second timeout is too aggressive for mobile networks where connections can pause during network transitions (WiFi to cellular, cell tower handoffs).
Recommended Mobile-Friendly Configuration
```nginx http { keepalive_timeout 65s; keepalive_requests 1000; send_timeout 30s;
# TCP optimization for mobile tcp_nopush on; tcp_nodelay on; reset_timedout_connection on; } ```
The keepalive_requests 1000 allows each connection to serve up to 1000 requests before closing, reducing the frequency of connection establishment.
HTTP/2 Multiplexing
If you enable HTTP/2, multiple requests share a single connection, making keepalive even more important:
```nginx server { listen 443 ssl http2; server_name example.com;
# HTTP/2 settings http2_max_requests 1000; http2_recv_timeout 30s; http2_idle_timeout 30s; } ```
With HTTP/2, the client opens one connection and multiplexes all requests over it. A short keepalive timeout forces reconnection, losing the HTTP/2 benefit entirely.
Measuring the Impact
Test connection reuse with curl:
```bash # Verbose output shows connection reuse curl -v https://example.com/ 2>&1 | grep -E "Connected|Reusable|left intact"
# Connection: keep-alive in response headers curl -sI https://example.com/ | grep -i "keep-alive|connection" ```
Check your Nginx access log for connection patterns:
```nginx log_format conn_log '$remote_addr - [$time_local] $status ' '$request_time $connection $connection_requests';
access_log /var/log/nginx/conn-analysis.log conn_log; ```
The $connection variable shows the connection serial number, and $connection_requests shows how many requests that connection has served. If $connection_requests is always 1, keepalive is not working.
Mobile-Specific Timeout Tuning
For sites with significant mobile traffic, consider these adjustments:
```nginx http { # Longer keepalive for mobile tolerance keepalive_timeout 75s 20s;
# The second value (20s) is the "header_timeout" -- # how long to wait for the client to send the request header. # This prevents slow clients from holding connections forever.
# Aggressive timeout for actual data transfer send_timeout 10s;
# Reset connections that have been idle too long reset_timedout_connection on; } ```
The two-value syntax (75s 20s) sets a generous connection lifetime but a shorter header wait, balancing mobile tolerance with resource efficiency.
The Connection Cost on Mobile
To understand why this matters, measure the full connection cost:
curl -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" -o /dev/null -s https://example.com/On a mobile network, you will typically see: - DNS: 50-100ms - TCP handshake: 200-400ms - TLS negotiation: 200-400ms - TTFB: 50-100ms
Total new connection cost: 500-1000ms. With keepalive, subsequent requests skip TCP and TLS, saving 400-800ms per request. On a page with 50 resources, that is 20-40 seconds of savings.