홈랩 WireGuard VPN은(는) 무엇을 하나요?
WireGuard는 빠르고 현대적인 VPN 프로토콜입니다. 홈 네트워크에 원격으로 접속하기에 적합한 선택이며, OpenVPN보다 설정이 간단하고 대부분의 대안보다 빠릅니다.
모든 구성 예제는 일반적인 설정을 보여줍니다. 특히 iptables 포워딩 규칙과 키 파일 권한과 같은 각 명령을 시스템에 적용하기 전에 검토하고, 유지보수 기간에 변경하십시오.
사용 시기
- Raspberry Pi, Linux 호스트, pfSense 또는 라우터에 WireGuard 서버 설정
- WireGuard 키 쌍 생성 및 피어 구성 파일 작성
- 전화 또는 노트북에서 홈 네트워크로의 원격 액세스 구성
- 분할 터널링(홈 트래픽만 라우팅)과 전체 터널(모든 트래픽 라우팅) 설명
- 연결되지 않는 WireGuard 연결 문제 해결
- 여러 클라이언트에 대한 피어 구성 생성 자동화
WireGuard 작동 방식
사용자 전화 (WireGuard 클라이언트)
│
│ 암호화된 UDP 터널 (포트 51820)
│
사용자 홈 라우터 (WireGuard 서버 — 공인 IP 또는 DDNS 필요)
│
사용자 홈 네트워크 (192.168.1.0/24, NAS, Pi 등)
모든 장치는 키 쌍(공개 키 + 개인 키)을 가집니다.
서버는 각 클라이언트의 공개 키를 알고 있습니다.
클라이언트는 서버의 공개 키와 엔드포인트(IP:포트)를 알고 있습니다.
트래픽은 중앙 서버나 인증 기관 없이 종단 간 암호화됩니다.
서버 설정 (Linux)
# WireGuard 설치
sudo apt update && sudo apt install wireguard -y
# 서버 키 쌍 생성 — 처음부터 개인 권한으로 파일 생성
sudo mkdir -p /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
# 서버 구성 작성 — 실제 개인 키 값으로 대체
# 개인 키를 버전 관리에 저장하거나 공유하지 마십시오
sudo tee /etc/wireguard/wg0.conf << 'EOF'
[Interface]
Address = 10.8.0.1/24 # VPN 서브넷 — 서버는 .1을 가짐
ListenPort = 51820
PrivateKey = <서버_개인_키_붙여넣기>
# 범위가 지정된 포워딩 규칙: VPN 트래픽의 입/출력만 허용, 포괄적인 FORWARD ACCEPT 금지
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
[Peer]
# 전화 — 실제 전화 공개 키로 대체
PublicKey = <전화_공개_키>
AllowedIPs = 10.8.0.2/32
[Peer]
# 노트북 — 실제 노트북 공개 키로 대체
PublicKey = <노트북_공개_키>
AllowedIPs = 10.8.0.3/32
EOF
sudo chmod 600 /etc/wireguard/wg0.conf
# eth0을 실제 아웃바운드 인터페이스 이름으로 대체
# 확인: ip route show default
# IP 포워딩 활성화 (서버를 통한 트래픽 라우팅에 필수)
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
# WireGuard 시작 및 부팅 시 활성화
sudo wg-quick up wg0
sudo systemctl enable wg-quick@wg0
클라이언트 구성
# 각 클라이언트 장치에 대해 고유한 키 쌍 생성
# 클라이언트에서 실행하거나, 서버에서 생성하여 개인 키를 안전하게 전송 — 절대 평문으로 전송하지 않음
umask 077
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
# 클라이언트 구성 파일 (phone_wg0.conf):
[Interface]
PrivateKey = <전화_개인_키>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # 선택 사항: 터널을 통해 Pi-hole을 DNS로 사용
[Peer]
PublicKey = <서버_공개_키>
Endpoint = your-home-ip.ddns.net:51820 # 공인 IP 또는 DDNS 호스트 이름
AllowedIPs = 192.168.1.0/24 # 분할 터널: 홈 네트워크 트래픽만
# AllowedIPs = 0.0.0.0/0, ::/0 # 전체 터널: 모든 트래픽이 VPN을 통과
PersistentKeepalive = 25 # NAT 홀 유지 (모바일 클라이언트에 필수)
분할 터널 vs 전체 터널
# 분할 터널: AllowedIPs = 192.168.1.0/24
홈 네트워크로 향하는 트래픽만 VPN을 통해 전송됩니다.
인터넷 트래픽(YouTube, Spotify)은 직접 전송 — 모바일에서 더 나은 성능.
최적: "NAS와 Pi에 어디서든 접속하고 싶을 때"
# 전체 터널: AllowedIPs = 0.0.0.0/0, ::/0
모든 트래픽이 홈 인터넷 연결을 통해 전송됩니다.
유용한 경우: 홈 DNS/Pi-hole 광고 차단 사용.
단점: 홈 업로드 속도가 모든 곳에서 병목 현상이 됩니다.
# 다중 서브넷 분할 터널 (가장 일반적인 홈랩 사용 사례):
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
모든 VLAN을 터널을 통해 라우팅하고 인터넷은 직접 유지합니다.
키 생성 및 피어 관리
import subprocess
def generate_keypair() -> tuple[str, str]:
"""WireGuard 키 쌍 생성. (개인_키, 공개_키) 반환."""
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
public = subprocess.run(
["wg", "pubkey"], input=private.encode(), capture_output=True
).stdout.decode().strip()
return private, public
def generate_preshared_key() -> str:
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
def build_client_config(
client_private_key: str,
client_vpn_ip: str, # 예: "10.8.0.3"
server_public_key: str,
server_endpoint: str, # 예: "home.example.com:51820"
allowed_ips: str = "192.168.1.0/24",
dns: str = "",
) -> str:
dns_line = f"DNS = {dns}\n" if dns else ""
return f"""[Interface]
PrivateKey = {client_private_key}
Address = {client_vpn_ip}/32
{dns_line}
[Peer]
PublicKey = {server_public_key}
Endpoint = {server_endpoint}
AllowedIPs = {allowed_ips}
PersistentKeepalive = 25
"""
def build_server_peer_block(
client_public_key: str,
client_vpn_ip: str,
comment: str = "",
) -> str:
comment_line = f"# {comment}\n" if comment else ""
return f"""
{comment_line}[Peer]
PublicKey = {client_public_key}
AllowedIPs = {client_vpn_ip}/32
"""
개인 키를 소스 제어에서 절대 보관하지 마십시오. 이 스크립트를 사용하는 경우 키 자료를 모드 600으로 파일에 쓰고 절대 로그하거나 출력하지 마십시오.
pfSense / OPNsense WireGuard
# pfSense: VPN → WireGuard → 터널 추가
인터페이스 키: 생성 (키 쌍 자동 생성)
수신 포트: 51820
인터페이스 주소: 10.8.0.1/24
# 피어 추가 (클라이언트당 하나):
공개 키: <클라이언트 공개 키>
허용 IP: 10.8.0.2/32
# WireGuard 인터페이스 할당:
인터페이스 → 할당 → 추가 (wg0 선택)
인터페이스 활성화, IP는 필요 없음 (터널 구성에서 설정)
# 방화벽 규칙:
WAN → UDP 포트 51820 인바운드 허용 (클라이언트가 서버에 도달할 수 있도록)
WireGuard 인터페이스 → 접근하려는 LAN 네트워크로의 트래픽 허용
홈 서버용 DDNS (동적 DNS)
대부분의 홈 인터넷 연결은 동적 IP를 사용합니다. DDNS를 사용하면 IP 변경 후에도 VPN 엔드포인트에 접근할 수 있습니다.
# 옵션 1: Cloudflare DDNS — 자격 증명을 인라인이 아닌 시크릿 파일에 저장
# env 파일을 사용하는 docker-compose 항목:
ddns-updater:
image: qmcgaw/ddns-updater
env_file: ./ddns.env # zone_id와 token을 여기에 저장, compose에는 저장하지 않음
restart: unless-stopped
# ddns.env (chmod 600, git에 커밋하지 않음):
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
# 옵션 2: DuckDNS (무료, 간단)
duckdns.org에 가입 → 토큰과 서브도메인(myhome.duckdns.org) 획득
토큰을 /etc/ddns.env (모드 600)에 저장한 후, 루트 소유의 작은 스크립트 사용:
# /usr/local/bin/update-duckdns
#!/bin/sh
set -eu
. /etc/ddns.env
curl --fail --silent --show-error --max-time 10 \
--get "https://www.duckdns.org/update" \
--data-urlencode "domains=myhome" \
--data-urlencode "token=${DUCKDNS_TOKEN}" \
--data-urlencode "ip="
# Cron 작업:
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
문제 해결
# WireGuard 상태 및 마지막 핸드셰이크 확인
sudo wg show
# "latest handshake"가 없거나 매우 오래된 경우 터널이 연결되지 않은 것입니다.
# 확인할 사항:
# 1. 라우터/방화벽에서 UDP 포트 51820이 열려 있습니까?
sudo ufw status # 또는 pfSense/UniFi 방화벽 규칙 확인
# 2. 클라이언트 구성에 있는 서버 공개 키가 올바릅니까?
sudo wg show wg0 public-key # 클라이언트 구성에 있는 것과 비교
# 3. 서버에서 IP 포워딩이 활성화되어 있습니까?
cat /proc/sys/net/ipv4/ip_forward # 1이어야 함
# 4. 클라이언트 AllowedIPs가 연결하려는 IP를 포함합니까?
# AllowedIPs = 192.168.1.0/24이고 192.168.3.5에 연결하려는 경우 라우팅되지 않습니다.
# WireGuard 오류에 대한 커널 로그 확인
dmesg | grep wireguard
# WireGuard 다시 시작
sudo wg-quick down wg0 && sudo wg-quick up wg0
안티 패턴
# 나쁨: 개인 키를 버전 관리에 저장하거나 공유하는 경우
# 개인 키는 비밀번호와 동일합니다 — git에 절대 커밋하지 마십시오
# 나쁨: 모바일에서 AllowedIPs = 0.0.0.0/0을 영향 고려 없이 사용하는 경우
# 전체 터널은 모든 모바일 트래픽을 홈 업로드를 통해 라우팅합니다 — 일반적으로 느림
# 나쁨: 모바일 클라이언트에서 PersistentKeepalive를 설정하지 않는 경우
# NAT 뒤의 모바일 클라이언트는 이것 없이 유휴 터널이 끊어집니다
# 나쁨: 방화벽에서 포트 51820을 열었지만 서버에서 IP 포워딩을 잊어버린 경우
# 터널은 연결되지만 트래픽이 라우팅되지 않아 디버깅이 혼란스러움
# 나쁨: 여러 클라이언트 장치에서 키 쌍을 공유하는 경우
# 각 장치는 고유한 키 쌍을 가져야 합니다 — 공유 키는 보안 모델을 손상시킵니다
# 나쁨: 광범위한 "FORWARD ACCEPT" iptables 규칙을 사용하는 경우
# 포워딩 규칙을 wg0 인터페이스와 방향으로만 범위를 한정하십시오
모범 사례
- 각 클라이언트 장치에 대해 고유한 키 쌍 생성 — 키 재사용 금지
- 모바일의 경우 분할 터널링 사용 (
AllowedIPs = <홈 서브넷>) - 모든 모바일 클라이언트에
PersistentKeepalive = 25설정 - ISP가 동적 IP를 할당하는 경우 DDNS 사용; 자격 증명을 인라인이 아닌 env 파일에 저장
- 포괄적인 FORWARD ACCEPT 대신 범위가 지정된 iptables 포워딩 규칙 사용(wg0에서 인바운드만)
- 클라이언트 구성에서
DNS =로 Pi-hole의 IP를 추가하여 VPN을 통해 광고 차단 - 주기적으로 서버 키 쌍을 교체하고 모든 클라이언트 구성 업데이트
관련 스킬
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns