Short answer: ERR_CONNECTION_RESET means the connection to the site was opened and then killed by a TCP reset (RST) packet, sent by the server or by something between you and it. If every site fails, the cause is on your side: a VPN, proxy, antivirus or network filter. If one site fails from every network, its server or a firewall in front of it cuts the connection.
One request from a terminal shows what the browser hides:
curl -sSv -o /dev/null https://example.com/curl.exe -sSv -o NUL https://example.com/Replace example.com with the failing site. If the request lines (> GET) are followed by Recv failure: Connection reset by peer (Windows: Connection was reset), the server or a device in front of it accepted the request and reset it. The same line right after Client hello points at TLS, cause 5. If curl works while the browser is reset, look at your own machine: a proxy setting, an extension, or antivirus that only filters browsers.
Which message do you see?
| Where | Message | What happened |
|---|---|---|
| Chrome, Edge | This site can’t be reached · The connection was reset. · ERR_CONNECTION_RESET | An RST arrived after the connection was open (Chromium net error -101) |
| Firefox | The connection was reset | The same event |
| curl, HTTP | curl: (56) Recv failure: Connection reset by peer | Reset after the request was sent |
| curl 7.88, HTTPS | curl: (35) Recv failure: Connection reset by peer | Reset during the TLS handshake |
| Python | ConnectionResetError: [Errno 104] Connection reset by peer | The same; 104 is ECONNRESET on Linux |
| Node.js | Error: read ECONNRESET | The same. socket hang up means the server closed cleanly without answering |
| nginx error.log | recv() failed (104: Connection reset by peer) while reading response header from upstream | Your app reset nginx; the visitor gets a 502, not a reset |
| ssh | kex_exchange_identification: read: Connection reset by peer | The SSH variant, with its own causes (fail2ban, MaxStartups) |
The Chrome, curl, Python and Node lines are real output (Chrome 153, Debian 12). Three neighbours get mixed up with it. ERR_CONNECTION_REFUSED is the answer to the very first packet: a reset because nothing listens, or a firewall’s reject. ERR_CONNECTION_TIMED_OUT means no answer at all. ERR_EMPTY_RESPONSE (curl: Empty reply from server) means the server closed the connection cleanly without sending anything.
For the SSH message, read SSH connection reset by peer instead.
Is it every site, or only one?
| Pattern | The reset comes from | Go to |
|---|---|---|
| Every site, on one device | That device: VPN, proxy, antivirus, broken network stack | Your computer |
| Every site, every device on one network | The router, the ISP or a network filter | Restart the router; test over mobile data |
| One site, from every network and device | The server or a firewall in front of it | Site owners |
| One site, only from one network | Something on that path: an ISP or office filter, MTU | Your computer, MTU |
Fixes on your own computer
- Open the site in a private window. That switches off extensions, including proxy and VPN add-ons. Clearing cache and cookies rarely helps: a reset happens below HTTP, where neither is involved.
- Turn off the VPN and any proxy. On Windows the proxy is under Settings → Network & internet → Proxy, on a Mac under System Settings → Network → your connection → Details → Proxies.
- Pause antivirus web protection. Products that scan HTTPS open every connection themselves and reset what they cannot parse. If the site then loads, add an exception and switch the shield back on.
- Try another network, such as your phone’s hotspot. If that works, your ISP, school or office filter resets the connection, usually after reading the hostname from the TLS handshake (SNI).
- An old firewall and a new browser. Since Chrome and Edge 124 (April 2024) the TLS ClientHello carries a post-quantum key share and no longer fits into one packet. Firewalls and TLS-inspecting proxies with outdated firmware reset such connections. Chrome’s temporary policy to switch it off ended with Chrome 146, so the fix is a firmware update of that device.
If every site fails on one Windows PC, reset its network stack in an administrator terminal, then restart:
netsh winsock reset
netsh int ip reset
ipconfig /flushdnsTo test the MTU, send a full 1500-byte packet that may not be fragmented (1472 bytes of data plus 28 of headers). If 1472 fails while 1400 gets replies, a link on the way carries less. Many hosts drop pings entirely, so replace TARGET with an address that answers small ones:
ping -f -l 1472 TARGET
ping -f -l 1400 TARGETping -c 3 -M do -s 1472 TARGET
ping -c 3 -M do -s 1400 TARGETIf only your connection is affected (a VPN, PPPoE or a mobile router), find the largest size that still gets replies, add 28, and set that as the adapter’s MTU. Site owners fix it for everyone with MSS clamping.
netsh interface ipv4 show subinterfaces
netsh interface ipv4 set subinterface "INTERFACE" mtu=NEW_MTU store=persistentSite owners: find out who sends the reset
Reproduce the reset with curl while the server captures resets on port 443. VISITOR_IP is the address curl runs from; drop the host part to see all clients:
sudo tcpdump -ni any 'tcp port 443 and host VISITOR_IP and tcp[tcpflags] & tcp-rst != 0'Without the flag filter you see the whole conversation; here a test server reads the request, then resets it:
IP 127.0.0.1.34914 > 127.0.0.1.47101: Flags [S], seq 788105413, win 65495, length 0
IP 127.0.0.1.47101 > 127.0.0.1.34914: Flags [S.], seq 3469401065, ack 788105414, win 65483, length 0
IP 127.0.0.1.34914 > 127.0.0.1.47101: Flags [.], ack 1, win 512, length 0
IP 127.0.0.1.34914 > 127.0.0.1.47101: Flags [P.], seq 1:80, ack 1, win 512, length 79
IP 127.0.0.1.47101 > 127.0.0.1.34914: Flags [.], ack 80, win 511, length 0
IP 127.0.0.1.47101 > 127.0.0.1.34914: Flags [R.], seq 1, ack 80, win 512, length 0Flags [R.]leaving your server: your server sent it. Causes 1 to 4 below.- The client is reset, but the server capture shows no RST: a device in between forged it, such as a filter on the visitor’s network, an IPS, or a DDoS filter in front of your server. In a capture on the client (Wireshark, or
tcpdump -v), compare thettlof the RST with that of your server’s SYN-ACK: a forged reset started at another hop and usually differs.
Our HTTP header checker requests the URL from Amsterdam. If it gets a normal answer while one visitor keeps getting resets, the reset is specific to that visitor: their path, or a rule matching their address (cause 3).
Why visitors rarely see your app’s reset
A browser only sees resets from the machine it talks to; anything further back turns into an HTTP error. A browser reset on a site behind nginx therefore came from nginx or from something in front of it:
| Setup | When the app resets the connection | The visitor sees |
|---|---|---|
| App on a public port (Node, Gunicorn, a Go binary) | The browser gets the RST | ERR_CONNECTION_RESET |
| nginx, Apache or Caddy in front | The proxy logs recv() failed (104: …) or upstream prematurely closed connection | 502 Bad Gateway |
| Cloudflare proxy in front | Cloudflare gets the RST or the 502 | Error 520 (or the 502) |
Cause 1: the process holding the connection dies or stops reading
When a process exits, the kernel closes its sockets, and a socket with unread request data is closed with a reset. That covers an app killed by the OOM killer, a crash, a restart during a deploy, and an app server that rejects an upload without reading it. Check first:
# killed by the OOM killer, or crashed?
sudo journalctl -k --since "1 hour ago" | grep -iE 'killed process|segfault'
# restarts and errors of the app's own service
systemctl status APP_SERVICE --no-pager
sudo journalctl -u APP_SERVICE --since "1 hour ago" --no-pager | tail -n 50If only large uploads are reset, a size limit refuses the upload and closes while the browser is still sending. Tomcat reads at most 2 MiB of a rejected upload (maxSwallowSize); beyond that, its docs say the client will hardly see the response. nginx, with lingering_close on by default, reads and discards the rest for up to 30 seconds, so the browser gets the 413. Put the limit into nginx (client_max_body_size).
With Docker, curl on the host can print the reset when the app in the container listens only on 127.0.0.1: docker-proxy accepts and has nowhere to forward. Make the app listen on 0.0.0.0 inside the container. From outside, the same mistake usually shows as connection refused.
Cause 2: nginx resets connections on purpose
return 444 closes a connection without a response. By default that is a clean close and the browser shows ERR_EMPTY_RESPONSE. With reset_timedout_connection on, nginx sends an RST instead, for 444 and for requests that hit client_body_timeout or send_timeout (60 seconds without progress by default; idle keep-alive connections still close cleanly). A slow upload or a stalled download on a weak mobile link then ends in ERR_CONNECTION_RESET. Find the directives in the loaded configuration:
sudo nginx -T 2>/dev/null | grep -nE 'return 444|reset_timedout_connection|lingering_close|client_max_body_size'A 444 in a catch-all default_server for unknown hostnames is fine; one that real visitors hit is your reset. Never set lingering_close off, which nginx’s documentation says breaks the protocol.
Cause 3: firewall rules, fail2ban and rate limits
What the visitor sees depends on when the rule matches:
- Rejecting new connections produces refused, not reset.
ufw rejectturns into--reject-with tcp-resetfor TCP, but ufw lets established traffic through before your rules, so only the first packet is answered. - fail2ban’s stock iptables action rejects with
icmp-port-unreachable(the nftables action withreject), and its chain sits at the top ofINPUT, so a ban also hits open connections: the page stalls, new connections are refused. A reset only appears if someone setblocktypetoREJECT --reject-with tcp-reset. - Rules that match open connections are the real source: connection or rate limits written with
--reject-with tcp-reset, an IPS or WAF in front of the server, a DDoS filter upstream. nginx’slimit_reqandlimit_connanswer with an HTTP status (503 by default), not a reset.
# Is this visitor banned, and in which jail?
sudo fail2ban-client banned VISITOR_IP
# Rules that send resets
sudo iptables -S | grep -i reset
sudo nft list ruleset | grep -iE 'reset|reject'
# Lift a ban
sudo fail2ban-client unban VISITOR_IPThe visitor can read their address on our what is my IP page.
Cause 4: the kernel runs out of socket memory
Under heavy load the kernel resets connections it can no longer afford: orphaned sockets beyond tcp_max_orphans, or TCP memory above tcp_mem. It logs that, and its counters show which reset is rising:
sudo dmesg -T | grep -E 'TCP: (too many orphaned sockets|out of memory)'
# run twice, a minute apart: the second run shows only what changed
nstat TcpOutRsts TcpExtTCPAbortOnClose TcpExtTCPAbortOnData TcpExtTCPAbortOnMemory TcpExtListenOverflowsTcpExtTCPAbortOnClose: an app closed a socket with unread data, cause 1.TcpExtTCPAbortOnData: a deliberate reset withSO_LINGER0, such as nginx’s 444 withreset_timedout_connection.TcpExtTCPAbortOnMemory: this cause. Find what holds so many connections before raising limits.TcpOutRstsalso counts answers to scanners on closed ports, so it always grows.TcpExtListenOverflows(a full accept queue) drops connections instead of resetting them, unlessnet.ipv4.tcp_abort_on_overflowis 1; the default is 0.
Cause 5: resets during the TLS handshake
Real curl output, shortened, from a server that resets as soon as the ClientHello arrives:
* Connected to 127.0.0.1 (127.0.0.1) port 47101 (#0)
* ALPN: offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
* OpenSSL SSL_connect: Connection reset by peer in connection to 127.0.0.1:47101
curl: (35) Recv failure: Connection reset by peernginx and Apache answer TLS problems with an alert, which browsers report as ERR_SSL_PROTOCOL_ERROR or a similar code, not with a reset. A reset here comes from something else on the port (a TCP load balancer, a port forward, a VPN sharing 443) or from a device that reads the ClientHello: an SNI filter, a TLS-inspecting firewall, or one that chokes on the large post-quantum ClientHello. curl on Debian 12 (OpenSSL 3.0) sends a small ClientHello and can pass where Chrome is reset; OpenSSL 3.5 on Debian 13 offers the X25519MLKEM768 key share by default, so compare both:
openssl s_client -connect example.com:443 -servername example.com -groups X25519MLKEM768 -brief </dev/null
openssl s_client -connect example.com:443 -servername example.com -groups X25519 -brief </dev/nullIf the first is reset and the second connects, a device on the path cannot handle large ClientHellos. Update or replace it; no setting on the web server fixes it.
Cause 6: MTU and MSS on tunnels
Behind a tunnel, for example a VPS that forwards ports to a home server over WireGuard, full 1500-byte packets no longer fit. If the ICMP “fragmentation needed” messages are filtered, large packets vanish: the connection opens, small requests work, and it hangs at the first full-size packet, ending in a timeout or, when a device gives up, a reset. If nginx on the VPS proxies to the home server, the kernel already sizes those connections for the tunnel. Traffic the VPS forwards (DNAT) needs MSS clamping, which rewrites the segment size in the handshake to fit:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtutcp flags syn tcp option maxseg size set rt mtuBehind Cloudflare
With the orange cloud, the browser’s TCP connection ends at Cloudflare, and whatever your origin does arrives as a Cloudflare error page: a reset after connecting becomes a 520, a refused connection a 521, no answer a 522. So ERR_CONNECTION_RESET on a proxied hostname happens between the visitor and Cloudflare: their network, their antivirus, or a filter that blocks Cloudflare addresses or that hostname. Check that the record really is proxied: dig +short example.com must return Cloudflare addresses (AS13335).
Our ASN lookup tells you which network an address belongs to.
If it returns your server’s own IP, the record is DNS-only and causes 1 to 6 apply. On a VMHeaven VPS in Amsterdam or Frankfurt you have root for tcpdump, nstat and the firewall, and the VNC console in the panel still reaches the server when a rule locks out SSH.
Frequently asked
Is ERR_CONNECTION_RESET my fault or the website's?
Test another site and another network. If every site fails on your device, the cause is local: a VPN, proxy, antivirus or a broken network stack. If only one site fails, from mobile data and other devices too, its server or a firewall in front of it sends the reset, and only the site owner can fix it.
What is the difference between ERR_CONNECTION_RESET and ERR_CONNECTION_REFUSED?
They happen at different moments. Refused is the answer to the very first packet: a TCP reset because nothing listens on the port, or a firewall's reject. Reset means the connection was already open, often with the request sent, when the server or a device in between killed it with a reset.
Can a firewall cause ERR_CONNECTION_RESET?
Yes, when it acts on connections that are already open: a rule that rejects with a TCP reset, an IPS, a TLS-inspecting firewall or antivirus, or an outdated firewall that cannot handle the larger post-quantum TLS handshake of current browsers. Rules that only reject new connections cause refused instead.