Nginx as API Gateway: A Deep Dive into Performance Troubleshooting
The Scenario
At OpsGlobal, we often see clients running Nginx as an API gateway in front of their microservices. The typical setup involves Nginx terminating TLS, routing requests to upstream services, and handling rate limiting or caching. Recently, one of our clients reported that their API latency had spiked from 50 ms to over 2 seconds, and they were seeing intermittent 504 Gateway Timeouts during peak business hours.
Symptoms
- Increased response latency (p95 latency tripled)
- 504 errors from Nginx when upstreams fail to respond within default timeout
- Nginx worker connections hitting
worker_connectionslimit - Upstream service CPU usage is high but not saturated
- No obvious code changes or deployment events
Diagnosis
We started by checking the basics:
-
Check Nginx error logs
tail -f /var/log/nginx/error.logLook forupstream timed outorno live upstreams. -
Check access logs for slow requests We enabled
$request_timeand$upstream_response_timein the log format. Commands to configure and tail logs. -
Validate Nginx configuration:
nginx -tandnginx -Tto dump full configuration. -
Test connectivity and timing to upstream: Use
curl -wto measure DNS, connect, TLS, TTFB, and total time. -
Check connection pool stats:
ss -sto view socket statistics, alsonetstat -an | grep ESTABLISHED | wc -l. -
Load test with
wrkorabto reproduce under controlled load.
We discovered that the Nginx config had proxy_connect_timeout and proxy_read_timeout set to the defaults (60s), but the real issue was that upstream keepalive connections were not configured properly. The upstream block had no keepalive directive, causing a new TCP connection and TLS handshake for every request. Under high concurrency, this added massive overhead.
Commands Used
Here are the key commands we used during diagnosis and fix:
Check error log
tail -f /var/log/nginx/error.log
Check access log with timing
Access log format definition:
log_format timed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time';
Then tail it:
tail -f /var/log/nginx/access.log | awk '$10 > 2 {print}'
(assuming field 10 is request_time)
Validate config
nginx -t
nginx -T
Curl timing
curl -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://api.example.com/health
Socket statistics
ss -s
ss -tn state established '( dport = :443 or sport = :443 )' | wc -l
Load test with wrk
wrk -t8 -c200 -d30s https://api.example.com/health
Fix Implementation with Risk Controls
We made the following changes:
- Enable upstream keepalive:
upstream backend {
server api1:8080 weight=3;
server api2:8080 weight=2;
keepalive 32;
}
- Adjust Nginx HTTP settings:
http {
upstream backend {
server api1:8080;
keepalive 32;
}
server {
listen 443 ssl;
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
proxy_send_timeout 10s;
proxy_buffering off; # only if needed
}
}
}
proxy_http_version 1.1is required for keepalive to upstream.- Empty
Connectionheader. - Reduce timeouts to prevent prolonged hangs.
- Tune worker processes/connections:
- Set
worker_processes auto;to match CPU cores. - Increaseworker_connectionsto 4096 or higher. - Adjustkeepalive_timeoutfor client connections.
Before applying, we:
- Backed up original config: cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak
- Tested in a staging environment that mirrored production.
- Validated with nginx -t before reload.
We used nginx -s reload for a graceful reload, which forks new workers and drains old connections.
Rollback Strategy
In case the changes caused issues:
- Restore the backup:
cp /etc/nginx/nginx.conf.bak /etc/nginx/nginx.conf - Test:
nginx -t - Reload:
nginx -s reload
If the issue is critical, we can quickly switch to old config using a symlink if multiple versions are kept.
Verification and Results
After reload, we monitored error logs, access logs, and external metrics (e.g., Datadog).
tail -f /var/log/nginx/error.logshowed no more timeout errors.wrkbenchmark showed a 40% improvement in throughput and latency.- The
ss -ssocket statistics showed fewer new TCP connections because keepalives were reused. - Our client's p95 latency dropped back to ~80 ms.
When to Submit an OpsGlobal Ticket
This level of troubleshooting is often within the reach of an in-house DevOps team. However, if you see any of the following, it’s time to bring in OpsGlobal:
- You’re not familiar with Nginx internals or not comfortable editing production configs.
- The issue persists after basic tuning and might require kernel parameters (e.g.,
tcp_tw_reuse,somaxconn) or system-level tuning. - You need to scale the gateway horizontally and require expertise in load balancer design or service mesh integration.
- The symptoms point to an upstream that’s misbehaving, and you need help tracing or instrumenting your microservices.
OpsGlobal offers 24/7 support. We can take over incident response, perform in-depth performance audits, and implement robust production changes with proper rollback plans.
Use cases
Useful for teams handling Performance issues and needing a clear troubleshooting and delivery workflow.
Problem background
Learn how to diagnose and resolve performance bottlenecks in Nginx-based API gateways, with practical commands, risk controls, and rollback strategies.
Troubleshooting steps
Confirm impact and recent changes, collect logs, configuration and metrics, then apply fixes from low to high risk.
Command examples
Replace sample resource names with real values and store passwords, tokens and keys in environment variables.
Risks
Before production changes, confirm backups, access boundaries, change windows and rollback paths.
Rollback plan
Keep original configuration and release versions; roll back config, images or database changes if metrics degrade.
Deliverables
Root-cause notes, key commands, remediation steps, verification results and follow-up recommendations.
Need help with a similar technical issue?
If your servers, Kubernetes, Docker, CI/CD, databases or monitoring systems have similar issues, submit logs and config files for remote diagnosis.