스크랩

공인 IP 없이도 집에서 서비스 운영하기: CGNAT를 우회하는 VPS 브리지 구축기

공인 IP 없이도 집에서 서비스 운영하기: CGNAT를 우회하는 VPS 브리지 구축기

한눈에 보는 요약

집에 서버를 두고 서비스를 외부에 공개하려는데, 포트 포워딩을 아무리 설정해도 외부에서 접속이 안 되는 경험을 해보셨나요? 원인은 대부분 CGNAT(Carrier-Grade NAT) 입니다. 이 글에서 소개하는 해법은 간단합니다. 공인 IP를 가진 저렴한 VPS를 '브리지'로 두고, 집 서버가 먼저 WireGuard 터널을 연결하는 것입니다.

  • 집에서 밖으로 나가는 연결만 사용하므로 가정에 고정 공인 IP가 필요 없음
  • 브리지는 목적지 주소만 바꿔 전달(DNAT)하므로 실제 클라이언트 IP가 그대로 보존됨
  • 홈랩은 정책 기반 라우팅으로 외부 요청의 응답만 터널로, 자체 트래픽은 가정용 라우터로 분리
  • 실제 사례(스페인 북부 홈랩 ↔ 프랑스 데이터센터 VPS)에서 추가 지연은 RTT 기준 39ms

왜 포트 포워딩이 더 이상 통하지 않을까

예전에는 셀프 호스팅이 꽤 단순했습니다. 라우터에서 포트 포워딩을 열고, ISP가 공인 IP를 바꿀 때마다 동적 DNS로 도메인 레코드를 갱신하면 끝이었죠.

문제는 IPv4 주소 고갈입니다. 주소가 모자라니 통신사는 여러 가정이 하나의 공인 IP를 공유하는 CGNAT를 도입했습니다. 이렇게 되면 통신사 네트워크에 두 번째 NAT 계층이 생기고, 이 계층이 우리 집 라우터의 사설 주소를 다시 변환합니다. 결국 우리가 제어할 수 없는 지점에서 주소 변환이 일어나기 때문에, 가정용 라우터의 포트 포워딩만으로는 외부에서 들어오는 요청을 받을 방법이 없습니다.

참고로 ISP에서 고정 IP를 구매하는 정공법도 있습니다. 다만 스페인 기준 월 20유로 수준이라, 저렴한 VPS 한 대를 브리지로 쓰는 편이 비용 면에서 유리할 수 있습니다.

핵심 아이디어: 터널을 '안에서 밖으로' 연다

셀프 호스팅의 지향점은 소프트웨어와 하드웨어를 직접 소유해서 클라우드 기업 의존과 검열에서 벗어나는 것입니다. 그렇다면 CGNAT 뒤에서도 이 목표를 지킬 방법이 필요합니다.

구성은 이렇습니다.

  1. 홈랩: 스페인 북부 어머니 집 지하실의 중급 서버. 실제 서비스가 여기서 돌아갑니다.
  2. 브리지: 프랑스 데이터센터의 저렴한 VPS. 공인 IP를 가집니다.
  3. 터널: 리눅스 커널에서 동작하는 VPN인 WireGuard로 양방향 연결.

중요한 포인트는 연결을 시작하는 쪽이 홈랩이라는 점입니다. 나가는 연결은 CGNAT도 막지 않으므로, 집에 고정 공인 IP가 없어도 터널이 유지됩니다. 터널이 서면 브리지로 들어온 외부 트래픽이 홈랩까지 흘러갑니다.

브리지(VPS) 쪽 설정

브리지의 wg0.conf 핵심 값은 다음과 같습니다.

  • 터널 주소: 10.0.0.1/24
  • 수신 포트: 51820
  • 홈랩 피어의 AllowedIPs: 10.0.0.2/32

그리고 PostUp에서 커널 수준의 NAT/포워딩 규칙을 추가하고, PostDown에서 정리합니다. 규칙의 논리는 이렇습니다.

  • 공인 인터페이스 ens3로 들어오는 UDP 51820은 VPN 자신을 위해 남겨 둡니다.
  • TCP 2222는 브리지 자체의 SSH 관리용으로 남겨 둡니다. (브리지에 문제가 생겼을 때 들어갈 문을 반드시 확보해 두세요.)
  • 그 외 모든 포트는 DNAT로 목적지를 10.0.0.2(홈랩)로 바꿔 전달합니다.
  • wg0와 ens3 사이의 포워딩을 허용합니다.

여기서 가장 실용적인 디테일은 출발지 주소(SNAT)를 건드리지 않는다는 점입니다. 덕분에 홈랩의 웹 서버 로그나 rate limit, 지역 기반 로직이 실제 방문자 IP를 그대로 볼 수 있습니다. 단순히 SNAT까지 걸어버리면 모든 요청이 브리지 IP에서 온 것처럼 보여 운영 정보가 사라집니다.

새 서버를 막 띄운 상태라면 기본 보안 설정(SSH 키, 방화벽 등)을 먼저 잡아두는 것을 권합니다. 원문은 "First Steps on a New Server" 문서를 참고 자료로 안내합니다.

홈랩 쪽 설정: 정책 기반 라우팅이 관건

홈랩의 wg0.conf는 터널 주소를 10.0.0.2/24로 잡고, 여기서 한 가지 중요한 선택을 합니다.

  • Table = off — WireGuard가 기본 라우팅 테이블을 알아서 건드리지 못하게 막습니다.
  • 대신 별도 라우팅 테이블 200 을 직접 구성합니다.
ip route add default dev wg0 table 200
ip rule add from 10.0.0.2 table 200

피어(브리지) 설정은 Endpoint = 213.32.19.229:51820, AllowedIPs = 0.0.0.0/0, PersistentKeepalive = 25입니다.

이 조합이 만들어내는 동작이 이 구성의 백미입니다.

  • 외부에서 들어온 요청의 응답은 출발지가 10.0.0.2이므로 테이블 200을 타고 터널로 되돌아갑니다. (요청이 들어온 경로 그대로 나가야 통신이 성립하니까요.)
  • 홈랩이 스스로 만들어내는 트래픽(패키지 업데이트, 백업 업로드 등)은 규칙에 걸리지 않아 평소처럼 가정용 라우터로 나갑니다.

덕분에 VPS의 대역폭을 불필요하게 소모하지 않으면서도, 외부 공개는 정상적으로 동작합니다. PersistentKeepalive = 25는 CGNAT와 방화벽의 세션 테이블이 만료되지 않도록 주기적으로 신호를 보내는 역할을 합니다.

전체 설정은 작성자의 homelab 저장소에 공개되어 있고, 접속 규칙도 깔끔합니다. ssh.alvarezrosa.com의 22번 포트로 접속하면 홈랩, 2222번 포트로 접속하면 브리지에 도달합니다.

장애는 세 곳에서 납니다

이런 구성에서 고장이 날 수 있는 지점은 딱 셋입니다. 각각에 대한 대비가 있어야 실제로 운영할 수 있습니다.

1. 홈랩 장애

cron 작업이 SSH가 정상 동작하는지 주기적으로 확인하고, 응답이 없으면 서버를 재부팅합니다. 물리적으로 멀리 떨어진 서버(어머니 집 지하실!)를 운영할 때 특히 현실적인 안전장치입니다.

2. 브리지 장애

브리지가 죽으면 외부 접근 경로 전체가 끊깁니다. 그래서 Cloudflare 터널이나 Tailscale로 홈랩에 직접 접근하는 예비 경로를 만들어 두는 것을 권장합니다. 복구를 위해 들어갈 문이 하나뿐이면 안 됩니다.

3. 터널 장애

짧은 단절은 WireGuard가 자동으로 핸드셰이크를 다시 수행하며 스스로 회복합니다. 긴 단절은 위의 홈랩·브리지 장애 대응 절차로 처리합니다.

정리하며

이 구성의 매력은 거창한 장비 없이, 가장 싼 VPS 한 대와 WireGuard만으로 CGNAT를 넘어선다는 데 있습니다. 비용으로 따지면 고정 IP 구매보다 저렴할 가능성이 높고, 39ms라는 추가 지연은 대부분의 셀프 호스팅 용도에서 충분히 감당할 만한 수준입니다.

직접 시도해 본다면 순서는 이렇습니다.

  1. 공인 IP가 있는 저렴한 VPS를 확보하고 기본 보안 설정을 마칩니다.
  2. 브리지의 SSH를 2222 같은 별도 포트로 옮겨 관리 통로를 먼저 확보합니다.
  3. 양쪽에 WireGuard 터널을 세우고 핸드셰이크를 확인합니다.
  4. 브리지에 DNAT/포워딩 규칙을 넣되 SNAT는 걸지 않습니다.
  5. 홈랩에 Table = off + 테이블 200 정책 라우팅을 적용합니다.
  6. 마지막으로 예비 접근 경로와 헬스체크 cron까지 붙여 마무리합니다.

CGNAT 때문에 셀프 호스팅을 포기했던 분이라면, 다시 한 번 도전해 볼 만한 구성입니다.


원문: https://news.hada.io/topic?id=34114