
Introduction
Reverse proxies and ingress controllers sit directly in the path of user traffic, which makes them one of the most critical layers to load test. Whether you are running Nginx, HAProxy, Traefik, or Kubernetes ingress, these components terminate TLS, route requests, enforce security rules, apply rate limits, and protect your upstream services. If they become a bottleneck, your entire application can slow down or fail even when backend services are healthy.
Load testing reverse proxies and ingress controllers helps you validate far more than raw requests per second. It reveals how your edge layer behaves under concurrent connections, large headers, sticky sessions, WebSocket upgrades, burst traffic, and mixed workloads. It also helps uncover configuration issues such as low worker limits, connection pool exhaustion, aggressive timeouts, misconfigured keep-alive settings, or upstream retry storms.
In this guide, you’ll learn how to perform practical load testing, performance testing, and stress testing for reverse proxies using LoadForge. Because LoadForge uses Locust under the hood, every example is a realistic Python-based Locust script you can run and adapt. We’ll cover basic proxy validation, authenticated traffic through ingress, API routing patterns, large uploads, and observability tips for interpreting results. If you need distributed testing from global locations, real-time reporting, and cloud-based infrastructure, LoadForge makes it much easier to simulate real-world traffic against your edge layer.
Prerequisites
Before you start load testing your reverse proxy or ingress controller, make sure you have:
- A target environment running Nginx, HAProxy, Traefik, or a Kubernetes ingress controller
- A list of realistic routes exposed through the proxy, such as:
//healthz/api/v1/products/api/v1/cart/auth/login/uploads/avatar
- Valid test credentials if authentication is enforced
- A staging or pre-production environment that mirrors production routing, TLS, and upstream behavior
- Access to proxy metrics and logs, such as:
- Nginx access/error logs
- HAProxy stats page
- Traefik dashboard/Prometheus metrics
- Kubernetes ingress controller metrics
- Baseline expectations for:
- p50, p95, and p99 latency
- error rate thresholds
- throughput targets
- concurrent connection goals
- LoadForge account access if you want to run distributed load tests at scale
It is also helpful to know whether your proxy handles:
- TLS termination
- HTTP/2
- WebSocket upgrades
- sticky sessions
- path-based routing
- host-based routing
- request buffering
- rate limiting
- WAF rules
- upstream retries or circuit breaking
These features significantly affect performance testing results.
Understanding Reverse Proxies and Ingress Controllers Under Load
Reverse proxies and ingress controllers behave differently from application servers because they are traffic mediators. They do not just process requests; they manage connection lifecycles, buffer payloads, forward headers, and coordinate with multiple upstream services.
Common bottlenecks in reverse proxies
When load testing reverse proxies, pay attention to these common bottlenecks:
Connection limits
Nginx worker connections, HAProxy maxconn, and ingress controller pod limits can cap concurrency. Once those limits are hit, new clients may queue, slow down, or receive connection errors.
TLS overhead
TLS negotiation can consume significant CPU, especially during traffic spikes or when keep-alive is not used effectively. Stress testing with many new connections often reveals TLS termination bottlenecks.
Upstream saturation
The proxy may appear slow, but the real issue may be overloaded upstream services. Good load testing isolates whether latency is introduced at the edge layer or downstream.
Header and cookie size
Large JWTs, many cookies, or forwarded tracing headers can increase parsing overhead and sometimes exceed configured buffer sizes.
Request buffering and uploads
Reverse proxies often buffer request bodies. Large file uploads or JSON payloads can stress memory, disk I/O, and timeout settings.
Retry amplification
If the proxy retries failed upstream requests under load, it can unintentionally multiply traffic and worsen backend instability.
Rate limiting and security middleware
Ingress controllers frequently enforce auth checks, IP filtering, or rate limits. Under stress, these protections can become a source of latency or false positives.
Kubernetes ingress-specific issues
For ingress controllers, performance testing should also account for:
- pod autoscaling delays
- node-level network saturation
- service discovery churn
- frequent config reloads
- uneven traffic distribution across replicas
A good reverse proxy load test should include multiple request types, authenticated and unauthenticated traffic, realistic headers, and a mix of small and large payloads.
Writing Your First Load Test
Let’s start with a basic Locust script that validates a reverse proxy serving static content, health endpoints, and a routed API path. This is useful for Nginx, HAProxy, or Traefik sitting in front of multiple services.
from locust import HttpUser, task, between
class ReverseProxyBasicUser(HttpUser):
wait_time = between(1, 3)
common_headers = {
"User-Agent": "LoadForge-ReverseProxyTest/1.0",
"Accept": "text/html,application/json",
"X-Forwarded-Proto": "https"
}
@task(5)
def homepage(self):
self.client.get(
"/",
headers=self.common_headers,
name="GET /"
)
@task(3)
def health_check(self):
self.client.get(
"/healthz",
headers=self.common_headers,
name="GET /healthz"
)
@task(2)
def products_api(self):
self.client.get(
"/api/v1/products?category=networking&limit=20",
headers={**self.common_headers, "Accept": "application/json"},
name="GET /api/v1/products"
)What this test does
This script simulates three common request types:
- Browser traffic to
/ - Health monitoring traffic to
/healthz - API traffic routed through the reverse proxy to
/api/v1/products
This is a good first step in load testing because it helps establish:
- baseline edge latency
- whether route matching works under concurrency
- whether static and dynamic traffic behave differently
- whether your proxy adds measurable overhead
Why this matters for reverse proxies
A reverse proxy may respond quickly to /healthz but become slow on API routes that involve upstream connection reuse, header rewriting, or authentication middleware. Even this simple load testing script can reveal early signs of queueing or upstream routing problems.
When running this in LoadForge, you can scale the user count across multiple generators to simulate more realistic distributed traffic, especially if your ingress is exposed globally.
Advanced Load Testing Scenarios
Once the basics are working, you should move to more realistic reverse proxy performance testing scenarios. These examples simulate the traffic patterns that often expose bottlenecks in Nginx, HAProxy, Traefik, and Kubernetes ingress.
Advanced Scenario 1: Authenticated API Traffic Through Ingress
Many ingress setups sit in front of authenticated APIs. This means your reverse proxy must correctly forward authorization headers, cookies, and tracing information while maintaining low latency.
from locust import HttpUser, task, between
import json
class IngressAuthenticatedApiUser(HttpUser):
wait_time = between(1, 2)
token = None
def on_start(self):
login_payload = {
"email": "loadtest.user@example.com",
"password": "Str0ngTestPass!",
"tenant": "acme-staging"
}
response = self.client.post(
"/auth/login",
json=login_payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json",
"X-Forwarded-Proto": "https",
"X-Request-Source": "loadforge"
},
name="POST /auth/login"
)
if response.status_code == 200:
body = response.json()
self.token = body.get("access_token")
@task(4)
def get_user_profile(self):
if not self.token:
return
self.client.get(
"/api/v1/users/me",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/json",
"X-Forwarded-Proto": "https",
"X-Request-ID": "lf-profile-request"
},
name="GET /api/v1/users/me"
)
@task(3)
def get_orders(self):
if not self.token:
return
self.client.get(
"/api/v1/orders?status=open&page=1&page_size=25",
headers={
"Authorization": f"Bearer {self.token}",
"Accept": "application/json",
"X-Forwarded-Proto": "https"
},
name="GET /api/v1/orders"
)
@task(2)
def post_cart_update(self):
if not self.token:
return
payload = {
"cart_id": "cart-847201",
"items": [
{"sku": "switch-24p-poe", "quantity": 2},
{"sku": "cat6-cable-5m", "quantity": 5}
],
"currency": "USD"
}
self.client.post(
"/api/v1/cart/update",
data=json.dumps(payload),
headers={
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json",
"Accept": "application/json",
"X-Forwarded-Proto": "https"
},
name="POST /api/v1/cart/update"
)Why this test is useful
This scenario is ideal for testing:
- JWT header forwarding
- ingress auth middleware behavior
- upstream keep-alive reuse
- path-based routing for authenticated APIs
- latency impact of larger headers and cookies
If you see high p95 or p99 latency here but not on public endpoints, the proxy may be struggling with auth middleware, TLS, or upstream routing rules.
Advanced Scenario 2: Host-Based Routing Across Multiple Upstreams
Reverse proxies often route traffic based on hostname. For example:
shop.example.com→ e-commerce frontendapi.example.com→ backend APIadmin.example.com→ admin portal
This is especially common in Traefik and Kubernetes ingress.
from locust import HttpUser, task, between
class HostRoutingUser(HttpUser):
wait_time = between(1, 3)
@task(5)
def shop_frontend(self):
self.client.get(
"/",
headers={
"Host": "shop.example.com",
"Accept": "text/html",
"User-Agent": "LoadForge-ShopTraffic/1.0"
},
name="shop.example.com /"
)
@task(3)
def api_catalog(self):
self.client.get(
"/api/v1/catalog?region=us-east&sort=popular",
headers={
"Host": "api.example.com",
"Accept": "application/json",
"Authorization": "Bearer test-api-token"
},
name="api.example.com /api/v1/catalog"
)
@task(1)
def admin_dashboard(self):
self.client.get(
"/dashboard",
headers={
"Host": "admin.example.com",
"Accept": "text/html",
"Cookie": "session_id=staging-admin-session-123"
},
name="admin.example.com /dashboard"
)
@task(1)
def admin_metrics(self):
self.client.get(
"/internal/metrics",
headers={
"Host": "admin.example.com",
"Accept": "text/plain",
"X-Internal-Token": "internal-metrics-token"
},
name="admin.example.com /internal/metrics"
)What this reveals
This kind of load testing shows whether your reverse proxy can:
- match host rules efficiently
- route requests to the correct upstream pools
- handle mixed workloads across frontends, APIs, and internal tools
- avoid starvation where one busy route affects another
For ingress controllers, this can also reveal whether one backend service is monopolizing available connections or CPU.
Advanced Scenario 3: Large Uploads and Buffering Behavior
Large file uploads are a classic stress point for reverse proxies. Nginx client body buffering, ingress body size limits, and upstream timeout settings often fail here before normal API traffic does.
from locust import HttpUser, task, between
from io import BytesIO
import os
class FileUploadThroughProxyUser(HttpUser):
wait_time = between(2, 5)
auth_token = "Bearer upload-test-token"
@task
def upload_avatar(self):
file_content = BytesIO(os.urandom(512 * 1024)) # 512 KB
files = {
"file": ("avatar.png", file_content, "image/png")
}
data = {
"user_id": "user-10482",
"folder": "avatars",
"visibility": "private"
}
self.client.post(
"/uploads/avatar",
files=files,
data=data,
headers={
"Authorization": self.auth_token,
"X-Forwarded-Proto": "https"
},
name="POST /uploads/avatar"
)
@task(2)
def upload_document(self):
file_content = BytesIO(os.urandom(2 * 1024 * 1024)) # 2 MB
files = {
"file": ("network-diagram.pdf", file_content, "application/pdf")
}
data = {
"project_id": "proj-7781",
"document_type": "architecture",
"retain_days": "30"
}
self.client.post(
"/api/v1/documents/upload",
files=files,
data=data,
headers={
"Authorization": self.auth_token,
"X-Forwarded-Proto": "https",
"X-Upload-Source": "load-test"
},
name="POST /api/v1/documents/upload"
)Why upload testing matters
This scenario is useful for stress testing:
- body size limits
- request buffering
- disk-backed temp storage
- upstream read timeouts
- slow-client behavior
- memory pressure on ingress pods
If these requests fail with 413, 499, 502, 504, or timeout errors, the issue may be at the proxy layer rather than the application. Reverse proxy performance testing should always include at least one body-heavy scenario if your production traffic includes uploads.
Analyzing Your Results
After running your load testing scenarios, the next step is interpreting the data. LoadForge provides real-time reporting that makes it easier to see trends as the test runs, including response times, failures, throughput, and user concurrency.
Key metrics to watch
Response time percentiles
Average latency is not enough. Focus on:
- p50 for normal user experience
- p95 for degraded conditions
- p99 for edge-case slowness under stress
A reverse proxy may look healthy on average while p99 latency spikes due to connection queueing or TLS bottlenecks.
Error rates by endpoint
Break down failures by route:
/healthzfailing may indicate proxy instability/auth/loginfailing may indicate header forwarding or auth backend issues- upload endpoints failing may indicate body size or timeout limits
Requests per second
Throughput should rise steadily with concurrency until saturation. If it plateaus too early, your reverse proxy may be hitting worker, CPU, or upstream connection limits.
Failure patterns
Look for these common proxy-related status codes:
429— rate limiting triggered502— bad gateway, often upstream failure or invalid proxying503— service unavailable, capacity or routing issue504— gateway timeout, slow upstream or timeout misconfiguration
Latency divergence between routes
If static routes remain fast while API routes slow down, the issue may be upstream. If all routes slow down together, the reverse proxy itself may be saturated.
Correlate with infrastructure metrics
For deeper performance testing, compare LoadForge results with:
- CPU and memory on proxy nodes or pods
- active connections
- TLS handshake rates
- upstream response times
- Nginx worker utilization
- HAProxy queue depth
- Traefik router/service metrics
- Kubernetes pod restarts or throttling
This is where LoadForge’s distributed testing is especially useful. You can test from multiple locations to determine whether edge latency is regional, DNS-related, or caused by load balancer routing.
Performance Optimization Tips
Once your load testing identifies bottlenecks, these are common ways to improve reverse proxy performance.
Tune keep-alive settings
Reusing connections reduces TLS and TCP overhead. Make sure client and upstream keep-alive values are appropriate for your traffic profile.
Increase worker and connection limits
For Nginx and HAProxy, low defaults can cap concurrency. Tune them carefully based on available CPU, memory, and file descriptor limits.
Optimize TLS
Use modern ciphers, session resumption, and efficient certificate chains. TLS-heavy workloads often benefit immediately from better connection reuse.
Reduce unnecessary retries
Retries can amplify load during backend instability. Review retry policies so the proxy does not worsen incidents.
Review buffering behavior
For uploads or large payloads, validate body size limits, temp storage, and timeout settings. Disable or tune buffering only when appropriate.
Scale ingress controllers horizontally
In Kubernetes, ensure ingress controller replicas and resource requests are sufficient. Performance testing often reveals that autoscaling reacts too slowly to bursts.
Separate noisy routes
If admin traffic, uploads, and public API traffic share one ingress path, isolate them with separate routing policies or dedicated edge resources.
Cache where appropriate
For Nginx or Traefik setups serving cacheable content, edge caching can dramatically reduce upstream pressure.
Common Pitfalls to Avoid
Reverse proxy load testing is easy to get wrong if the test does not reflect real traffic.
Testing only the homepage
A reverse proxy may serve / perfectly but fail under authenticated API requests, uploads, or host-based routing. Always include realistic route diversity.
Ignoring headers and cookies
Large auth headers, forwarded headers, and cookies affect routing and parsing. If your test omits them, results may be misleading.
Not distinguishing proxy issues from upstream issues
If the backend is slow, the proxy will look slow too. Use health endpoints, direct upstream comparisons, and route segmentation to isolate the bottleneck.
Using unrealistic concurrency ramps
Sudden spikes can be useful for stress testing, but you should also run controlled ramps to observe saturation points and queueing behavior.
Forgetting TLS and DNS realism
If production traffic uses HTTPS and multiple hostnames, your load test should too. Simplified HTTP-only tests often miss real bottlenecks.
Overlooking ingress autoscaling delays
Kubernetes ingress may pass a short test but fail under sustained pressure because scaling lags behind traffic growth.
Running tests from only one region
If your users are global, test from multiple locations. LoadForge’s cloud-based infrastructure and global test locations help simulate realistic edge traffic patterns.
Not monitoring the proxy during the test
Load testing without logs and metrics makes root cause analysis much harder. Always collect proxy and upstream telemetry.
Conclusion
Load testing reverse proxies and ingress controllers is essential for reliable cloud infrastructure. Nginx, HAProxy, Traefik, and Kubernetes ingress layers all introduce their own performance characteristics, and even small misconfigurations can become major outages under load. By testing realistic traffic patterns, authenticated requests, host-based routing, and upload-heavy workloads, you can uncover bottlenecks before they affect production users.
With LoadForge, you can run scalable load testing, performance testing, and stress testing for your reverse proxy layer using Locust-based scripts, distributed generators, real-time reporting, and CI/CD integration. If you want to improve reliability at scale and validate your ingress before the next traffic spike, try LoadForge and start testing your edge infrastructure with confidence.
LoadForge Team
LoadForge is a load and performance testing platform built on Locust. Our team has been shipping load tests against production systems since 2018, and we write these guides from real customer engagements.
Related guides
Keep going with more guides from the same category.

AWS Lambda Load Testing Guide
Learn how to load test AWS Lambda functions with LoadForge to measure cold starts, concurrency, and serverless scaling.

Azure Load Testing Guide with LoadForge
Discover how to load test Azure-hosted apps and services with LoadForge for better scalability, reliability, and response times.

DigitalOcean Load Testing Guide
Load test DigitalOcean apps, droplets, and APIs with LoadForge to uncover limits and optimize performance at scale.