How to configure Nginx web server for high load?
We provide turnkey web server and proxy configuration services: Nginx, HAProxy, Traefik, Envoy, Istio, Linkerd. Our experience: 7+ years in DevOps and 50+ projects on this topic.
A recent case: Nginx serves static files in 2 ms, but the first byte from upstream arrives in 800 ms — yet the median upstream_response_time in Grafana shows 1.2 seconds. The problem is not Nginx. The problem is that upstream keepalive is not configured. Each request to the backend creates a new TCP connection. At 500 rps, that's 500 handshakes per second. Typical savings from proper keepalive are up to 30% response time and reduced CPU load, which for a cluster of 10 instances at $100/month each translates into $300/month saved. Wikipedia confirms that without the pair proxy_http_version 1.1 + Connection: "", keepalive does not work.
How to avoid mistakes when configuring Nginx under load?
worker_processes auto and worker_connections 1024 are default settings that often remain untouched for years. On a server with 8 cores and 10 Gbps network, this caps concurrent connections at around 8000. During spike loads, file descriptors run out before CPU.
Critical parameters for production:
worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 16384; use epoll; multi_accept on; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; keepalive_requests 1000; upstream backend { server 10.0.1.10:8080; server 10.0.1.11:8080; keepalive 64; # pool of keepalive connections to upstream keepalive_requests 100; keepalive_timeout 60s; } server { location /api/ { proxy_pass http://backend; proxy_http_version 1.1; # mandatory for keepalive proxy_set_header Connection ""; # removes "close" from header proxy_connect_timeout 5s; proxy_read_timeout 60s; proxy_send_timeout 60s; } } } Without proxy_http_version 1.1 + proxy_set_header Connection "", keepalive to upstream does not work—Nginx still closes the connection after each request.
worker_rlimit_nofile must match the system ulimit -n. If /etc/security/limits.conf has nginx soft nofile 1024, all configuration settings are useless.
Nginx as TLS termination with OCSP stapling
TLS handshake with OCSP validation takes 50–200 ms per new connection. OCSP Stapling caches certificate status on the server side, reducing latency. Recommendation from Mozilla SSL Configuration Generator:
ssl_certificate /etc/ssl/certs/example.com.crt; ssl_certificate_key /etc/ssl/private/example.com.key; ssl_trusted_certificate /etc/ssl/certs/ca-bundle.crt; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; ssl_session_cache shared:SSL:50m; ssl_session_timeout 1d; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 1.1.1.1 8.8.8.8 valid=300s; resolver_timeout 5s; ssl_session_tickets off — forward secrecy. Session tickets allow resuming sessions without a new handshake, but if the ticket key is compromised, all sessions are compromised.
When is HAProxy more beneficial than Nginx?
HAProxy specializes in L4 and L7 load balancing, backend health monitoring, and fine-grained traffic control. For TCP load balancing (databases, queues), HAProxy is more convenient.
Specific use case: PostgreSQL load balancing with read/write traffic separation on different ports:
frontend postgres_write bind *:5432 mode tcp default_backend pg_primary frontend postgres_read bind *:5433 mode tcp default_backend pg_replicas backend pg_primary mode tcp option tcp-check server pg1 10.0.1.1:5432 check port 8008 inter 2s # Patroni REST API healthcheck server pg2 10.0.1.2:5432 check port 8008 inter 2s backup backend pg_replicas mode tcp balance leastconn option tcp-check server pg2 10.0.1.2:5432 check port 8008 inter 2s server pg3 10.0.1.3:5432 check port 8008 inter 2s Patroni exposes a REST API on port 8008, returning HTTP 200 for primary and 503 for replica. HAProxy checks this endpoint—after a failover, write traffic is automatically redirected to the new primary.
Traefik: dynamic configuration for Kubernetes
Traefik is a cloud-native reverse proxy with auto-discovery from K8s API, Consul, Docker. No need to reload when adding new services—configuration is dynamically updated.
Middleware for rate limiting and circuit breaker:
apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: rate-limit spec: rateLimit: average: 100 burst: 50 period: 1s sourceCriterion: ipStrategy: depth: 2 # X-Forwarded-For second from end (behind load balancer) apiVersion: traefik.containo.us/v1alpha1 kind: Middleware metadata: name: circuit-breaker spec: circuitBreaker: expression: ResponseCodeRatio(500, 600, 0, 600) > 0.25 checkPeriod: 10s fallbackDuration: 30s recoveryDuration: 10s Circuit breaker triggers when >25% of responses are 5xx in the last 10 seconds. Protection against cascading failures: Traefik returns 503 instead of queue buildup.
Why can Istio be overkill?
Istio is the most feature-rich and the heaviest service mesh. Envoy sidecar is injected into each Pod via MutatingAdmissionWebhook. Control plane (istiod) manages Envoy configuration through xDS API: EDS, CDS, RDS, LDS.
In practice, this provides: mTLS between all services automatically, circuit breaking at each upstream level, distributed tracing through automatic B3/W3C header injection, traffic shifting for canary deployments.
Canary deployment via VirtualService:
apiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: api-service spec: http: - match: - headers: x-canary: exact: "true" route: - destination: host: api-service subset: v2 weight: 100 - route: - destination: host: api-service subset: v1 weight: 95 - destination: host: api-service subset: v2 weight: 5 5% of traffic goes to v2 for all users. Header x-canary: true for testers—they always hit v2.
The real problem with Istio is overhead: each sidecar consumes ~50-100 MB RAM and adds 1-5 ms latency per hop. On a cluster with 200 pods, that's 10-20 GB of additional RAM. This can lead to significant cloud instance costs. Istio Ambient Mesh (ztunnel + waypoint proxy) addresses this by moving the proxy to the node level, but as of now, it is still beta quality.
Linkerd as an alternative
Linkerd is written in Rust (data plane proxy linkerd2-proxy), consuming 10 times less resources than Envoy. No Lua extensions, WebAssembly plugins—but it's stable. If you need mTLS + basic traffic splitting + golden metrics (success rate, latency, RPS) without complex routing rules, Linkerd is easier to operate.
Envoy as a standalone
Envoy without Istio is an excellent edge proxy or sidecar for a specific service. Its xDS API is supported by Control Planes from Contour, Gloo Edge, Ambassador. For those who want L7 functionality of Istio without a full service mesh, Contour (Envoy-based Ingress Controller) is a good entry point.
What is included in web server and proxy configuration services
| Deliverable | Description |
|---|---|
| Current config audit | Check kernel parameters, worker, upstream, TLS |
| Performance optimization | keepalive, buffers, timeouts, epoll, worker_rlimit_nofile |
| SSL/TLS with OCSP stapling and HSTS | Configure cipher suites, session cache, OCSP stapling |
| Load balancing and health checks | Round-robin, least_conn, IP hash, tcp-check |
| Integration with observability | Prometheus metrics, logging, Grafana dashboards |
| Documentation and runbook | Configuration, update procedure, troubleshooting |
| Team training (optional) | How to make changes, how to debug |
| Post-launch support | 2 weeks of monitoring and adjustments |
| SLA guarantee | 99.9% uptime for configured services |
Comparison of approaches: when to choose what
| Criteria | Nginx | HAProxy | Traefik | Envoy (with Contour) |
|---|---|---|---|---|
| L4 load balancing | + (stream) | +++ | – (L7 only) | + (via listener) |
| L7 load balancing | +++ | ++ | +++ | +++ |
| Dynamic configuration | – (reload) | – (reload) | +++ (K8s API) | ++ (xDS) |
| Service mesh | – | – | – | +++ (with Istio) |
| Resource consumption | low | low | medium | high (sidecar) |
| Learning curve | low | low | medium | high |
Envoy handles L7 routing 3 times faster than Nginx in benchmark tests, but at a higher resource cost.
How we configure web servers: step-by-step process
- Current config audit — collect logs, metrics, configs. Identify bottlenecks (keepalive, buffers, TLS).
- Design optimal architecture — choose the right tool for the task: Nginx for static, HAProxy for TCP, Traefik for K8s.
- Implementation — apply IaC (Ansible, Terraform), configure kernel parameters, worker, upstream, TLS.
- Load testing — use wrk2, vegeta, k6. Check p99 latency, error rate.
- Deploy and documentation — hand over runbook, train the team. Provide 2 weeks of support.
Work stages and timelines
| Task | Timeline |
|---|---|
| Nginx performance tuning (keepalive, TLS, buffers) | 1–2 days |
| HAProxy for TCP load balancing (PostgreSQL, Redis) | 2–3 days |
| Traefik in K8s + middleware (rate limit, circuit breaker) | 3–5 days |
| Istio service mesh installation + basic configuration | 1–2 weeks |
| Istio mTLS + canary deployments + observability | 2–4 weeks |
Pricing is calculated individually after infrastructure audit. Contact us for a consultation — we will evaluate your project and propose the optimal solution. Guarantee: all configurations are covered by an SLA and supported with a runbook. Request a consultation to discuss your scenario.
Our experience and approach
We have been doing DevOps and infrastructure configuration for 7+ years. We have completed 50+ projects — from startups to enterprises with hundreds of Pods. We use best practices: GitOps, Infrastructure as Code, automated configuration testing. Our engineers hold certifications in Kubernetes and AWS. We work turnkey — from audit to delivering a ready system with guaranteed performance. Get a consultation — we will tell you how to solve your task.







