집에서 내 서버에 도메인으로 접속이 안 되는 이유 (NAT Loopback)
문제 설명
미니PC를 집 랜선에 연결해서 셀프 호스팅(서버를 직접 운영하는 것)을 한다. 밖에서 도메인(evanshlee.com)으로 접속하면 잘 되는데, 정작 집 WiFi에서 같은 도메인으로 접속하면 안 된다. 왜 그럴까?
공유기가 NAT Loopback(헤어핀 NAT)을 지원하지 않기 때문이다. 쉽게 말하면, 집 안에서 보낸 요청이 집 밖으로 나갔다가 다시 집 안으로 돌아오는 경로를 공유기가 처리하지 못하는 것이다. 왜 그런지 알려면 먼저 몇 가지 개념을 알아야 한다.
기본 개념
- 공인 IP: 인터넷에서 우리 집을 찾는 고유 주소. 이 글에서는
203.0.113.45를 예시로 쓴다. - 사설 IP (로컬 IP): 집 안에서만 통하는 내부 주소.
192.168.1.X형태. 인터넷에서는 이 주소로 접속할 수 없다. - 도메인:
evanshlee.com처럼 사람이 읽는 주소. DNS(Domain Name System) 서버가 이 도메인을 공인 IP로 변환해준다. - 포트포워딩: 공유기가 외부에서 들어오는 특정 요청을 내부 특정 기기로 전달하는 설정. 예: "외부에서 22번 포트로 오는 요청은 미니PC(
192.168.1.100)로 보내라." - 패킷: 네트워크에서 데이터를 주고받는 단위. 편지에 비유하면 봉투(출발지·목적지 주소)와 내용물을 합친 것.
집 WiFi에서 evanshlee.com 접속 시도
graph TD
A["Mac (집 WiFi)"] -->|"evanshlee.com"| B["공유기"]
B -->|"203.0.113.45로 나갔다가"| C["인터넷"]
C -->|"다시 들어오려 함"| D["공유기(?)"]
D -->|"❌"| E["미니PC"]
많은 공유기가 "자기 공인 IP로 다시 들어오는 연결"을 차단한다.
비유로 이해하기
상황: 같은 아파트에 사는 친구에게 우편 보내기
방법 1: 직접 전달
graph LR
A["나 (401호)"] -->|"복도 걸어가기"| B["친구 (501호)"]
- 빠르고 간단
- 아파트 안에서만 가능
방법 2: 우체국 경유
graph LR
A["나 (401호)"] -->|"우편 발송"| B["아파트 밖<br/>우체국"]
B -->|"배달"| C["아파트 주소"]
C -->|"???"| D["❓"]
- 우체국에서 "어? 이거 이 아파트 주소네? 근데 어디로 배달하지?"
- 많은 아파트 관리실이 이런 우편을 거부함 ("우리 아파트에서 보낸 건데 왜 우체국을 거쳐서 다시 돌아와? 이상하니까 안 받아.")
내부에서 공인 IP로 접속하면 왜 안 되는가?
핵심은 기술적 한계다. 공유기의 NAT는 집 안의 여러 기기가 하나의 공인 IP를 공유해서 인터넷을 쓰게 해주는 기능인데, "내부 → 외부" 방향의 통신만 처리하도록 설계됐다. 그런데 NAT Loopback은 "내부 → 외부 → 다시 내부"라는 특수한 경로를 요구한다.
외부 요청과 내부 요청, 공유기는 어떻게 구분하는가?
공유기에는 두 개의 네트워크 연결이 있다:
- WAN (Wide Area Network, 외부): 인터넷과 연결 — 공인 IP
203.0.113.45가 할당됨 - LAN (Local Area Network, 내부): 집 안 기기들과 연결 —
192.168.1.1대역
공유기가 패킷을 받으면 세 가지를 확인한다: 어느 쪽(WAN/LAN)으로 들어왔는지, 보낸 사람 IP가 뭔지, 받는 사람 IP가 뭔지. 두 상황을 비교하면 차이가 명확하다.
외부에서 온 정상 요청:
| 항목 | 값 |
|---|---|
| 수신 | WAN |
| 보낸 사람 IP | 1.2.3.4 (외부 사용자) |
| 받는 사람 IP | 203.0.113.45 (공유기 공인 IP) |
→ WAN으로 들어온 외부 요청. 포트포워딩 규칙에 따라 미니PC로 전달. 정상 처리. ✅
내부에서 온 NAT Loopback 요청:
| 항목 | 값 |
|---|---|
| 수신 | LAN |
| 보낸 사람 IP | 192.168.1.10 (집 안의 Mac) |
| 받는 사람 IP | 203.0.113.45 (공유기 공인 IP) |
→ LAN으로 들어왔는데 받는 사람이 자기 공인 IP. 포트포워딩 규칙은 WAN으로 들어오는 요청만 처리하므로, 이 패킷에 해당하는 규칙이 없다. 드롭(폐기). ❌
응답이 엉뚱한 곳으로 돌아가는 문제
포트포워딩 규칙이 매칭되더라도 문제가 하나 더 남는다. 공유기가 패킷의 받는 사람 주소를 바꾸는 작업(DNAT, Destination NAT)을 할 때 일어난다.
일반적인 외부 접속에서 공유기는 이렇게 동작한다:
- 외부(
1.2.3.4)에서 공인 IP(203.0.113.45)로 패킷 도착 - 공유기가 받는 사람 주소를 내부 IP로 변환 — 이것이 DNAT
- 미니PC가 응답을 보냄 → 받는 사람이 외부 IP(
1.2.3.4)이므로, 인터넷으로 나가야 한다 → 공유기를 거침
graph TD
A["외부 클라이언트<br/>1.2.3.4"] -->|"받는 사람: 203.0.113.45"| B["공유기"]
B -->|"DNAT: 받는 사람을<br/>192.168.1.100으로 변환"| C["미니PC<br/>192.168.1.100"]
C -->|"응답 → 받는 사람이<br/>외부 IP이므로<br/>공유기 경유"| B
B -->|"응답 전달"| A
NAT Loopback에서는 이게 꼬인다:
- 집 안의 Mac(
192.168.1.10)이 공인 IP(203.0.113.45)로 패킷 전송 - 공유기가 DNAT으로 받는 사람을 미니PC(
192.168.1.100)로 변환 → 여기까지는 동일 - 문제: 미니PC가 응답을 보내려는데, 받는 사람이
192.168.1.10— 같은 집 안 네트워크다. 인터넷으로 나갈 필요가 없으니 공유기를 거치지 않고 직접 돌아감 - Mac 입장에서는
203.0.113.45(공인 IP)에 보냈는데,192.168.1.100(내부 IP)에서 응답이 옴 → "내가 보낸 상대가 아닌데?" → 연결 실패
graph TD
A["Mac (집 WiFi)<br/>192.168.1.10"] -->|"① 받는 사람: 203.0.113.45"| B["공유기"]
B -->|"② DNAT: 받는 사람을<br/>192.168.1.100으로 변환"| C["미니PC<br/>192.168.1.100"]
C -.->|"③ 응답: 받는 사람이<br/>같은 내부 네트워크<br/>→ 공유기를 안 거침"| A
A -->|"④ 공인 IP로 보냈는데<br/>내부 IP에서 응답? ❌"| X["연결 실패"]
style X fill:#ffcccc
이걸 해결하려면 공유기가 보낸 사람 주소까지 자기 IP로 바꿔야 한다 — SNAT (Source NAT: 보낸 사람 주소를 바꾸는 변환). 그러면 미니PC는 "공유기가 보낸 요청"이라고 인식하고, 응답도 공유기를 거쳐서 돌아간다.
graph TD
A["Mac (집 WiFi)<br/>192.168.1.10"] -->|"① 받는 사람: 203.0.113.45"| B["공유기"]
B -->|"② DNAT: 받는 사람을<br/>192.168.1.100으로 변환<br/>+ SNAT: 보낸 사람을<br/>공유기 IP로 변환"| C["미니PC<br/>192.168.1.100"]
C -->|"③ 응답: 받는 사람이<br/>공유기 → 공유기 경유"| B
B -->|"④ 원래 보낸 사람에게<br/>응답 전달 ✅"| A
style A fill:#ccffcc
즉, 받는 사람 변환(DNAT) + 보낸 사람 변환(SNAT) 두 번이 필요하다. 하지만 많은 공유기가 이 이중 변환을 기본적으로 지원하지 않는다. 해당 경우를 처리하는 규칙이 없거나, 성능·보안상 의도적으로 비활성화돼 패킷이 드롭(폐기)된다.
전체 상황 비교
graph TD
subgraph S1["❌ 집 WiFi + 도메인"]
direction TB
A1[Mac] --> R1[공유기]
R1 -->|"그게 나인데?"| X1[차단]
end
subgraph S2["✅ 집 WiFi + 로컬 IP"]
direction TB
A2[Mac] --> M2[미니PC]
end
subgraph S3["✅ 외부 + 도메인"]
direction TB
A3[Mac] --> I[인터넷]
I --> R3[공유기]
R3 --> M3[미니PC]
end
style X1 fill:#ffcccc
style M2 fill:#ccffcc
style M3 fill:#ccffcc
해결책 정리
아래 예시는 SSH(원격으로 다른 컴퓨터에 접속하는 프로토콜) 기준이지만, 이 문제는 웹사이트, API(프로그램끼리 데이터를 주고받는 통신), 파일 전송 등 포트포워딩을 사용하는 모든 서비스에 동일하게 적용된다.
방법 1: /etc/hosts로 도메인을 로컬 IP에 매핑 (내가 선택한 방법)
/etc/hosts는 컴퓨터에 있는 "나만의 주소록" 파일이다. 여기에 도메인과 IP를 적어두면, DNS 서버에 물어보지 않고 이 파일을 먼저 참고한다. 집 WiFi에서 evanshlee.com을 로컬 IP로 연결하게 만들 수 있다.
# /etc/hosts
192.168.1.100 evanshlee.com# /etc/hosts
192.168.1.100 evanshlee.com- 장점: 집에서도 밖에서도
evanshlee.com하나로 통일. SSH Config를 두 개 관리할 필요 없음. - 단점: 네트워크 바뀔 때마다
/etc/hosts를 수동으로 켜고 꺼야 함. (자동화하려면 네트워크 변경 이벤트에 스크립트를 걸 수 있다 — macOS는configd, Linux는NetworkManager dispatcher.) - 판단: 공유기 설정(NAT Loopback 활성화)을 건드리는 것보다, 필요할 때
/etc/hosts한 줄 토글하는 게 더 간단하고 안전함.
방법 2: 상황별 IP 직접 사용
| 위치 | 접속 방법 | 이유 |
|---|---|---|
| 집 WiFi | ssh evan@192.168.1.100 |
같은 네트워크, 직접 연결 |
| 핫스팟/외부 | ssh evan@evanshlee.com |
포트포워딩 통해 접속 |
SSH Config로 자동화할 수도 있다:
Host home
HostName 192.168.1.100
User evan
Host remote
HostName evanshlee.com
User evanHost home
HostName 192.168.1.100
User evan
Host remote
HostName evanshlee.com
User evan방법 3: 공유기에서 NAT Loopback 활성화
공유기 설정에서 NAT Loopback(헤어핀 NAT)을 켜면 근본적으로 해결된다. 다만 모든 공유기가 지원하는 건 아니고, 공유기 설정을 건드려야 한다는 부담도 따른다.
방법 4: Split-horizon DNS (내부 DNS 서버)
내부 네트워크 전용 DNS 서버를 두고, evanshlee.com에 대해 로컬 IP를 응답하게 설정한다. 방법 1(/etc/hosts)의 자동화 버전이라고 보면 된다.
# 내부 DNS (Pi-hole, dnsmasq 등)에서
evanshlee.com → 192.168.1.100# 내부 DNS (Pi-hole, dnsmasq 등)에서
evanshlee.com → 192.168.1.100- 장점: 모든 내부 기기에 자동 적용. 기기별
/etc/hosts관리 불필요. - 단점: DNS 서버를 별도로 운영해야 함. 또한 기기가 WiFi에 연결되면 공유기가 DHCP(기기에 IP 주소를 자동 할당하는 기능)로 "이 DNS 서버를 써라"라고 알려주는데, 이 설정을 내부 DNS 서버 주소로 바꿔야 한다.
방법 5: 오버레이 네트워크 (Tailscale, WireGuard)
Tailscale이나 WireGuard 같은 VPN(가상 사설 네트워크) 도구를 쓰면, 기기들이 실제 위치와 관계없이 하나의 가상 네트워크에 속하게 된다. 집이든 카페든 동일한 가상 IP로 접속한다. 통신이 VPN 터널을 통해 직접 전달되므로 공유기의 NAT를 거치지 않고, NAT Loopback 문제가 원천적으로 발생하지 않는다.
- 장점: 위치 무관하게 동일한 접속 경험. 암호화 통신.
- 단점: 모든 기기에 클라이언트 설치 필요. 외부 서비스(Tailscale) 의존 또는 자체 WireGuard 서버 운영 필요.
요약
NAT Loopback 문제란?
같은 건물(네트워크) 안에서 건물 주소(공인 IP)로 우편(패킷)을 보내려 하면, 관리실(공유기)이 "이상하다"며 거부하는 현상
해결:
/etc/hosts에 로컬 IP 매핑 (가장 간편)- 집/밖에서 다른 IP 사용 (SSH Config 분리)
- 공유기 NAT Loopback 설정 (근본 해결, 지원 여부 확인 필요)
- Split-horizon DNS (기기 여러 대일 때 유리)
- 오버레이 네트워크 (위치 무관한 접속)
References
- RFC 3022 - Traditional IP Network Address Translator - NAT의 기본 설계: 내부→외부 단방향 세션 변환
- RFC 4787 - NAT Behavioral Requirements for UDP - Section 6에서 hairpin 동작 요구사항 정의 (REQ-9: "A NAT MUST support Hairpinning")
- RFC 5382 - NAT Behavioral Requirements for TCP - TCP에 대한 동일한 hairpinning 요구사항
- MikroTik RouterOS NAT Documentation - Hairpin NAT - Hairpin NAT 설정 예시 (srcnat + dstnat 규칙)와 비대칭 라우팅 문제 설명
- Wikipedia - Hairpin NAT - NAT hairpinning의 정의와 개요