VPS를 경유해 공인 IP 없는 홈서버를 외부에 열기
집 회선에는 고정 공인 IP가 없고 포트를 열기도 마땅치 않다. 공인 IP가 있는 VPS를 하나 두고, 거기로 온 요청을 WireGuard 터널을 타고 집으로 넘기는 구조로 만들었다.
VPS는 트래픽만 흘려보내므로 가장 싼 것으로 충분하다.
패킷이 지나가는 길

두 번 주소가 바뀐다. VPS에서 한 번, 집 안의 VPN 서버에서 한 번이다.
PREROUTING이어야 하는 이유
목적지를 바꾸는 DNAT는 PREROUTING에서 해야 한다.
커널은 패킷을 받으면 목적지를 보고 "이건 내가 처리할 것"인지 "다른 데로 보낼 것"인지 정한다. 이 판단이 끝난 뒤에 주소를 바꾸면 이미 늦었다. 커널은 이미 로컬 처리로 결정했으므로 포워딩이 일어나지 않는다.
PREROUTING은 그 판단 전에 실행된다. 여기서 목적지를 집 쪽으로 바꿔놓으면 커널이 "다른 데로 보낼 것"으로 결정하고 FORWARD 체인을 태운다.
MASQUERADE가 없으면 응답이 사라진다
출발지를 바꾸는 것도 필요하다. 이게 없으면 요청은 집까지 잘 가는데 응답이 안 돌아온다.
집 안의 NPM이 받은 패킷의 출발지가 사용자의 공인 IP 그대로라면, NPM은 그 주소로 직접 답하려 한다. 그런데 집 네트워크의 라우팅 테이블에는 그리로 가는 경로가 없다. 응답이 그냥 유실된다.
MASQUERADE로 출발지를 터널 쪽 주소로 바꿔두면 NPM은 항상 그 주소로 답한다. 되돌아온 응답은 NAT 추적을 따라 원래 사용자에게 전달된다.
VPS 쪽 규칙
# postup.sh
sysctl -w net.ipv4.ip_forward=1
# 목적지를 VPN 서버로
iptables -t nat -I PREROUTING 1 -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.10.11.1:80
iptables -t nat -I PREROUTING 1 -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.10.11.1:443
# 출발지를 VPS의 터널 주소로
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
# postdown.sh
iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to-destination 10.10.11.1:80
iptables -t nat -D PREROUTING -i eth0 -p tcp --dport 443 -j DNAT --to-destination 10.10.11.1:443
iptables -t nat -D POSTROUTING -o wg0 -j MASQUERADE
-I PREROUTING 1 로 맨 앞에 끼워넣는다. -A 로 뒤에 붙이면 앞선 규칙에 먼저 걸릴 수 있다.
80과 443만 넘긴다. VPS의 SSH 포트까지 넘기면 VPS에 들어갈 방법이 없어진다.
VPN 서버 쪽 규칙
# postup.sh
#!/bin/bash
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -I PREROUTING 1 -i $1 -s 10.10.11.99 -p tcp --dport 80 -j DNAT --to-destination <집 NPM 주소>:80
iptables -t nat -I PREROUTING 1 -i $1 -s 10.10.11.99 -p tcp --dport 443 -j DNAT --to-destination <집 NPM 주소>:443
iptables -t nat -A POSTROUTING -o eth0 -s 10.10.11.99 -j MASQUERADE
-s 10.10.11.99 조건이 붙어 있다. VPS의 터널 주소다. 이 조건이 없으면 터널 안의 다른 peer가 보낸 것까지 집 안 NPM으로 넘어간다. VPS에서 온 것만 통과시킨다.
-i $1 은 WireGuard가 넘겨주는 인터페이스 이름이다. 스크립트를 인터페이스에 묶지 않는다.
NPM
집 쪽에서 실제로 받는 건 Nginx Proxy Manager다.
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: always
ports:
- '80:80'
- '443:443'
- '81:81'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
여기까지 오면 그 다음은 평범한 reverse proxy다. 도메인을 보고 내부 서비스로 넘긴다. 인증서도 NPM이 받는다.
81번 관리 포트는 VPS에서 넘기지 않는다. 내부에서만 접근한다.