Diagnosing Gatling Connection Errors During Load Test Ramp-Up
When Gatling KO% spikes during ramp-up with connect timeouts, connection refused, or too many open files while the target looks healthy, the issue is usually injector socket exhaustion or target accept-queue overflow.
23 Feb 2026, 16:18 UTC

Your Gatling HTML report shows KO% climbing as the injection profile ramps up, dominated by i.n.channel.ConnectTimeoutException, java.net.ConnectException: Connection refused, or too many open files. Meanwhile the target's own dashboards look calm. The useful takeaway: separate injector-side socket exhaustion from target-side accept-queue overflow before changing load or code, because the two require opposite fixes.
KO% is Gatling's term for the percentage of requests that failed, or were \"knocked out.\" A high KO rate during ramp-up—but not during a low-rate smoke test—usually means the injector or the network path is saturating before the application logic is stressed.
Recognizable condition
- KO% rises during ramp-up rather than at steady state.
- Errors group into connection-level failures: connect timeouts, connection refused/reset, or injector file-descriptor exhaustion.
- The target's CPU and memory metrics appear normal, even though its connection counts or accept queues may be saturated.
Cause and diagnostic table
| Symptom | Likely cause | Where to confirm |
|---|---|---|
| Connection refused | Target listen backlog is full, or the process is down | Target socket state and process logs |
| ConnectTimeoutException | Packet drops, firewall rules, or SYN-backlog exhaustion (the queue of half-open TCP connections) | Network path tools and target SYN queue statistics |
| Connection reset | Target or middlebox (firewall, load balancer, or proxy) closing idle keep-alive sockets | Target idle-timeout configuration and middlebox logs |
| too many open files | Injector file-descriptor limit reached | ulimit -n on the injector and simulation.log |
Ordered checks
- Run a one-user smoke test. Against the same
baseUrl, execute a scenario with one virtual user, one request, and no pauses. If this fails, stop; the problem is reachability, TLS, or the base URL, not load. - Read the raw logs. The HTML report aggregates errors, but
simulation.logand console output carry the stack traces needed to separate client-side from server-side causes. - Correlate KO onset with the injection profile. Compare the time KO% starts climbing with the ramp-up step and the achieved request rate. Does it track the number of injected users or the requests per second?
- Check injector limits. Run
ulimit -nin the same shell that launches Gatling (Linux/macOS). Reading the soft limit needs no special permissions; raising the hard limit requires root or a service-manager override such as systemd'sLimitNOFILE. Watch CPU and memory during the run. If the file-descriptor ceiling is low—for example, 1024—the injector can exhaust descriptors before the target is stressed. - Check target capacity. On the target host, run
ss -s(requires root orCAP_NET_ADMINfor full detail) to summarize socket counts. Look for established connections approaching the injector's user count. Usenetstat -s | grep -i listento look for counters indicating listen-queue overflow; those counters mean the accept queue is full and the target is dropping connection attempts.
Fixes tied to findings
If the smoke test fails
Fix DNS, TLS trust, firewall rules, or the baseUrl first. Do not tune load parameters until a single request succeeds.
If injector limits bind
Raise the file-descriptor limit for the Gatling process, for example by setting LimitNOFILE=65535 in the systemd unit or running ulimit -n 65535 before launch. If you run multiple injectors, reduce the per-injector user count instead of pushing one machine harder. Risk: raising the limit without confirming target capacity can turn a client bottleneck into a target outage.
If socket count binds
Slow the ramp-up so the target has time to accept connections, reduce peak users, or enable shared connections with a hard cap. A configuration snippet illustrating the decision looks like this:
val httpProtocol = http
.baseUrl(\"https://target.example.com\") // replace with your base URL
.shareConnections
.maxConnectionsPerHost(100) // start low; raise only if the target accepts more
By default Gatling gives each virtual user its own connection pool. With shareConnections disabled and a large injected-user count, the injector opens more sockets than the OS or target accepts. Enabling shareConnections changes connection and cookie semantics and can mask per-user session bugs, so re-validate functional correctness afterward. Confirm the exact property names and defaults in your Gatling version before applying them.
If the target closes idle sockets
Review your Gatling version's HTTP client idle-timeout settings and align them with the target's keep-alive timeout. If the target reaps idle connections faster than the injector reuses them, connection-reset errors will appear even at moderate load.
When to escalate
- Escalate to target and network owners when the single-user smoke test also fails, when errors persist after injector limits are raised and ramp-up is slowed, or when the target reports accept-queue overflow.
- Escalate to the Gatling maintainers or community when a trivial scenario reproduces the failure against a known-good target, which suggests an injector-side defect rather than a capacity issue.
Limitations and verification
Do not tune from the HTML report alone; simulation.log and console output carry the stack traces needed to separate client-side from server-side causes. Gatling defaults such as shareConnections, maxConnections, and HTTP/2 enablement are version-sensitive; confirm them in the exact version you run rather than trusting remembered values. Change one variable per run.
To verify a fix, reproduce the failure with the minimal scenario first. After each change, compare KO% and the error-group breakdown between runs to confirm the targeted error class disappeared. On the injector, record ulimit -n and watch CPU and memory; on the target, watch established-connection counts and accept-queue drops. Because configuration keys and defaults vary across Gatling releases, treat the snippets above as illustrative and verify them against your version's documentation before production use.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.