ERR_CONNECTION_RESET means the connection was open, your request was on its way, and something sent a TCP reset to end it. Nothing about that is accidental - a reset is an active decision by some device or process in the path. That makes this error more findable than most, because whatever sent it usually wrote a log line at the same moment.
Where a reset sits relative to the other errors
The sequence matters, because it tells you which layer to look at.
- Refused - the connection never opened. Nothing was listening.
- Timed out - the connection never opened, and nothing said so.
- Reset - the connection opened, the request went out, and something killed it. The service is running.
- Empty response - the connection opened and closed cleanly with zero bytes back.
Reset and empty response are neighbours and often share a root cause, but a reset specifically means an RST packet, which is the signature of a firewall or security module rather than a process quietly exiting.
Step 1: reproduce it and capture where it stops
curl -sSv https://yourdomain.com/the-failing-path 2>&1 | tail -25Note whether the output dies during the TLS handshake or after the request was sent. Then bypass anything in front of the origin, so you know which leg is failing:
curl -sSv --resolve yourdomain.com:443:ORIGIN_IP https://yourdomain.com/the-failing-pathClean against the origin and reset through the public hostname puts the fault in the proxy or CDN layer. Failing against both puts it on the server.
Cause 1: a WAF rule firing mid-request
This is the most common cause on a managed or cPanel server. ModSecurity, Imunify360 and similar engines inspect the request as it arrives, and some configurations drop the connection outright when a rule matches instead of returning a clean 403. The visitor gets a reset with no explanation.
grep -i "$(date +%d/%b/%Y)" /usr/local/apache/logs/modsec_audit.log | tail -100
# cPanel: WHM → ModSecurity Tools → HitsFind the rule ID that fired on the exact request that failed, then whitelist that rule for that path. Disabling the engine entirely to fix one false positive is the wrong trade - it removes protection from every other request on the server and leaves the bad rule in place for next time.
The tell for this cause is that it is reproducible on one page and never on others: checkout, a file upload, a search with unusual characters, an API endpoint receiving JSON.
Cause 2: an IP ban inserted mid-session
CSF and Fail2Ban do not only block new connections. When a rule fires, the firewall rule they insert can reset connections that are already open - so a visitor who trips a rate limit sees the current page die, not just the next one.
csf -g VISITOR_IP
grep VISITOR_IP /var/log/fail2ban.log
fail2ban-client statusShared office addresses, monitoring services and API integrations get caught by these rules regularly. If the address is legitimate, whitelist it rather than raising the threshold for everyone.
Cause 3: a Cloudflare SSL mode that the origin cannot satisfy
If Cloudflare is set to Full or Full (strict) and the origin's certificate is self-signed, expired, or missing, Cloudflare can reset the connection instead of showing a clean error. This typically appears right after enabling the proxy, or silently months later when a certificate lapses unnoticed.
curl -vI https://ORIGIN_IP -H "Host: yourdomain.com" -k
openssl s_client -connect ORIGIN_IP:443 -servername yourdomain.com </dev/null | head -20Fix the certificate on the origin first, then confirm the SSL mode matches what the origin can actually serve. Setting the mode down to Flexible to make the error go away leaves the traffic between Cloudflare and your server unencrypted, which is a worse problem than the one you started with.
Cause 4: the backend died mid-response
A PHP worker that segfaults or is killed by the kernel while output is already in flight leaves the connection to be torn down rather than closed properly.
tail -50 /var/log/php*-fpm.log
dmesg -T | grep -i 'killed process' | tail -20An OOM kill naming php-fpm around the failure time is conclusive. The fix is usually the script rather than the limit: something is loading far more into memory than it needs to, and raising memory_limit only moves the threshold.
Cause 5: it is not your server at all
Devices between the visitor and you reset connections too. HTTPS-inspecting antivirus, corporate proxies, and some ISP-level filtering all do it, and none of them leaves a trace on your side.
The diagnostic is the same one that works for most of this family: ask the affected person to load the page on a phone with Wi-Fi off. If it works there, nothing on your server needs changing, and the answer is on their network.
Matching the log to the failure
The efficient way to solve a reset is to have the logs open while you trigger it, rather than reading them afterwards and guessing at timestamps:
tail -f /var/log/nginx/error.log \
/var/log/php8.2-fpm.log \
/usr/local/apache/logs/modsec_audit.logLoad the failing page once. Whatever is written in that second is the cause, and if nothing is written at all, the connection is being killed before it reaches your stack - which puts you back at Cloudflare, the firewall, or the visitor's own network.