TT Lab
블로그

한국어

시작하기
블로그

블로그

헤어핀 NAT 실측 1: 사내에서만 우리 도메인이 안 열린다

상태: 실측 (2026-11-01). 가정용 공유기 뒤의 TT Lab 클러스터에서 잰 값입니다. 공유기·클러스터·네트워크 설정은 하나도 바꾸지 않았고, 읽기와 임시 파드만 썼습니다. 주소와 도메인은 문서용 예시(192.0.2.0/24, 203.0.113.0/24, app.example.com)로 바꿔 적었고, 시간과 횟수는 실제 값입니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 원인을 확인하지 못한 해석은 「추정」 으로 표시합니다.

목차

1. 결론부터

공유기 뒤에 웹 서비스를 열고 도메인을 붙였습니다. 휴대폰 데이터로 열면 잘 열리는데, 같은 와이파이에 붙은 노트북에서는 열리지 않습니다. 서버도 멀쩡하고 DNS 도 맞습니다. 이것이 헤어핀 NAT 문제입니다.

쿠버네티스 안의 같은 모양의 문제(파드가 자기 Service 로 요청하는 경우)는 2편에서 봅니다.

2. 무엇이 문제인가

집이나 작은 사무실의 공유기는 보통 공인 주소 하나를 갖고, 안쪽 기기들에게는 사설 주소를 나눠 줍니다. 바깥에서 들어오는 요청을 안쪽 서버로 보내려면 포트 포워딩을 겁니다. 「공인 주소의 443 으로 오는 것은 서버 192.0.2.10 의 443 으로」 같은 규칙입니다. 공유기는 들어온 패킷의 목적지 주소를 서버 주소로 바꿉니다. 이것을 DNAT(Destination NAT)라고 합니다.

바깥 손님에게는 이것으로 충분합니다. 서버의 답은 기본 경로를 따라 공유기로 가고, 공유기는 기록해 둔 연결 정보로 출발지를 공인 주소로 되돌려 손님에게 보냅니다. 손님은 공인 주소로 보냈고 공인 주소에서 답을 받았으니 문제가 없습니다.

문제는 손님이 같은 사설망 안에 있을 때입니다. 노트북(192.0.2.20)이 app.example.com 을 찾으면 공인 DNS 는 공인 주소(203.0.113.7)를 알려 줍니다. 노트북은 그 주소로 요청을 보내고, 요청은 기본 게이트웨이인 공유기로 갑니다. 여기서 공유기가 할 수 있는 일은 셋입니다.

  1. 아무것도 하지 않는다. 사설망 쪽에서 들어온 패킷에는 포트 포워딩을 적용하지 않습니다. 공유기는 그 주소를 자기 것으로 보고 받지만 443 에서 듣는 것이 없으니 버리거나 거절합니다.
  2. 목적지만 바꾼다(DNAT). 아래 그림의 경우입니다.
  3. 목적지와 출발지를 모두 바꾼다(DNAT + SNAT). 이것이 헤어핀 NAT 입니다.

목적지만 바꾸면

헤어핀 NAT 가 없을 때: 답이 다른 주소에서 온다 같은 사설망 안에서 서비스의 공인 주소로 접속하는 경우 클라이언트 192.0.2.20 공유기 안 192.0.2.1 밖 203.0.113.7 서버 192.0.2.10 192.0.2.20 → 203.0.113.7 ① 공인 주소로 보낸다 192.0.2.20 → 192.0.2.10 ② 목적지만 바꾼다(DNAT) 192.0.2.10 → 192.0.2.20 ③ 같은 망이라 공유기 없이 바로 답한다 클라이언트는 203.0.113.7 의 답을 기다린다 192.0.2.10 에서 온 답은 모르는 연결이라 버린다 연결이 맺어지지 않고 시간 초과로 끝난다
그림: 헤어핀 NAT 가 없을 때: 공유기가 목적지만 바꾸면 서버가 같은 망의 클라이언트에게 바로 답하고, 클라이언트는 그 답을 버린다

공유기가 목적지만 서버 주소로 바꿔 보내면, 서버가 받은 패킷의 출발지는 노트북의 사설 주소(192.0.2.20)입니다. 서버가 보기에 노트북은 같은 망의 이웃이므로 답을 공유기로 보내지 않고 노트북에게 바로 보냅니다. 답 패킷의 출발지는 서버 주소(192.0.2.10)입니다.

노트북은 203.0.113.7 의 443 과 연결을 맺으려고 SYN 을 보냈습니다. 그런데 돌아온 SYN-ACK 의 출발지는 192.0.2.10 입니다. 노트북의 TCP 는 이것을 자기가 연 연결의 답으로 맞춰 볼 수 없으므로 모르는 패킷으로 처리합니다(보통 RST 로 거절합니다). 노트북은 기다리던 답을 끝내 받지 못하고 SYN 을 몇 번 더 보낸 뒤 시간 초과로 끝납니다. 공유기도 답이 자기를 지나가지 않았으므로 주소를 되돌려 줄 기회가 없습니다.

요점은 요청과 답이 다른 길로 다닌다는 것입니다. NAT 는 한 연결의 두 방향이 모두 자기를 지나야 주소를 바꿨다가 되돌릴 수 있습니다.

출발지도 바꾸면: 헤어핀 NAT

헤어핀 NAT: 출발지도 바꿔 같은 길로 돌아오게 공유기가 목적지(DNAT)와 출발지(SNAT)를 함께 바꾼다 클라이언트 192.0.2.20 공유기 안 192.0.2.1 밖 203.0.113.7 서버 192.0.2.10 192.0.2.20 → 203.0.113.7 ① 공인 주소로 보낸다 192.0.2.1 → 192.0.2.10 ② 도착과 출발을 모두 바꾼다 192.0.2.10 → 192.0.2.1 ③ 답이 공유기로 돌아온다 203.0.113.7 → 192.0.2.20 ④ 되돌려 바꿔 전달한다 클라이언트는 기다리던 주소에서 답을 받는다 대가: 서버에는 사내 접속이 모두 공유기 주소로 보인다 규격(RFC 4787·5382)은 출발지를 공인 주소로 보이게 하라고 한다
그림: 헤어핀 NAT: 공유기가 도착과 출발을 모두 바꿔 서버의 답이 공유기로 돌아오고, 공유기가 되돌려 바꿔 클라이언트에게 전달한다

공유기가 목적지와 함께 출발지도 자기 주소로 바꾸면(②), 서버는 요청이 공유기에서 왔다고 보고 답을 공유기로 보냅니다(③). 공유기는 연결 기록대로 두 주소를 되돌려, 출발지 203.0.113.7 로 노트북에게 넘깁니다(④). 노트북은 기다리던 주소에서 답을 받았으므로 연결이 맺어집니다. 패킷이 공유기에 들어왔다가 머리핀처럼 꺾여 같은 쪽으로 나가므로 헤어핀이라고 부릅니다. 루프백 NAT, NAT 리플렉션도 같은 기능을 가리키는 이름입니다.

대가도 있습니다. 서버 쪽에서는 사설망 안의 모든 접속이 공유기 하나에서 온 것처럼 보입니다. 접속 기록으로 사내 사용자를 구분할 수 없고, 출발지 주소로 거는 접근 제한이나 요청 수 제한이 사내 사용자 전체를 한 사람으로 셉니다. 사내 트래픽이 공유기를 두 번 지나므로 공유기의 처리량도 씁니다.

규격은 무엇을 요구하나

NAT 의 동작을 정한 IETF 문서는 헤어핀을 선택 사항으로 두지 않습니다.

그림에서는 이해하기 쉽도록 출발지를 공유기의 사설 쪽 주소(192.0.2.1)로 그렸지만, 규격이 요구하는 모양은 출발지가 공인 주소와 바깥에서 쓰는 포트로 보이는 것입니다. 어느 쪽이든 답이 공유기로 돌아온다는 핵심은 같습니다. 직접 리눅스로 공유기를 만들 때 흔히 쓰는 「사설망 출발, 서버 도착이면 MASQUERADE」 규칙은 사설 쪽 주소로 바꾸는 방식입니다. 실제 가정용 공유기 가운데는 규격과 달리 헤어핀을 지원하지 않거나 끌 수 있게 둔 제품이 있습니다(이번에 잰 공유기도 2절의 1이나 2에 해당했습니다. 어느 쪽인지는 5절에서 봅니다).

3. 해결책 넷

공유기를 고칠 수 있으면 그것이 가장 깔끔하지만, 늘 그럴 수는 없습니다. 방법은 크게 넷이고, 실제로는 둘 이상을 섞어 씁니다.

방법 하는 일 언제 쓰나 단점
분할 DNS 사설망 안의 DNS 가 같은 이름에 내부 주소를 답한다 사내 기기·클러스터가 많고 DNS 서버를 직접 운영할 때 안과 밖의 답이 달라 「DNS 가 맞는지」 확인할 때 헷갈린다. 내부 주소가 바뀌면 두 곳을 고쳐야 한다. DoH 처럼 사내 DNS 를 건너뛰는 기기에는 듣지 않는다
hosts 파일 기기 하나에서 이름을 내부 주소로 고정한다 기기 몇 대, 급할 때 기기마다 손으로 고쳐야 하고 잊힌다. 노트북을 들고 나가면 바깥에서 그 이름이 안 열린다
공유기 설정 공유기의 헤어핀(NAT 루프백) 기능을 켠다 공유기가 지원하고 설정을 바꿀 수 있을 때 제품마다 이름과 위치가 다르고 아예 없기도 하다. 서버에는 사내 접속이 공유기 주소로 보인다
바깥 경유 사내 기기가 바깥의 프록시·터널·VPN 을 거쳐 공인 주소로 들어온다 바깥 손님이 보는 길을 그대로 시험해야 할 때 느리고 비용이 들며, 바깥 장비가 하나 더 고장 날 수 있다

몇 가지를 덧붙입니다.

4. 측정 방법

설정을 바꾸지 않고 「같은 이름으로, 공인 주소와 내부 주소 각각에 접속」 하는 것을 세 자리에서 비교했습니다.

자리 이름을 어떻게 푸나
노트북(macOS, 사설망) hosts 파일이 이름을 내부 주소로 고정해 두었다. 공인 주소 쪽은 --resolve 로 강제
클러스터 노드(우분투, 사설망) hosts 파일에 오히려 공인 주소가 적혀 있다. 내부 주소 쪽을 --resolve 로 강제
클러스터 안의 임시 파드 클러스터 DNS(CoreDNS)의 hosts 플러그인이 이름에 내부 주소를 답한다(분할 DNS). 공인 주소 쪽은 TCP 연결만 시도

명령의 모양은 이렇습니다. 시도마다 5초 제한을 두었습니다.

# 공인 주소로 (DNS 를 건너뛰고 공인 주소를 강제, 이름은 그대로)
curl -sv --max-time 5 --resolve app.example.com:443:203.0.113.7 https://app.example.com/
# 내부 주소(게이트웨이)로
curl -sv --max-time 5 --resolve app.example.com:443:192.0.2.10 https://app.example.com/

바깥 대조는 여러 나라에서 HTTP 요청을 보내 주는 공개 점검 서비스(check-host.net 의 HTTP 점검)로 네 지점에서 했습니다.

5. 실측 결과

자리 공인 주소로 내부 주소로
노트북 3/3 시간 초과(5.00~5.01초, 연결 단계에서 멈춤). 포트 80 도 시간 초과 3/3 HTTP 200, 연결 2.8~4.0ms, 전체 23~28ms
클러스터 노드 3/3 시간 초과(5.00초). hosts 의 공인 주소를 따르는 기본 접속도 시간 초과 3/3 HTTP 200, 전체 24~31ms
임시 파드 TCP 연결 시간 초과(5.0초) 이름이 내부 주소로 풀려 /healthz 200(136ms, 첫 요청이라 TLS 연결 포함)
바깥 네 지점 세 지점 HTTP 200(0.78~1.05초), 한 지점 「Connection reset by peer」 해당 없음

바깥에서는 열리고 안에서는 공인 주소로 열리지 않는다는 헤어핀 문제의 모양이 그대로 나왔습니다. 2026-10 의 장애 기록에 「사내 헤어핀은 막힘」 이라고 적어 두었는데, 2026-11-01 에도 그대로였습니다. 바깥 한 지점의 연결 거절은 다른 세 지점이 정상이어서 그 지점 쪽 사정으로 보지만 원인은 확인하지 못했습니다(추정).

몇 가지를 더 쟀습니다.

6. TT Lab 에서 있었던 일

일반화해서 적으면 이렇습니다.

  1. 사내에서만 서비스 도메인이 열리지 않았습니다. 바깥 손님은 문제가 없었으므로, 처음에는 사내 기기의 DNS 나 브라우저 캐시를 의심했습니다. 실제로는 공유기가 헤어핀을 하지 않았습니다.
  2. 사내 시험이 바깥 상태를 알려 주지 못했습니다. 바깥 접속이 정말로 끊긴 장애가 났을 때, 사내에서는 평소에도 안 열리던 이름이라 「안 된다」 가 새 정보가 되지 못했습니다. 반대로 사내에서 내부 주소로 「된다」 도 바깥을 보장하지 않습니다. 그 뒤로 확인은 두 갈래로 나눕니다. 안은 --resolve 로 게이트웨이 주소에 직접, 밖은 바깥 점검 서비스로 봅니다.
  3. 해결책을 자리마다 다르게 썼습니다. 클러스터 안에는 CoreDNS 의 hosts 플러그인으로 분할 DNS 를 두었습니다(파드에서 이름이 내부 주소로 풀린 것이 이것입니다). 개발용 노트북은 hosts 파일로 고정했습니다. 공유기 설정은 건드리지 않았습니다.
  4. 잊힌 hosts 파일이 하나 있었습니다. 한 노드의 hosts 파일에는 이름이 오히려 공인 주소로 적혀 있어서, 그 노드에서 도메인으로 하는 접속은 언제나 실패했습니다. hosts 파일은 「기기마다 손으로, 그리고 잊힌다」 는 단점이 그대로 드러난 경우입니다.

클러스터 안에서 자기 공인 주소로 나가는 길이 막히면 cert-manager 의 HTTP-01 자체 점검처럼 「바깥에서는 되는데 안에서 확인만 실패하는」 일이 생깁니다. 이것은 cert-manager 는 어떻게 인증서를 만들고 갈아 끼우나에서 다뤘습니다.

7. 한계

8. 정리: 「안에서만 안 돼요」 를 들으면

확인 이유
바깥에서 먼저 연다(휴대폰 데이터, 바깥 점검 서비스) 바깥이 되고 안이 안 되면 헤어핀일 가능성이 크다
안에서 --resolve 이름:443:내부주소 이것이 되면 서버는 정상이고 길이 문제다
안에서 공인 주소로 시간 초과인지 거절이 아니라 시간 초과면 공유기가 버리거나 목적지만 바꾸는 경우
해결은 이름 쪽에서 분할 DNS(여럿), hosts(몇 대), 공유기 기능이 있으면 켜기
잊힌 hosts 파일 한 기기에서만 다르게 되면 hosts 부터 본다
확인용 기기의 자리 클러스터 노드에서 잰 「된다」 는 사설망을 안 건넜을 수 있다

티티 한마디. 우리 집 주소로 편지를 부쳤는데 우체국이 「같은 동네네」 하고 옆집 아저씨 이름으로 답장을 바로 넣어 주면, 저는 모르는 사람 편지라고 버려요. 우체국이 답장도 꼭 우체국 도장으로 찍어 보내 줘야 제가 받아요. 그게 안 되면 그냥 집 안에서는 「옆방!」 하고 부르면 돼요(분할 DNS)!

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다