이 오류가 정확히 말하는 것

크롬은 “사이트에 연결할 수 없음 / 연결이 재설정되었습니다.”와 함께 ERR_CONNECTION_RESET(내부 코드 -101)을 보여 줍니다. 파이어폭스는 연결이 초기화(reset)되었다는 문구를, TLS 핸드셰이크 도중이면 PR_CONNECT_RESET_ERROR를 보여 줍니다. 프로그램에서는 ECONNRESET, “Connection reset by peer”로 보입니다.

패킷 수준에서는 RST 플래그가 켜진 TCP 패킷 하나입니다. 정상적인 종료는 점잖습니다. 양쪽이 FIN을 보내고, 이미 오가던 데이터는 끝까지 전달됩니다. RST는 그 반대입니다. “이 연결은 지금 당장 잊어라”는 뜻이고, 버퍼에 있던 데이터는 버려집니다. 브라우저는 응답을 기다리다가 대화 자체가 없던 것으로 통보받은 셈입니다.

핵심은 열린 뒤에 끊겼다는 점입니다. 이 한 가지가 형제 오류 둘과 이 오류를 가르고, 원인을 찾는 가장 빠른 길이기도 합니다.

오류연결에 일어난 일체감볼 곳
ERR_CONNECTION_REFUSED첫 패킷(SYN)에 RST가 돌아옴즉시 실패그 포트에 듣고 있는 프로그램이 없음, 거부(reject)하는 방화벽
ERR_CONNECTION_TIMED_OUT첫 패킷에 아무 응답 없음한참 멈췄다 실패패킷을 버리는(drop) 방화벽, 잘못된 IP, 라우팅
ERR_CONNECTION_RESET연결은 열렸고, 그 뒤 RST가 옴로딩이 시작되거나 연결 직후 실패애플리케이션, 프록시, 트래픽을 들여다보는 필터

reset은 거의 언제나 연결을 들여다보고 끊기로 결정한 무언가가 보냅니다. 서버 프로세스, 로드 밸런서, 트래픽을 검사하는 방화벽, 또는 내 PC의 보안 프로그램입니다. 할 일은 그중 무엇인지 알아내는 것이고, 가장 빠른 방법은 한 번에 하나씩 바꿔 보는 것입니다.

먼저: 어디서 나는가

두 질문이면 대부분 1분 안에 갈립니다.

한 사이트인가, 모든 사이트인가? 모든 HTTPS 사이트에서 재설정된다면 내 PC나 공유기 문제입니다. 아래 “내 PC의 프로그램”으로 바로 가세요. 한 사이트라면 계속 읽으세요.

한 네트워크인가, 모든 네트워크인가? 휴대폰에서 와이파이를 끄고 모바일 데이터로 같은 주소를 열어 보세요. 모바일 데이터로는 되고 와이파이에서만 안 되면, reset은 사이트가 아니라 내 네트워크(회사, 학교, 통신사, 국가 차단)에서 오는 것입니다. 어디서나 안 되면 사이트 자체가 끊고 있는 것이고, 사이트 운영자만 고칠 수 있습니다.

내 네트워크를 완전히 벗어나서 확인할 수도 있습니다. HTTP 점검은 DechoNet 서버에서 페이지를 가져옵니다. 거기서는 열리고 나만 안 된다면 서버는 정상입니다.

방문자라면

내 PC의 프로그램

‘웹 보호’나 ‘HTTPS 검사’ 기능이 있는 백신, VPN 프로그램, 회사 PC의 보안 에이전트는 모두 내 연결 중간에 끼어듭니다. 연결이 수상하다고 판단하거나 그냥 오작동하면 reset이 흔한 결과입니다. 은행·공공기관 사이트가 설치하게 하는 온라인 보안 프로그램 중에도 웹 보호나 피싱 차단처럼 연결에 관여하는 기능을 가진 것이 있습니다.

하나씩 끄고 다시 시도해 보세요. HTTPS 검사를 끄니 해결된다면 제품 전체를 지우기보다 그 기능만 끄거나 해당 사이트를 예외로 등록하세요.

프록시 설정도 확인하세요. 윈도우는 설정 → 네트워크 및 인터넷 → 프록시, macOS는 시스템 설정 → 네트워크 → 연결 → 세부사항 → 프록시입니다. 모르는 프록시가 잡혀 있다면 지우는 것이 맞습니다.

윈도우에서 보안 프로그램이나 VPN을 지운 뒤부터 재설정이 시작됐다면, 네트워크 스택에 망가진 구성 요소가 남았을 수 있습니다. 관리자 권한 명령 프롬프트에서 netsh winsock reset을 실행하고 재부팅하면 Winsock 설정이 기본값으로 돌아갑니다. 이걸로 해결되는 경우는 드물지만, 사이트를 탓하기 전 마지막 단계로는 맞습니다.

네트워크 차단: 회사, 학교, 통신사, 국가

웹 필터는 HTTPS를 복호화하지 않고도 사이트를 막을 수 있습니다. HTTPS 연결을 시작할 때 브라우저는 TLS ClientHello의 SNI 필드에 사이트 이름을 평문으로 실어 보냅니다. 경로에 있는 필터가 이것을 읽고, 차단 목록에 있는 이름이면 내 브라우저에(때로는 서버에도) 위조한 RST를 보냅니다. 내 쪽에서 보면 서버가 끊은 것과 똑같아 보입니다.

회사·학교 방화벽 다수가 이렇게 동작하고, 일부 국가 차단도 그렇습니다. 한국은 2019년 2월부터 불법·유해 사이트로 지정된 HTTPS 사이트를 SNI로 차단하고 있습니다. HTTP 사이트라면 warning.or.kr 안내 페이지로 넘어가지만, HTTPS는 안내 페이지를 띄울 방법이 없어 연결이 끊기는 형태로 나타납니다. 보고된 증상은 네트워크에 따라 연결 재설정이나 무응답(시간 초과)으로 갈립니다.

사이트가 아니라 필터라는 신호는 이렇습니다.

  • 모바일 데이터나 HTTP 점검에서는 열리고, 특정 네트워크에서만 실패합니다.
  • 연결 직후, 첫 요청에서, 매번 똑같이 실패합니다.
  • 같은 서버나 CDN에 있는 다른 사이트는 잘 열립니다.

브라우저에서 할 수 있는 해결책은 없습니다. 차단 사유는 네트워크 관리자가 볼 수 있으니 그쪽에 문의하세요. 내가 운영하는 사이트가 회사·학교 필터에 잘못 걸린 것이라면, 필터 제조사에 분류 재검토를 요청하는 창구가 대개 있습니다.

큰 의미 없는 것들

쿠키·캐시 삭제로 reset이 해결되는 일은 드뭅니다. reset은 TCP 수준에서 일어나고, 브라우저가 캐시된 무언가를 보내거나 읽기 전입니다. 한 번 해 보는 것은 괜찮지만 오래 붙잡고 있을 일은 아닙니다.

사이트를 운영한다면

필터가 없는 네트워크의 기기에서 curl로 재현하고, reset이 언제 오는지 보세요.

curl -v https://www.example.com/ -o /dev/null

TLS 핸드셰이크 도중의 reset:

* Connected to www.example.com (203.0.113.10) port 443
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* Recv failure: Connection reset by peer
curl: (35) Recv failure: Connection reset by peer

요청을 보낸 뒤의 reset:

> GET / HTTP/1.1
> Host: www.example.com
...
* Recv failure: Connection reset by peer
curl: (56) Recv failure: Connection reset by peer

curl 버전과 TLS 라이브러리에 따라 문구는 조금씩 다르지만, 중요한 것은 시점입니다. 핸드셰이크 도중이면 TLS 계층이나 SNI로 거르는 무언가를, 요청 뒤라면 애플리케이션이나 그 앞의 프록시를 가리킵니다.

curl: (52) Empty reply from server와도 구분하세요. 이건 응답 없이 정상 종료(FIN)된 것이고, 크롬에서는 ERR_EMPTY_RESPONSE로 나옵니다. 원인은 비슷하지만 신호가 다릅니다. 상대가 강제로 끊은 게 아니라 점잖게 닫은 것입니다.

요청 처리 중 프로세스가 죽음

애플리케이션이 크래시하거나 커널의 OOM killer가 프로세스를 죽이면, 커널이 그 소켓들을 닫습니다. 수신 버퍼에 읽지 않은 데이터가 남아 있으면 FIN 대신 RST가 나가므로 클라이언트는 reset을 봅니다.

실패한 시각의 로그를 보세요.

journalctl -u myapp --since "10 min ago"
dmesg -T | grep -i -E 'out of memory|killed process'

systemctl status myapp에서 시작 시각이 방금이고 재시작 횟수가 늘고 있다면 같은 이야기입니다. 크래시나 메모리 한도를 고치세요. 네트워크 쪽에서 조정할 것은 없습니다.

큰 업로드가 413 대신 reset으로 끝남

흔한 경우입니다. 클라이언트가 서버 허용치보다 큰 파일을 올리기 시작합니다. 서버는 거절하기로 하고 413 응답을 보낸 뒤, 클라이언트가 아직 보내는 중인데 연결을 닫습니다. 서버 버퍼에 남은 업로드 데이터 때문에 그 종료가 RST가 되고, 브라우저는 413 페이지 대신 ERR_CONNECTION_RESET을 보여 줍니다. 서버들은 닫기 전에 남은 업로드를 한동안 읽어 버리는 방식으로 이를 완화하지만(nginx의 lingering close), 파일이 크거나 회선이 느리거나 중간에 프록시가 하나 더 있으면 reset이 그대로 전달되는 일이 잦습니다.

업로드할 때만 재설정된다면 모든 계층의 본문 크기 제한을 확인하세요. nginx의 client_max_body_size(기본 1MB), Apache의 LimitRequestBody, 로드 밸런서나 CDN의 제한, 프레임워크 자체의 업로드 제한입니다.

sudo nginx -T | grep -n client_max_body_size

컨테이너 안의 앱이 127.0.0.1에서만 듣고 있음

Docker로 포트를 공개했는데(-p 8080:3000), 컨테이너 안의 앱은 127.0.0.1:3000에서만 듣고 있는 경우입니다. 공개한 포트로는 컨테이너의 loopback에 닿지 않습니다. 트래픽이 컨테이너에 어떤 경로로 들어가느냐에 따라 깔끔한 오류 대신 reset, 빈 응답, 거부 중 하나로 나타납니다.

컨테이너 안에서 앱이 0.0.0.0에서 듣게 하세요. 그 뒤 docker port <컨테이너>와 호스트의 ss -ltnp가 공개된 포트에 대해 같은 답을 내야 합니다.

로드 밸런서·NAT가 유휴 연결을 잊음

로드 밸런서, NAT 게이트웨이, 상태를 추적하는 방화벽은 일정 시간 이상 조용한 연결을 잊어버립니다. 나중에 어느 한쪽이 그 연결로 데이터를 보내면, 장비는 그런 연결을 모르므로 RST로 답할 수 있습니다. 예를 들어 AWS는 Network Load Balancer의 이 동작을 문서에 적어 두고 있습니다.

브라우저는 재사용한 keep-alive 연결에서 실패한 요청을 다시 보내기 때문에 이 문제를 대부분 가려 줍니다. 대신 다른 클라이언트에서 드러납니다. Node의 ECONNRESET, 파이썬의 ConnectionResetError, Go의 “connection reset by peer”가 한동안 조용하다가 나타나는 식입니다. 애플리케이션의 keep-alive 타임아웃을 경로상 가장 짧은 유휴 타임아웃보다 짧게 잡거나, TCP keepalive를 켜서 연결이 유휴 상태로 보이지 않게 하면 됩니다.

서버 앞의 방화벽, WAF, DDoS 방어

서버 앞의 보안 장비는 마음에 들지 않는 연결을 끊습니다. WAF 규칙에 걸린 요청, 속도 제한을 넘은 클라이언트, 해석하지 못한 ClientHello 등입니다. reset이 특정 URL, 특정 요청 내용, 특정 클라이언트 IP에 몰린다면 애플리케이션을 건드리기 전에 원 서버 앞에 있는 장비의 로그부터 보세요.

RST를 누가 보냈는지 확인하기

서버가 보낸 건지 중간의 무언가가 보낸 건지 모르겠다면 서버에서 패킷을 잡아 보세요.

sudo tcpdump -ni any -v 'tcp port 443 and (tcp[tcpflags] & tcp-rst != 0)'

서버가 RST를 보냈다면 서버에서 나가는 것이 보입니다. 클라이언트는 reset을 받았는데 서버 캡처에 아무것도 없다면 경로의 무언가가 보낸 것입니다. 클라이언트 쪽 캡처도 도움이 됩니다. 위조된 RST는 서버보다 가까운 장비에서 보내기 때문에, 같은 연결의 진짜 서버 패킷과 IP TTL 값이 다른 경우가 많습니다.

DechoNet으로 바깥에서 확인하기

  • HTTP 점검은 내 네트워크 밖에서 주소를 불러 상태 코드, 리다이렉트 경로, 헤더를 보여 줍니다. 여기서는 되는데 내 브라우저만 재설정된다면 문제는 나나 내 네트워크 쪽입니다. DechoNet의 해외 출구에서 응답을 받지 못하면 서울 관측점에서 다시 시도하므로, 해외 IP를 막는 국내 사이트도 확인할 수 있습니다.
  • 포트 점검은 443 포트에서 TCP 핸드셰이크가 완료되는지 알려 줍니다. ‘열림’인데 브라우저는 여전히 재설정된다면 TCP는 정상이고 문제는 TLS, 애플리케이션, 필터 중 하나입니다. 한계가 있습니다. 포트 점검은 거부, reset, 3초 안에 응답 없음을 모두 ‘닫힘’으로 표시하므로 셋을 구분하지 못합니다. 또 Cloudflare IP 주소에 있는 호스트는 우리 쪽에서 아예 점검할 수 없고, 도구가 추측하지 않고 그렇게 알려 줍니다.
  • DNS 조회로 이름이 생각한 곳을 가리키는지 확인하세요. 이전 직후에는 예전 IP의 서버가 모르는 호스트 요청을 끊고 있을 수 있습니다.

ERR_SSL_PROTOCOL_ERROR로 검색해서 오셨다면

같은 상황에서 함께 나타나는 다른 오류입니다. ERR_SSL_PROTOCOL_ERROR는 TLS 핸드셰이크가 reset이 아니라 프로토콜 오류로 실패했다는 뜻입니다. 서버가 올바른 TLS가 아닌 것을 보냈거나 핸드셰이크를 거부한 경우입니다. 흔한 원인 둘은 이 글과 겹칩니다. HTTPS에 끼어드는 보안 프로그램이나 필터는 두 오류 중 어느 쪽이든 낼 수 있고, 브라우저가 HTTPS를 기대하는 포트에서 서버가 평문 HTTP로 답하면 프로토콜 오류가 납니다. 자세한 진단은 ERR_SSL_PROTOCOL_ERROR에 있습니다.

비슷해 보이는 다른 오류

  • ERR_CONNECTION_REFUSED: 첫 패킷에 reset이 돌아와 연결이 아예 열리지 않았습니다.
  • ERR_CONNECTION_TIMED_OUT: 아무 응답이 없습니다. 패킷을 버리는 방화벽이나 잘못된 IP인 경우가 많습니다.
  • 502 Bad Gateway: 프록시에는 닿았고, 프록시와 애플리케이션 사이 연결이 실패했습니다. 그 구간의 reset은 브라우저 오류가 아니라 502로 보입니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.