VPN vs 포트포워딩에서 “홈서버 외부 접속의 기본값은 VPN”이라고 결론 냈다. 그런데 막상 시작하려 하면 “직접 구축”이라는 말의 무게에 멈칫하게 된다. 키 쌍 생성, 설정 파일 작성, peer마다 IP 배정, 라우팅 규칙… 순정 WireGuard의 진입 장벽은 분명히 있다. wg-easy는 이 전부를 웹 UI 뒤로 숨긴다. 이 글은 wg-easy로 VPN 허브를 세우는 실전 과정이다.
순정 WireGuard와 wg-easy
WireGuard 자체는 이미 충분히 단순한 프로토콜이다. 문제는 운영이다. 기기를 하나 추가할 때마다 키를 만들고, 서버 설정에 peer 블록을 더하고, 클라이언트 설정 파일을 손으로 써야 한다. 가족 폰 세 대만 붙여도 이 과정이 번거롭다.
wg-easy는 순정 WireGuard 위에 웹 관리 UI를 씌운 것이다. 기기 추가가 버튼 하나, 클라이언트 설정은 QR 코드로 나온다. 트래픽 자체는 그대로 커널 WireGuard로 흐르므로 성능 손해는 없고, 번거로운 관리만 사라진다. 다만 UI라는 공격면이 하나 생기므로, 관리 UI 포트는 인터넷에 절대 열지 않는다 — 내부망이나 VPN 안에서만 접근하는 것이 전제다.
올리기: 도커 컴포즈
도커 컴포즈 운영 원칙에서 정리한 “서비스 하나, 폴더 하나” 구조 그대로 올린다.
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:latest
environment:
- WG_HOST=내공인IP_또는_도메인 # 외부에서 접속할 주소
- WG_PORT=51821 # WireGuard UDP 포트
- WG_DEFAULT_ADDRESS=10.10.1.x # VPN 클라이언트 IP 대역
- WG_DEFAULT_DNS=1.1.1.1,8.8.8.8 # 클라이언트가 쓸 DNS
- PASSWORD_HASH=... # UI 로그인 비밀번호 해시
volumes:
- ./config:/etc/wireguard
ports:
- "51821:51821/udp" # WireGuard 터널 (인터넷에 열 유일한 포트)
- "51821:51821/tcp" # 관리 UI (내부에서만)
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
restart: unless-stopped
핵심은 두 가지다. 첫째, WG_HOST는 외부에서 서버에 닿을 주소다. 고정 IP가 없다면 DDNS로 도메인을 걸어두는 것이 정석이다. 둘째, 공유기에서 포워딩할 포트는 WireGuard UDP 하나뿐이다. 이 한 포트가 VPN 입구의 전부이고, WireGuard는 유효한 키 없이 온 패킷에 응답하지 않으므로 스캐너에는 닫힌 포트처럼 보인다. restart: unless-stopped는 재부팅 후에도 VPN이 자동으로 살아나게 하는 안전장치다.
기기 추가: QR 스캔 한 번
올리고 나면 관리 UI(http://서버내부IP:51821)에 접속한다.
- New Client 버튼으로 기기를 만든다 (이름만 적으면 키는 자동 생성).
- 폰이라면 화면에 뜬 QR 코드를 공식 WireGuard 앱으로 스캔하면 끝이다.
- PC라면 설정 파일(
.conf)을 내려받아 WireGuard 클라이언트에 불러온다.
순정 WireGuard에서 손으로 하던 키 교환·IP 배정·설정 작성이 전부 이 화면 뒤로 사라졌다. 기기를 지울 때도 목록에서 삭제 한 번이면 해당 키가 즉시 무효화된다.
full tunnel과 split tunnel
WG_ALLOWED_IPS(클라이언트의 AllowedIPs)가 VPN의 성격을 결정한다.
| 설정 | 동작 | 쓰임새 |
|---|---|---|
0.0.0.0/0 (full tunnel) | 모든 인터넷 트래픽이 VPN 경유 | 카페·공항 등 신뢰 못 할 WiFi에서 전 구간 암호화 |
| 내부망 대역만 (split tunnel) | 집 서버 대역만 VPN, 나머지는 직접 | 집 서비스 접근만 필요할 때, 서버 대역폭 절약 |
full tunnel은 보안이 강하지만 모든 트래픽이 집을 거쳐 나가므로 집 회선 업로드 속도와 데이터에 의존한다. 반대로 내부 서비스 접근만 목적이라면 split tunnel이 가볍다. 클라이언트가 VPN 안에서 쓸 DNS는 WG_DEFAULT_DNS로 지정하는데, 공개 DNS(1.1.1.1 등)를 줄 수도 있고 내부 DNS 서버를 가리켜 내부 도메인을 해석하게 할 수도 있다.
내 실측 구성
참고로 이 글을 쓰는 서버의 실제 wg-easy 설정을 공유한다. 인터넷에 연 포트는 WireGuard용 UDP 51821 하나뿐이고 SSH를 포함한 나머지는 0개다. WG_ALLOWED_IPS는 0.0.0.0/0(full tunnel), 클라이언트 대역은 10.10.1.x, DNS는 1.1.1.1·8.8.8.8을 물려 뒀다. 관리는 dockge로 하고 올해 3월 구축한 뒤로 손댈 일이 거의 없었다. 한 가지 솔직한 부분은, 현재 등록된 클라이언트가 1개뿐이고 그나마 상시 연결이 아니라 밖에서 필요할 때만 켜는 방식이라는 것이다 — wg show의 최근 핸드셰이크 기록이 비어 있는 게 내겐 정상 상태다. 또 WG_HOST에 도메인이 아니라 공인 IP를 직접 박아둔 탓에, IP가 바뀌면 끊긴다는 그 숙제는 여전히 남아 있다. 구축이 쉬웠던 것과 운영을 완성하는 것은 별개라는 걸, 이 빈칸이 매번 상기시킨다.
결론: 더 이상 어렵지 않다
“VPN 직접 구축”은 한때 OpenVPN의 복잡한 인증서 체계를 떠올리게 하는 말이었지만, WireGuard와 wg-easy 조합에서는 도커 컴포즈 한 번과 QR 스캔으로 끝나는 일이 됐다. 어렵지 않으니, 집 안에서만 쓸 서비스는 포트를 여는 대신 이 터널 뒤에 두자. 공개가 꼭 필요한 것만 리버스 프록시로 내보내고, 나머지는 전부 VPN 안에 숨기는 것 — 그것이 노출면을 최소로 유지하는 가장 단순한 길이다.