문제
서버를 새 OS로 업그레이드했습니다 — Ubuntu 22.04, 새 Debian, OpenSSL 3을 담은 무엇이든 — 그러자 로그가 이걸로 뒤덮였습니다:
error:0A000126:SSL routines::unexpected eof while reading
또는 curl에서:
curl: (56) OpenSSL SSL_read: error:0A000126:SSL routines::unexpected eof while reading, errno 0
당신 코드는 바뀐 게 없습니다. 같은 스크립트, 같은 메일 서버, 같은 API 호출이 옛 서버에선 멀쩡했습니다. 지금은 겉보기엔 작동하면서 로그에 이 줄을 쏟아내거나, 아예 실패합니다. 그 둘은 같은 오류 메시지를 걸친 완전히 다른 상황이고, 해결책 전체가 그 둘을 구분하는 데 달려 있습니다.
증상
- 자기 코드엔 변화가 없는데 OS 업그레이드나 OpenSSL 3.x 상향 후 오류가 나타나기 시작.
- 메일 서버 로그(Postfix, Exim), curl의
(56), PHP/WHMCS, 또는 아웃바운드 TLS를 여는 모든 앱에서 등장. - 어떨 땐 요청이 여전히 성공하고 순수 노이즈, 어떨 땐 연결이 진짜 실패해 아무것도 안 옴.
unexpected eof while reading가 실제로 뜻하는 것
TLS에는 대화를 정중히 끝내는 방법이 있습니다: “끝났습니다, 그리고 메시지가 잘리지 않았음을 알리려 당신에게 말합니다”라고 하는 close_notify 경고입니다. 대안 — 그냥 TCP 연결을 끊는 것 — 은 상대가 정상 종료와 공격자가 스트림을 일찍 자른 것을 구별하지 못하게 만듭니다.
OpenSSL 1.1.1은 빠진 close_notify를 대체로 무시했습니다. errno 0인 모호한 SSL_ERROR_SYSCALL이 나거나 아무것도 안 났습니다. OpenSSL 3.0은 그 탐지를 복원했고 이름을 붙였습니다: error:0A000126 ... unexpected eof while reading. 그러니 와이어 상의 동작은 이전과 동일합니다 — 상대가 작별 인사 없이 끊었다 — 다만 1.1.1이 조용히 있던 자리에서 3.0이 이제 당신에게 알립니다. 바로 그 한 가지 변화가 업그레이드 뒤 오류가 “나타난” 이유입니다. 업그레이드가 뭘 망가뜨린 게 아니라, 줄곧 일어나던 걸 알려주기 시작한 겁니다.
그러면 질문 전체가 다시 짜입니다. 오류가 문제가 아닙니다. 질문은 그래도 연결이 됐는가입니다.
분기: 노이즈 vs 진짜 실패
데이터가 통과했다면 무해(그냥 노이즈)입니다. 수많은 상대가 끝나는 순간 close_notify 대신 소켓을 닫습니다: 모바일 클라이언트, 메일 클라이언트, 일부 로드밸런서, 오래된 서버. 응답은 도착했고, 전송은 완료됐고, 종료만 무례했습니다. 여기엔 당신 쪽에서 고칠 게 없고 — 상대의 매너라 고칠 수도 없습니다. 노이즈가 메일 로그를 압도한다면 그건 TLS가 아니라 로그 상세도 문제입니다.
아무것도 안 왔다면 진짜 실패입니다 — 핸드셰이크가 끝내 완료되지 않았거나 0바이트를 받았거나. 이제 unexpected eof는 증상이고, 원인은 거의 인증서가 아닙니다:
- TLS가 아닌 포트에 TLS로 말하고 있다. 고전: HTTPS/TLS 클라이언트를 평문 서비스나 아예 틀린 포트로 겨눈 경우. 서버는 당신의 ClientHello를 쓰레기로 읽고 소켓을 닫습니다. 인증서를 탓하기 전에 포트가 실제로 TLS를 협상하는지 확인하세요.
- 공유하는 프로토콜 버전/cipher 없음. 한쪽이 TLS 1.0/1.1을 버렸는데(최신 빌드 대부분 그렇습니다) 다른 쪽은 그것만 제공. 핸드셰이크가 즉시 죽고 갑작스런 EOF처럼 보입니다.
- 방화벽이나 로드밸런서가 끼어든다. 핸드셰이크 도중 RST를 보내는 미들박스, 연결을 죽이는 유휴 타임아웃, 백엔드 없는 LB가 같은 잘린 종료를 만듭니다.
- 서버가 쓰러지고 있다. 핸드셰이크를 끝내기 전에 크래시·OOM·타임아웃 — TLS와 무관하게 죽어서 끊는 겁니다.
빠르게 가르는 법: 포트가 TLS를 말하기는 하는가, 그리고 양쪽이 버전과 cipher를 공유하는가? 그렇고 데이터가 흐르면 노이즈 영역입니다. 핸드셰이크가 끝내 완료되지 않으면 실패 영역이고 문제는 포트, 프로토콜, 또는 중간의 박스입니다.
DechoNet으로 진단
- SSL 확인은 호스트와 포트에 실제 TLS 핸드셰이크를 완료해, 엔드포인트가 정말로 TLS를 협상하는지, 어떤 버전과 cipher를 제공하는지, 유효한 인증서가 제공되는지 보여줍니다 — “핸드셰이크가 완료될 수 있는가”를 가장 빠르게 확인하는 길이고, 그게 바로 노이즈와 실패를 가르는 선입니다.
- 포트 확인은 포트가 밖에서 열려 있고 도달 가능한지를 알려줘, “서비스가 안 떠 있다 / 방화벽이 떨군다”를 TLS 계층 문제와 분리합니다. filtered거나 closed인 포트는 인증서와 무관한
unexpected eof를 설명합니다. - HTTP 확인은 HTTPS 엔드포인트가 진짜 응답을 돌려주는지 확인해, 무례하게 닫기만 하는 작동 연결(무해)과 아무것도 전달하지 않는 연결(쫓을 가치가 있는 실패)을 구분합니다.
해결 체크리스트
- 요청이 실제로 성공했는지 먼저 확인. 데이터가 왔고 종료만 불완전했다면 노이즈 — 여기서 멈추세요.
- 실패했다면 포트가 실제로 TLS를 말하는지(평문·틀린 포트에 TLS 클라이언트를 겨누지 않았는지) 확인.
- 클라이언트와 서버가 TLS 버전을 공유하는지 — 최신 클라이언트는 TLS 1.0/1.1에 묶인 서버와 말하지 않고, 반대도 마찬가지.
- 핸드셰이크 도중 RST를 보내거나 타임아웃하는 방화벽/로드밸런서와, 응답 전에 크래시하는 서버를 배제.
-
-k/--insecure나NODE_TLS_REJECT_UNAUTHORIZED=0으로 덮지 마세요; 실제 원인은 안 건드리고 검증만 끕니다. - 얌전하지만 무례한 상대가 내는 로그 노이즈일 뿐이라면, 버그가 아니라 로그 레벨 결정으로 다루세요.
이럴 땐 넘기세요
- 무해한 노이즈가 메일 서버 로그를 뒤덮는 경우, “해결”은 상대 쪽에 있고(그들이
close_notify를 보내야 함) 당신 손을 벗어납니다 — 없는 TLS 버그를 쫓지 말고 로그 필터링으로 관리하세요. - 핸드셰이크가 정말로 완료되지 않는데 포트·프로토콜·인증서가 다 멀쩡하다면,
openssl s_client -connect host:port(또는 패킷 캡처)로 교환을 떠서 상대가 정확히 어디서 끊는지 보세요 — 미들박스나 서버측 크래시를 짚어 줍니다. - 갑작스런 종료가 아니라 구체적으로 인증서 검증 문제라면, 다른 오류 패밀리입니다 — 서버 체인 vs 클라이언트 신뢰 분기는 curl: (60) SSL certificate problem을 보세요.
내 도메인에서 바로 확인
무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.