ERR_EMPTY_RESPONSE (server closed the connection without sending data)
The server accepted the TCP connection then closed the socket before sending a single byte of HTTP response.
What you see
This page isn't working [domain] didn't send any data. ERR_EMPTY_RESPONSE
What’s actually happening
Chrome shows a bare "didn't send any data" page. The TCP handshake completed, the browser sent its request, and then the connection was torn down before any status line or headers came back. curl -v makes it obvious: you'll see "* Connection #0 to host ... left intact" or "Empty reply from server (52)" right after the request, with no "< HTTP/1.1" line at all. It often hits intermittently under load, or on one specific heavy URL, while the rest of the site stays up. Firefox words the same condition as "The connection was reset."
Common causes
- The app worker handling the request crashed mid-response — a PHP-FPM child segfaulting, an OOM kill, or a fatal that killed the process before output flushed.
- The kernel OOM killer reaped the process: check dmesg or /var/log/syslog for "Out of memory: Killed process" naming php-fpm, node, or the app.
- A reverse proxy (nginx, HAProxy, a load balancer) closed the downstream socket after its own upstream timed out or returned garbage.
- A protocol mismatch — something speaking plain HTTP to a TLS port, or HTTP/2 negotiated then mishandled, so the server drops the connection without a valid response.
- An infinite redirect or buffer overflow in the app that aborts the worker, or a firewall/IPS silently RSTing connections it flags.
How to fix it
- Reproduce with curl -v and confirm there's no response lineRun curl -v https://site/slow-page from the server itself and from outside. "Empty reply from server" with no "< HTTP/..." confirms the socket closed pre-headers. Hit it 10-20 times (for i in $(seq 20); do curl -s -o /dev/null -w "%{http_code}\n" URL; done) — a mix of 200s and 000s points to a worker crashing under load, not a config error.
- Check for OOM kills and worker crashesRun dmesg -T | grep -i "killed process" and grep oom /var/log/syslog. For PHP-FPM, look in /var/log/php*-fpm.log for "child %d exited on signal 11 (SIGSEGV)" or "signal 9 (SIGKILL)". A SIGSEGV is a crashing extension (often opcache, a PECL module, or a buggy version mismatch); a SIGKILL is almost always the OOM killer.
- Raise limits or fix the leak behind the crashIf it's OOM, either lower pm.max_children so total worker memory fits RAM, or add swap, or move to a box with more memory. memory_limit in PHP throws a catchable fatal (that's ERR_EMPTY_RESPONSE only if it kills mid-flush) — the kernel OOM kill is the harder one. For a segfault, disable extensions one at a time (start with opcache.enable=0) to find the offender.
- Check the proxy layer if there's one in frontBehind nginx, an upstream that dies gives you 502 in the nginx log but the browser can still see ERR_EMPTY_RESPONSE if proxy_buffering passed partial data first. Read /var/log/nginx/error.log for "upstream prematurely closed connection" or "recv() failed". Match the nginx error timestamp to the app crash to prove they're the same event.
- Rule out a protocol or firewall mismatchIf it's every request (not load-dependent), test http:// vs https:// explicitly and try curl --http1.1 to bypass HTTP/2 negotiation. A server set to TLS-only that gets a cleartext request on :443, or an mod_security/IPS rule issuing TCP resets, both produce empty replies — check the WAF/IPS logs and the listen directives.
Stop it recurring
Size pm.max_children (or your worker count) so peak concurrent workers fit in RAM with headroom, and alert on OOM-killer lines in syslog before users hit them.