도메인 앞에 프록시가 있을 때 원본 서버에 직접 연결해 응답을 확인하고 싶을 수 있다. 그런데 HTTPS 주소를 IP로 바꾸고 Host 헤더만 도메인으로 넣으면 인증서 오류가 날 수 있다. 같은 서버에 연결했는데 왜 결과가 다른지, 이번에는 임시 인증서와 로컬 TLS 서버로 확인했다.
접속 주소와 확인할 서버 이름을 분리했다

실험용 인증서에는 origin.punch.test라는 DNS 이름만 넣었다. 별도의 임시 CA로 서명하고 유효기간을 1일로 정했다. 서버는 127.0.0.1의 임시 포트에서만 수신했다. 운영 도메인의 인증서나 DNS 설정은 사용하지 않았으며, CA 인증서는 필요한 curl 명령에 --cacert로만 전달했다.
서버의 TLS 콜백에서는 클라이언트가 보낸 SNI 이름을 기록했다. SNI는 TLS 연결 초기에 서버에 전달되는 이름이다. HTTP Host 헤더는 그 뒤의 HTTP 요청에 포함된다. 두 값이 같은 역할을 한다고 가정하지 않고, URL과 옵션을 바꿨을 때 실제로 어떤 이름이 들어오는지 비교했다.
| 검사 방법 | 서버가 받은 SNI | curl 종료 코드 | HTTP 상태 |
|---|---|---|---|
| IP 주소 + Host 헤더 | 없음 | 60 | 000 |
| 정확한 이름 + --resolve + CA 신뢰 | origin.punch.test | 0 | 200 |
| 다른 이름 + --resolve + CA 신뢰 | wrong.punch.test | 60 | 000 |
| 정확한 이름 + --resolve, CA 미지정 | origin.punch.test | 60 | 000 |
HTTP 상태 칸의 000은 서버가 보낸 HTTP 상태 코드가 아니다. 이 curl 실행에서 HTTP 응답을 얻지 못했을 때 표시된 값이다. 실패한 세 요청은 종료 코드 60이었지만 원인은 같지 않았다. IP로 검사한 경우와 다른 이름으로 검사한 경우는 인증서 이름이 맞지 않았고, CA를 지정하지 않은 경우는 임시 CA를 신뢰하지 못했다.
Host 헤더만 바꿔도 인증서의 검사 대상은 바뀌지 않았다
IP URL에 Host: origin.punch.test를 추가한 요청에서 서버가 기록한 SNI는 없었다. 인증서는 IP 주소 127.0.0.1을 위한 인증서가 아니므로 검증이 실패했고 HTTP 응답을 받지 못했다. Host 헤더로 가상 호스트를 고를 수 있다는 HTTP의 경험을 TLS 이름 확인에 그대로 적용할 수 없었던 것이다.
반대로 URL을 origin.punch.test로 유지하고 --resolve로 그 이름과 포트의 연결 주소만 127.0.0.1로 지정했을 때는 SNI가 origin.punch.test로 들어왔다. 임시 CA도 지정한 이 요청은 검증을 통과해 200과 synthetic-origin 본문을 받았다. 원자료에는 성공 응답의 Host도 기록했으며 임시 포트 숫자만 TLS_PORT로 바꿔 공개했다.
원본 확인에는 도메인 URL을 유지한다

# 예시 주소다. 자신이 관리하는 도메인과 실제 원본 IP로 바꾼다.
# 공인 CA 인증서를 사용하는 원본을 직접 조회하는 예
curl --noproxy "*" --resolve example.com:443:192.0.2.10 \
--connect-timeout 3 --max-time 10 -sS -D - -o /dev/null \
https://example.com/192.0.2.10은 설명용 주소이며 펀치블로그 서버 주소가 아니다. --resolve는 지정한 호스트와 포트의 연결 주소를 그 curl 실행에 제공한다. 도메인 URL은 유지하므로 이름 확인을 끄지 않고 원본을 조회할 수 있다. 시스템 DNS나 다른 방문자의 접속 경로를 변경하는 작업도 아니다.
이 명령의 --noproxy는 환경에 설정된 프록시를 거치지 않고 직접 연결하기 위한 것이다. 직접 원본 접속이 허용되는 환경을 전제로 한다. 원본이 CDN 접속만 허용하거나 별도 인증을 요구하면 실패할 수 있으며, 그 실패만으로 공개 사이트 전체가 내려갔다고 판단할 수 없다.
같은 오류 코드라도 이름과 신뢰를 나눠 본다
wrong.punch.test를 --resolve로 같은 서버에 연결했을 때는 서버가 그 SNI를 받았지만 인증서에 그 이름이 없어 실패했다. 정확한 origin.punch.test를 사용해도 --cacert를 빼면 실패했다. 임시 CA를 운영체제에 설치하지 않았기 때문이다. “서버에 연결됐는가”, “인증서 이름이 맞는가”, “발급자를 신뢰하는가”는 각각 확인할 질문이다.
실험에 사용한 --cacert는 임시 CA를 명시적으로 신뢰시키기 위한 조건이다. 실제 공개 사이트에 이 임시 CA를 설치하라는 뜻은 아니다. 공개 CA 인증서를 쓰는 서버에서는 기본 신뢰 저장소로 검증이 되는지 확인하고, 사설 CA 환경이라면 해당 환경에서 관리하는 신뢰 체인을 사용해야 한다.
| 오류에서 볼 단서 | 먼저 대조할 것 | 확인 방향 |
|---|---|---|
| 인증서 이름과 URL 호스트 불일치 | URL에 IP를 썼는지와 인증서 대상 이름 | 도메인 URL을 유지하고 --resolve로 접속 목적지만 지정 |
| 발급자·신뢰 체인 문제 | 서버 인증서 체인과 클라이언트의 신뢰 저장소 | 자신의 시험 CA인지 공인 CA인지 구분 |
| 연결 거부·시간 초과 | 접속 IP·포트·검사 위치 | TLS 이름 검사 전의 연결 문제부터 확인 |
오류 문장은 TLS 라이브러리와 curl 버전에 따라 달라진다. 종료 코드 하나만으로 이름 문제와 CA 문제를 동일하게 처리하지 않는다. 위 마지막 행은 일반적인 진단 순서이며 이번 네 접속 실험에서 네트워크 차단을 추가로 재현한 결과는 아니다.
검증을 끈 200만으로 진단을 끝내지 않는다
curl의 인증서 검증을 끄면 이름이나 신뢰 문제를 확인하는 이번 실험의 전제가 사라진다. 우리는 네 요청 모두 검증을 끄지 않았다. HTTP 본문을 받을 수 있는지만 볼 것인지, 해당 도메인에 대해 신뢰할 수 있는 TLS 연결까지 확인할 것인지 목적을 구분하는 것이 중요하다.
원본 검사가 성공한 뒤에도 독자가 이용하는 공개 도메인을 별도로 조회해야 한다. 공개 프록시의 인증서, DNS 결과, 라우팅은 이번 로컬 실험의 확인 대상이 아니다. 기존 도메인 연결 글에서는 실제 운영 연결 과정과 후속 공개 응답 검사를 다루므로 함께 보면 검사 범위를 구분하기 쉽다.
편집자 의견
원본 HTTPS를 확인할 때는 검사할 도메인 이름을 URL에 유지하는 방식을 우선하겠습니다. IP URL에 Host 헤더만 바꾸는 방법은 HTTP 대상과 인증서 검사 대상의 차이를 가려 문제를 잘못 좁힐 수 있기 때문입니다.
어떤 서버에 연결했는지와 어떤 이름의 인증서를 검증했는지를 기록에 함께 남기겠습니다. 인증서 검증을 끄고 열린 페이지는 올바른 HTTPS 연결을 확인한 근거로 사용하지 않습니다.
판단 근거: Host 헤더만 바꿔도 인증서의 검사 대상은 바뀌지 않았다
의견 추가 2026-10-23 · 본문 자료를 바탕으로 한 편집 판단
실험 환경과 원자료
관찰 시각: 2026-09-13T03:08:37.539668+00:00
os: Linux · python: 3.10.12 · openssl: OpenSSL 3.0.2 15 Mar 2022 · curl: curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/3.0.2 zlib/1.2.11 brotli/1.0.9 zstd/1.4.8 libidn2/2.3.2 libpsl/0.21.0 (+libidn2/2.3.2) libssh/0.9.6/openssl/zlib nghttp2/1.43.0 librtmp/2.3 OpenLDAP/2.5.20
1일 유효 임시 CA와 origin.punch.test 전용 인증서. Python 루프백 TLS 서버에 curl 네 가지 요청. 서버 콜백에서 SNI 이름을 기록. 검증 비활성화 옵션은 사용하지 않음.
확인하지 않은 범위: 공개 CA 발급·갱신, 실제 DNS·CDN, 다중 인증서 선택, 운영 원본 방화벽, 인증서 만료 장애는 시험하지 않았다. 키와 인증서는 임시 디렉터리 정리 시 삭제된다.
Python 3, OpenSSL 명령행 도구, curl이 있는 Linux 실험 환경에서 실행합니다. 임시 CA는 해당 curl 요청에만 지정하며 운영체제 신뢰 저장소에 설치하지 않습니다. 임시 파일과 스크립트가 만든 자식 프로세스만 사용하며, 기존 결과 파일은 덮어쓰지 않습니다.
python3 lab-tls-resolve.py --output new-observations.json참고한 공식 문서
원고 수정: 2026-10-24 · 정정 요청
이 글의 보완 기록
- · 이름 불일치·CA 신뢰·연결 오류를 나누는 진단표를 보강했다.
- · 이름·CA·연결 문제를 중복해서 나열한 표를 정리했습니다. 실제 관찰 네 요청과 후속 진단표를 구분해 유지했습니다.
- · 본문 근거에 대한 편집자의 판단과 결론이 달라지는 조건을 추가했습니다. 기존 관찰일·공식 자료 확인일은 유지했습니다.
- · 글별 적용 사례와 반례를 보강했습니다. 기존 관찰일과 원자료 확인일은 유지했습니다.