홈랩 WireGuard VPN은(는) 무엇을 하나요?
WireGuard는 빠르고 현대적인 VPN 프로토콜입니다. 홈 네트워크에 원격으로 접속하기에 적합한 선택으로, OpenVPN보다 설정이 간단하고 대부분의 대안보다 빠릅니다.
모든 구성 예시는 일반적인 설정을 보여줍니다. 특히 iptables 포워딩 규칙과 키 파일 권한을 시스템에 적용하기 전에 각 명령어를 검토하고, 유지 관리 시간에 변경 사항을 적용하세요.
사용 시기
- 라즈베리 파이, 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 = <paste_server_private_key_here>
# 범위가 지정된 포워딩 규칙: 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 = <phone_public_key>
AllowedIPs = 10.8.0.2/32
[Peer]
# 노트북 — 실제 노트북 공개 키로 대체
PublicKey = <laptop_public_key>
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 = <phone_private_key>
Address = 10.8.0.2/32
DNS = 192.168.1.2 # 선택 사항: 터널을 통해 Pi-hole을 DNS로 사용
[Peer]
PublicKey = <server_public_key>
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 → Add Tunnel
Interface Keys: Generate (키 쌍 자동 생성)
Listen Port: 51820
Interface Address: 10.8.0.1/24
# Add Peer (클라이언트당 하나):
Public Key: <클라이언트 공개 키>
Allowed IPs: 10.8.0.2/32
# WireGuard 인터페이스 할당:
Interfaces → Assignments → Add (wg0 선택)
인터페이스 활성화, IP 불필요 (터널 구성에서 설정)
# 방화벽 규칙:
WAN → UDP 포트 51820 인바운드 허용 (클라이언트가 서버에 연결 가능)
WireGuard 인터페이스 → 액세스하려는 LAN 네트워크로의 트래픽 허용
홈 서버용 DDNS (Dynamic 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 (mode 600)에 저장하고, root 소유의 작은 스크립트 사용:
# /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 파일에 저장
- 범위가 지정된 iptables 포워딩 규칙(wg0 인바운드만) 사용, 포괄적인 FORWARD ACCEPT 금지
- Pi-hole의 IP를 클라이언트 구성의
DNS =로 추가하여 VPN을 통한 광고 차단 활성화 - 주기적으로 서버 키 쌍을 교체하고 모든 클라이언트 구성 업데이트
관련 스킬
- homelab-network-setup
- homelab-vlan-segmentation
- homelab-pihole-dns