WireGuard DNS 문제 3가지와 해결 방법
WireGuard 터널은 연결됐지만 DNS가 작동하지 않거나 로컬 라우터로 쿼리가 새나요? 3가지 실패 원인을 구분하고 라우팅과 resolver 설정을 바로잡습니다.
WireGuard 터널이 올라오는 순간 DNS가 중단되는 이유
WireGuard를 통한 DNS는 3가지 방식으로 실패하며, 각각 별도의 해결 방법이 있습니다. 아무것도 확인되지 않거나, 이름은 확인되지만 쿼리가 터널 외부로 나가거나, 인터페이스가 시작된 뒤 몇 초 만에 클라이언트 자체의 resolver manager가 설정을 덮어씁니다. 터널 자체가 문제인 경우는 거의 없습니다. 문제는 클라이언트가 어떤 resolver에 질의할지 지정하는 한 줄과 해당 resolver까지 패킷이 이동하는 방식을 결정하는 라우팅입니다.
WireGuard는 IP 패킷을 전달하지만 DNS(domain name system, example.com과 같은 이름을 IP 주소로 변환하는 서비스)는 알지 못합니다. 클라이언트 [Interface] 블록의 DNS = 줄은 WireGuard 설정이 아닙니다. 이 줄은 인터페이스를 시작하는 shell wrapper인 wg-quick이 읽으며, wg-quick은 터널이 활성화된 동안 클라이언트의 resolver 설정을 수정하고 wg-quick down에서 이를 복원합니다. 따라서 아래의 모든 문제는 라우팅 문제이거나 wg-quick 문제이며, 암호화 문제는 아닙니다. 터널 자체를 아직 구성하지 않았다면 자체 VPS에서 호스팅하는 WireGuard VPN부터 시작한 다음 이 페이지로 돌아오십시오.
DNS를 전혀 변경하기 전에 터널이 정상인지 확인하십시오.
sudo wg show
ping -c 3 10.8.0.1
ping -c 3 1.1.1.1wg show에는 최근 latest handshake이 있는 peer가 표시되어야 하며, 두 ping 모두 응답해야 합니다. ping 1.1.1.1이 시간 초과되면 DNS 문제가 아니라 forwarding 또는 NAT(network address translation) 문제입니다. resolver 설정을 아무리 변경해도 해결되지 않습니다. 여기의 모든 예제에서는 10.8.0.0/24를 터널 서브넷으로, 10.8.0.1를 서버의 터널 주소로 사용합니다. 사용 중인 값으로 바꾸십시오.
첫 번째 장애: resolver가 응답하지 않아 아무것도 확인되지 않음
증상은 명확합니다. ping 1.1.1.1은 작동하지만, curl https://example.com은 다음을 반환합니다.
curl: (6) Could not resolve host: example.com클라이언트에서 터널 resolver에 직접 질의합니다. dig은 Ubuntu와 Debian의 dnsutils 패키지에 포함되어 있습니다.
dig +short @1.1.1.1 example.com
dig +short @10.8.0.1 example.com첫 번째 명령은 주소를 반환합니다. 이는 패킷이 터널을 통해 인터넷에 도달한다는 뜻입니다. 두 번째 명령은 아무것도 반환하지 않고 ;; communication timed out; no servers could be reached을 출력합니다. 진단 결과는 간단합니다. 클라이언트가 10.8.0.1을 사용하도록 설정되어 있지만, 10.8.0.1이(가) UDP port 53에서 응답하지 않습니다.
원인은 2가지입니다. 서버에서 resolver가 실행되고 있지 않거나, 서버 방화벽이 질의가 도착하기 전에 삭제하고 있을 수 있습니다. 서버에서 두 가지를 모두 확인합니다.
sudo ss -ulnp | grep ':53'
sudo nft list ruleset실행 중이며 올바르게 바인딩된 resolver에는 10.8.0.1:53 또는 0.0.0.0:53이(가) 포함된 행이 표시됩니다. Ubuntu에서는 보통 127.0.0.53:53이(가) 문제입니다. 이는 systemd-resolved stub listener로, loopback 주소에 바인딩되며 다른 시스템에서 의도적으로 접근할 수 없습니다. 유일한 resolver가 이 stub인 서버를 VPN 클라이언트가 사용하도록 설정하면 정확히 이 timeout이 발생합니다.
해결 방법은 tunnel address에서 수신하는 resolver를 설정하고, peer가 해당 resolver에 접근할 수 있도록 firewall rule 1개를 추가하는 것입니다.
sudo apt update && sudo apt install -y unbound
printf 'server:\n interface: 10.8.0.1\n access-control: 10.8.0.0/24 allow\n' \
| sudo tee /etc/unbound/unbound.conf.d/wireguard.conf
sudo systemctl restart unbound
sudo ss -ulnp | grep '10.8.0.1:53'그런 다음 tunnel traffic에만 port를 엽니다. nftables를 사용하는 경우 /etc/nftables.conf의 input chain에 다음 2줄을 추가하고 sudo systemctl reload nftables로 다시 로드합니다.
udp dport 53 iifname "wg0" accept
tcp dport 53 iifname "wg0" acceptufw에서는 sudo ufw allow in on wg0 to any port 53이(가) 같은 작업을 수행합니다. port 53을 public internet에 절대 개방하지 마십시오. 개방된 recursive resolver는 며칠 내에 scanner에 발견되어 denial of service attack을 증폭하는 데 사용됩니다. 그러면 사용자가 알아차리기 전에 provider가 해당 traffic을 감지합니다.
클라이언트에서 dig +short @10.8.0.1 example.com을(를) 다시 실행합니다. 출력에 주소가 표시되면 resolver 경로가 작동하는 것입니다. 이제 클라이언트가 해당 resolver를 사용하도록 설정하면 됩니다. 클라이언트의 [Interface] block에 해당 줄을 추가하고 sudo wg-quick down wg0 && sudo wg-quick up wg0로 interface를 다시 시작합니다.
[Interface]
PrivateKey = <client private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1장애 2: 분할 터널은 resolver를 라우팅하지 않으므로 DNS가 유출됩니다
이번 문제는 더 심각합니다. 모든 기능이 정상적으로 작동하는 것처럼 보이기 때문입니다. 이름이 확인되고 페이지가 로드되지만, 쿼리는 신뢰하지 않으려던 로컬 네트워크를 통해 평문으로 전송됩니다.
두 가지 구성이 이 문제를 일으킵니다. 첫 번째는 AllowedIPs = 0.0.0.0/0, ::/0가 설정되어 있지만 DNS = 줄이 없는 클라이언트입니다. wg-quick는 자체 라우팅 테이블에 기본 경로를 설치하고 suppress_prefixlength 0를 사용하여 규칙을 추가합니다. 이 규칙은 더 구체적인 로컬 경로를 의도적으로 유지하므로 시스템이 프린터에 계속 연결할 수 있습니다. 클라이언트가 DHCP를 통해 알아낸 resolver는 일반적으로 192.168.1.1의 라우터이며, 이러한 로컬 경로 중 하나와 일치합니다. 트래픽은 터널을 통과하지만, 로컬 네트워크는 조회하는 전체 이름 목록을 계속 받습니다.
두 번째는 AllowedIPs = 10.8.0.0/24와 DNS = 9.9.9.9를 사용하는 분할 터널입니다. 9.9.9.9가 AllowedIPs 안에 없으므로 클라이언트에는 해당 주소로 터널을 통해 연결되는 경로가 없습니다. 따라서 쿼리는 첫 번째 경우와 동일하게 로컬 링크를 통해 나갑니다.
실제로 어떤 resolver가 응답하는지 확인합니다. whoami.akamai.net는 요청한 재귀 resolver의 IP 주소로 응답하는 공개 테스트 이름입니다. 따라서 응답을 서버의 공용 주소와 비교할 수 있습니다.
resolvectl status
dig +short whoami.akamai.net
sudo tcpdump -ni any -c 10 port 53resolvectl status는 링크마다 하나의 블록을 출력합니다. 이더넷 또는 무선 링크 블록에 여전히 Current DNS Server: 192.168.1.1가 표시되고 wg0 블록에는 아무것도 표시되지 않는다면 유출이 발생한 것입니다. dig +short whoami.akamai.net가 서버 주소가 아닌 가정용 broadband 주소를 반환하면 원격 측에서도 이를 확인할 수 있습니다. tcpdump 줄은 논쟁을 끝내는 증거입니다. 정상적인 출력에서는 모든 포트 53 패킷이 wg0로 전송되고, 유출이 발생하면 wlan0 또는 enp3s0으로 전송됩니다.
해결 방법은 두 부분으로 구성되며 둘 다 필요합니다. DNS을 터널 내부에 존재하는 주소로 설정하고, 해당 주소가 AllowedIPs 안에 포함되는지 확인합니다.
[Interface]
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 10.8.0.0/24, 10.20.0.0/1610.8.0.1는 10.8.0.0/24 안에 있으므로 쿼리가 암호화되어 서버로 전송됩니다. 분할 터널에서 공개 resolver를 사용해야 한다면 호스트 경로로 추가합니다: AllowedIPs = 10.8.0.0/24, 9.9.9.9/32. 그러면 패킷이 터널을 통해 전송됩니다. 다만 로컬 네트워크는 이전 세션을 통해 사용자가 해당 provider를 선택했다는 사실을 여전히 확인할 수 있습니다. 직접 운영하는 resolver를 사용하면 이 문제를 피할 수 있습니다.
resolver 할당은 직접 구축한 WireGuard와 중앙에서 조정하는 mesh의 차이 중 하나입니다. 이는 WireGuard와 Tailscale 비교에서 설명하는 트레이드오프의 일부입니다. 자체 호스팅 Headscale control server를 운영하면 키 자료를 제3자에게 넘기지 않고도 이러한 조정을 사용할 수 있습니다.
실패 3: Linux 클라이언트에서 resolvconf와 systemd-resolved가 충돌함
macOS, Windows, iOS 및 Android 클라이언트는 공식 앱을 통해 DNS =을 적용하므로 문제가 거의 발생하지 않습니다. Linux에서는 여러 resolver manager 중 어떤 것을 사용하는지 shell script가 추측해야 하므로 설정 적용이 실패할 수 있습니다.
첫 번째 실패는 명확하게 나타납니다. sudo wg-quick up wg0이 다음 메시지와 함께 중지됩니다.
resolvconf: command not foundwg-quick은 resolvconf을 호출하지만 해당 binary가 설치되어 있지 않습니다. systemd-resolved와 통신하는 implementation을 설치한 다음 interface를 다시 올립니다.
sudo apt install -y openresolv
sudo systemctl restart systemd-resolved
sudo wg-quick down wg0 && sudo wg-quick up wg0
resolvectl status wg0두 번째 실패는 조용히 발생하며, 문제를 해결하는 데 저녁 시간을 허비하게 만드는 원인입니다. interface는 올라오고 resolvectl status wg0은 DNS Servers: 10.8.0.1을 올바르게 표시하지만, lookup은 여전히 이전 resolver로 전송됩니다. systemd-resolved는 link마다 별도의 resolver list를 유지하고 query마다 link를 하나 선택합니다. 이름에 대한 default route로 표시된 link가 없으면 wireless link에 search domain이 설정되어 있고 현재 link에는 없기 때문에 wireless link의 resolver를 계속 사용합니다.
같은 단계에서 resolver를 설정하고 default route를 지정합니다. %i은 interface name으로 확장되므로 이 block은 모든 interface에서 수정 없이 작동합니다.
[Interface]
Address = 10.8.0.2/32
PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~.
PostDown = resolvectl revert %i이 방식으로 PostUp을 사용할 때는 DNS = line을 삭제합니다. 그렇지 않으면 두 mechanism이 resolver state를 기록하고 그중 하나만 나중에 정리합니다. ~. argument가 핵심입니다. 이 argument는 wg0을 모든 name에 대한 routing domain으로 지정하므로 systemd-resolved가 query마다 link를 선택하지 않고 모든 query를 해당 link로 전송합니다. 이를 확인합니다.
resolvectl status wg0정상적인 output에는 DNS Servers: 10.8.0.1과 Default Route: yes이 포함됩니다. Default Route가 no을 읽는다면 resolvectl domain 부분이 실행되지 않은 것이므로 다시 link selection 방식으로 동작하고 있는 상태입니다.
한 가지 사례를 더 확인해야 합니다. /etc/resolv.conf이 /run/systemd/resolve/stub-resolv.conf을 가리키는 symlink가 아니라 실제 file이라면 다른 component가 해당 file을 관리하는 것입니다. 일반적으로 NetworkManager 또는 container runtime이 원인입니다. 다른 문제를 debug하기 전에 ls -l /etc/resolv.conf을 실행합니다. network change가 발생할 때마다 해당 file을 다시 작성하는 tool이 작업 내용을 가장 불리한 시점에 취소할 수 있기 때문입니다.
터널을 통한 자체 필터링 resolver 업그레이드
쿼리가 안정적으로 터널을 통과하면 원격지의 resolver가 제어 지점이 됩니다. 해당 위치에서 AdGuard Home을 실행하면 연결된 모든 장치에 클라이언트 소프트웨어나 장치별 설정 없이 차단 목록 필터링과 쿼리 로그를 제공할 수 있습니다. 2026년 7월에 확인된 공식 설치 스크립트는 한 줄입니다.
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v처음 실행할 때 설정 마법사는 port 3000에서 수신 대기합니다. 이 port를 공개적으로 열지 말고 터널을 통해 http://10.8.0.1:3000에서 접속합니다. 그런 다음 마법사에서 DNS listen address와 admin listen address를 모두 10.8.0.1으로 설정합니다. 첫 번째 장애의 unbound이 여전히 동일한 주소를 사용하고 있다면 먼저 sudo systemctl disable --now unbound로 중지합니다. 하나의 주소에서 두 프로세스가 UDP port 53에 bind할 수 없으므로 두 번째 프로세스가 listen udp 10.8.0.1:53: bind: address already in use와 함께 종료됩니다.
클라이언트 설정이 이미 DNS = 10.8.0.1으로 되어 있다면 변경할 필요가 없습니다. 이제 쿼리 로그에 모든 peer의 모든 조회가 표시됩니다. 이는 아무 비용 없는 이점이 아니라 실제 개인정보 보호 결정입니다. 인터넷 provider에 대한 신뢰를 자신에게로 옮기며, 해당 장치에 패치를 계속 적용할 책임도 자신에게 있습니다. 인터넷에 노출된 서버는 먼저 기본 보안 설정을 갖추어야 하며, 새 VPS에서 처음 10분에서 이를 다룹니다.
FAQ
WireGuard 터널은 연결되지만 이름이 확인되지 않는 이유는 무엇입니까?
터널은 패킷을 전달할 뿐 이름을 처리하지 않습니다. 따라서 터널은 정상적으로 작동하지만 조회가 실패한다면 지정한 resolver가 응답하지 않는 것입니다. 클라이언트에서 dig +short @10.8.0.1 example.com을 사용하여 테스트합니다. communication timed out 응답이 나타나면 해당 터널 주소에서 수신 대기 중인 resolver가 없거나, systemd-resolved stub이 127.0.0.53에만 바인딩되어 있거나, 서버 방화벽이 wg0로 들어오는 UDP port 53을 삭제하고 있는 경우입니다. 먼저 수신 대기 설정을 수정한 다음 wg0에 대해서만 port를 엽니다.
WireGuard를 통해 DNS가 유출되는지 확인하려면 어떻게 해야 합니까?
클라이언트에서 sudo tcpdump -ni any -c 10 port 53을 실행하고 웹을 탐색하는 동안 interface 열을 확인합니다. 모든 패킷은 wg0에 있어야 합니다. 패킷이 wireless 또는 ethernet interface에 나타나면 query가 암호화되지 않은 상태로 나가고 있는 것입니다. dig +short whoami.akamai.net은 추가 확인에 사용할 수 있습니다. 이 명령은 query를 처리한 recursive resolver의 public address를 반환하므로, 서버 주소가 아닌 주소가 반환되면 유출이 확인됩니다.
split tunnel을 사용하는 경우에도 DNS = 줄이 필요합니까?
필요합니다. 또한 resolver address는 AllowedIPs 내부에 있어야 합니다. 그렇지 않으면 클라이언트에 해당 resolver로 가는 route가 없습니다. AllowedIPs = 10.8.0.0/24을 사용하면 10.8.0.1의 resolver가 해당 범위에 포함되므로 query가 암호화됩니다. 9.9.9.9과 같은 public resolver는 포함되지 않으므로 DNS 줄이 올바르게 보이더라도 query는 local link를 통해 나갑니다.
resolvectl에 올바른 server가 표시되는데도 조회가 다른 곳으로 전송되는 이유는 무엇입니까?
systemd-resolved는 link마다 하나의 resolver 목록을 유지하고 query마다 사용할 link를 선택합니다. 따라서 wg0에 올바른 항목이 있어도 다른 link가 이름 조회의 default route를 보유하고 있으면 해당 항목은 무시됩니다. 클라이언트의 [Interface] block에 PostUp = resolvectl dns %i 10.8.0.1; resolvectl domain %i ~.을 추가하고 DNS = 줄을 제거합니다. 그러면 resolvectl status wg0이 Default Route: yes을 보고해야 합니다.
여러 client에서 문제가 발생한 경우 어떤 client를 먼저 수정해야 합니까?
Linux client 하나를 수정합니다. Linux는 작동 방식을 확인할 수 있는 유일한 platform이기 때문입니다. resolvectl status과 tcpdump를 사용하면 어떤 resolver가 응답했는지와 어떤 interface가 패킷을 전달했는지 확인할 수 있습니다. Phone 및 desktop app은 표시되는 네트워크 설정 없이 동일한 DNS 및 AllowedIPs 값을 적용합니다. 따라서 Linux client가 올바르게 작동하면 이미 검증한 설정을 그대로 복사할 수 있습니다.