Split View: DNS 완전 가이드: 이름이 주소가 되는 경로를 끝까지 따라가기
DNS 완전 가이드: 이름이 주소가 되는 경로를 끝까지 따라가기
- 들어가며
- 1. 이름 해석 경로 — 질의 하나가 지나가는 다섯 구간
- 2. 레코드 타입 — 무엇을 어디에 두는가
- 3. TTL과 캐시 — "전파"의 실체
- 4. 위임과 네임서버 — 권한이 갈라지는 지점
- 5. dig 읽는 법
- 6. 흔한 장애 — 전파 지연, 스테일 캐시, CNAME 제약
- 7. DNSSEC — 무엇을 보장하고 무엇을 보장하지 않는가
- 8. 사내 DNS 운영
- 9. 진단 순서 요약
- 퀴즈: 실력을 확인해 보세요
- 마치며
- 참고 자료
- 이어서 읽기
들어가며
장애 회의에서 "DNS 문제 같습니다"라는 말이 나오면 대개 그 자리에서 조사가 멈춥니다. DNS는 하나의 시스템이 아니라 성격이 다른 다섯 구간이 이어 붙은 경로이기 때문입니다. 어느 구간이 문제인지 말하지 않은 진단은 진단이 아닙니다.
이 블로그에는 이미 DNS 심층 분석과 DNS 해석 순서 디버깅이 있습니다. 앞의 글은 프로토콜 구조를, 뒤의 글은 리눅스 호스트의 해석 순서를 다룹니다. 이 글은 그 사이를 메웁니다. 레코드를 바꾸고, 전파를 기다리고, 캐시를 비우고, 어느 구간이 범인인지 판별해야 하는 운영자의 관점에서 절차와 판단 기준을 정리합니다.
명령은 BIND 9 계열의 dig, glibc 스텁 리졸버, systemd-resolved를 기준으로 합니다. 이 글에 나오는 모든 옵션은 매뉴얼과 RFC 원문에서 확인한 것만 실었고, 근거는 마지막 참고 자료에 URL과 확인 날짜로 남겼습니다.
dig -v
resolvectl status
1. 이름 해석 경로 — 질의 하나가 지나가는 다섯 구간
이름 하나가 주소가 되기까지 다음 다섯 구간을 지납니다. 각 구간은 서로 다른 이유로, 서로 다른 증상을 내며 실패합니다.
- 애플리케이션과 스텁 리졸버. 프로그램이 이름 해석 함수를 부르면 시스템의 스텁 리졸버가 처리합니다. RFC 8499는 스텁 리졸버를 "스스로 전체 해석을 수행할 수 없고 실제 해석은 재귀 리졸버에 의존하는 리졸버"로 정의합니다. 이 구간에는
/etc/hosts와 이름 서비스 스위치 설정이 함께 걸립니다. - 리졸버 선택.
/etc/resolv.conf가 어느 서버로 보낼지, 어떤 검색 도메인을 붙일지 결정합니다. - 재귀 리졸버의 캐시. 캐시에 있으면 여기서 끝납니다. 대부분의 질의가 이 구간에서 끝나기 때문에, 변경 사고의 대부분도 이 구간에서 발생합니다.
- 위임 추적. 캐시에 없으면 루트에서 시작해 TLD를 거쳐 권한 서버까지 내려갑니다. RFC 1034는 이 과정을 "SNAME에서 시작해 부모, 조부모 순으로 로컬에 있는 네임서버 레코드를 찾고, 없으면 루트 서버가 들어 있는 안전벨트(safety belt) 설정에서 출발한다"고 기술합니다.
- 권한 서버의 응답. RFC 8499의 권한 서버 정의는 "존의 내용을 로컬 지식으로 알고 있어서 다른 서버에 묻지 않고 답할 수 있는 서버"입니다.
구간을 나누는 이유는 각 구간을 따로 물어볼 수 있기 때문입니다.
getent hosts api.internal.example.com
cat /etc/resolv.conf
resolvectl status
dig @127.0.0.53 api.internal.example.com
dig @10.0.0.53 api.internal.example.com
dig @ns1.example.com api.internal.example.com
getent는 이름 서비스 스위치를 거치는 경로이고 dig는 그렇지 않습니다. 이 둘의 결과가 다르면 문제는 DNS가 아니라 스텁 리졸버 구간에 있습니다.
/etc/resolv.conf의 지시어는 다음과 같습니다. 값은 매뉴얼 기준입니다.
nameserver— 질의를 보낼 서버의 주소입니다. 최대 3개까지만 사용됩니다.search— 짧은 이름에 붙일 검색 도메인 목록입니다. glibc 2.25 이하에서는 6개, 총 256자로 제한되었고 2.26부터 제한이 없어졌습니다.options ndots:n— 이름에 점이 몇 개 이상 있어야 검색 도메인을 붙이지 않고 절대 이름으로 먼저 질의할지의 임계값입니다. 기본값 1, 최대 15입니다.options timeout:n— 응답 대기 시간입니다. 기본 5초, 최대 30초입니다.options attempts:n— 포기하기 전 시도 횟수입니다. 기본 2회, 최대 5회입니다.options rotate— 나열된 네임서버를 순환 선택합니다.options single-request— IPv4와 IPv6 질의를 병렬이 아니라 순차로 보냅니다.
여기서 두 가지 계산이 나옵니다. 첫째, 첫 번째 네임서버가 죽어 있으면 사용자는 기본값 기준 5초를 그대로 체감합니다. 타임아웃과 시도 횟수를 줄이지 않으면 이중화는 있으나 마나입니다. 둘째, ndots 값이 크면 짧은 이름은 물론이고 점이 몇 개 들어간 이름까지 검색 도메인을 먼저 시도하므로, 외부 이름 하나를 푸는 데 실패 질의가 여러 번 앞서 나갑니다. 컨테이너 환경에서 이름 해석이 느린 전형적인 원인입니다.
검색 도메인이 관여하는지 아닌지는 다음으로 갈라 볼 수 있습니다.
dig +search api
dig +nosearch api
dig api.internal.example.com.
마지막 줄처럼 이름 끝에 점을 붙이면 절대 이름이 되어 검색 도메인을 건너뜁니다. 애플리케이션 설정에 외부 도메인을 적을 때 후행 점을 붙여 두면 불필요한 질의가 사라집니다.
2. 레코드 타입 — 무엇을 어디에 두는가
| 타입 | 담는 것 | 운영에서 걸리는 제약 |
|---|---|---|
| A / AAAA | IPv4 / IPv6 주소 | 가장 기본. 둘 다 있으면 클라이언트가 선택 |
| CNAME | 다른 이름으로의 별칭 | 같은 이름에 다른 데이터와 공존 불가 |
| MX | 메일 수신 서버 | 대상이 별칭이면 안 됨 |
| NS | 하위 존의 권한 서버 | 대상이 별칭이면 안 됨 |
| SOA | 존의 권한 정보와 네거티브 캐시 TTL | 존 정점에 반드시 존재 |
| TXT | 자유 텍스트 | 소유 확인, 메일 정책 등이 여기에 얹힘 |
| PTR | 주소에서 이름으로 | in-addr.arpa / ip6.arpa 아래에 위치 |
| SRV | 서비스의 호스트와 포트 | 대상이 별칭이면 안 됨 |
| CAA | 인증서 발급 허용 CA | 상위로 올라가며 탐색됨 |
몇 가지는 근거를 그대로 인용해 둘 가치가 있습니다.
CNAME은 혼자여야 합니다. RFC 1034는 "어떤 노드에 CNAME 레코드가 있으면 다른 데이터가 있어서는 안 된다"고 못 박습니다. 정규 이름과 별칭의 데이터가 달라지는 것을 막기 위해서입니다. 그리고 RFC 8499는 존 정점(apex)을 "SOA와 그에 대응하는 권한 NS 레코드 집합의 소유자에 해당하는 지점"으로 정의합니다. 정점에는 SOA와 NS가 반드시 있으므로, 정점에는 CNAME을 둘 수 없습니다. 루트 도메인을 CDN에 붙이려다 막히는 사고의 정확한 근거가 이것입니다.
NS와 MX의 대상은 별칭이면 안 됩니다. RFC 2181은 "NS 레코드의 값으로 쓰이는 도메인 이름, 그리고 MX 레코드 값의 일부로 쓰이는 도메인 이름은 별칭이어서는 안 된다"고 규정합니다. SRV도 마찬가지입니다. RFC 2782는 대상에 대해 "이 이름에 대한 주소 레코드가 하나 이상 있어야 하며, 그 이름은 별칭이어서는 안 된다"고 요구합니다. SRV의 형식은 _Service._Proto.Name TTL Class SRV Priority Weight Port Target이고, 우선순위가 낮은 대상을 먼저 시도하며 같은 우선순위 안에서는 가중치에 비례해 선택합니다.
CAA는 위로 올라가며 찾습니다. RFC 8659는 CA가 "지정된 레이블에서 시작해 루트를 포함하지 않는 지점까지 DNS 이름 트리를 올라가며 CAA 레코드 집합을 찾는다"고 정의합니다. 태그는 issue, issuewild, iodef가 있고, 발급을 제한하는 태그가 하나도 없으면 CAA는 발급을 제한하지 않습니다. 서브도메인에 CAA를 안 넣었는데 상위의 CAA에 막히는 상황이 여기서 나옵니다.
크기 제한도 기억해 둘 값이 있습니다. RFC 1035는 레이블 63옥텟, 이름 255옥텟, UDP 메시지 512옥텟을 상한으로 정했습니다. 512옥텟 제한은 RFC 6891의 EDNS(0)로 확장되며, 요청자는 OPT 레코드의 클래스 필드에 자신이 받을 수 있는 최대 UDP 페이로드 크기를 적습니다. 이 문서는 출발점으로 4096옥텟을, 폴백 구간으로 1280~1410바이트를 제시합니다. 응답이 이보다 크면 TC 비트가 서고 TCP로 다시 물어야 합니다.
3. TTL과 캐시 — "전파"의 실체
DNS에는 푸시가 없습니다. 변경이 퍼지는 것이 아니라 캐시가 만료되는 것입니다. 이 한 문장을 이해하면 전파 관련 사고의 대부분이 사라집니다.
RFC 1034는 TTL을 "레코드가 버려지기 전까지 얼마나 오래 캐시될 수 있는지"로 정의합니다. RFC 2181은 여기에 실무에서 걸리는 규칙을 더합니다. 하나의 레코드 집합 안에서 모든 레코드의 TTL은 같아야 하며, 서버는 TTL이 서로 다른 집합을 보내서는 안 됩니다. 또한 TTL은 부호 없는 32비트 값이지만 최대값은 2147483647이고, 최상위 비트가 선 값은 0으로 취급해야 합니다.
없다는 사실도 캐시됩니다. RFC 2308은 두 종류를 구분합니다.
- NXDOMAIN — 응답 코드가 이름 오류이며, 질의한 도메인 자체가 존재하지 않습니다.
- NODATA — 응답 코드는 정상인데 답 구역에 관련 레코드가 없습니다. 이름은 있고 그 타입만 없는 상태입니다.
네거티브 응답의 캐시 시간은 SOA의 MINIMUM 필드와 SOA 레코드 자신의 TTL 중 작은 값입니다. 같은 문서는 실무 권고도 남겼습니다. "1시간에서 3시간 사이의 값이 잘 동작하는 것으로 확인되었고 기본값으로 삼을 만하다. 하루를 넘는 값은 문제를 일으키는 것으로 확인되었다." 오타 난 레코드를 고쳤는데 한나절 동안 계속 실패하는 상황의 원인이 대개 이 값입니다.
변경 절차는 TTL을 중심으로 짭니다.
- 변경 예정일 기준으로 기존 TTL만큼 앞서서 TTL을 낮춥니다(예: 300초).
- 기존 TTL이 다 지날 때까지 기다립니다. 이 대기를 건너뛰면 낮춘 TTL이 아직 캐시에 반영되지 않은 리졸버가 남습니다.
- 레코드를 변경합니다.
- 권한 서버와 여러 리졸버에서 각각 확인합니다.
- 안정되면 TTL을 원래 값으로 되돌립니다.
캐시에 남은 시간은 관찰할 수 있습니다.
dig +noall +answer +ttlunits example.com
dig +noall +answer example.com
dig @1.1.1.1 +noall +answer example.com
dig @ns1.example.com +noall +answer example.com
리졸버에 반복해서 물으면 TTL이 줄어드는 것이 보입니다. 남은 초가 곧 그 리졸버의 캐시 만료까지 남은 시간입니다. 반면 권한 서버에 직접 물으면 항상 원래 설정값이 나옵니다.
4. 위임과 네임서버 — 권한이 갈라지는 지점
RFC 8499는 위임을 "부모 존에 자식 원점에 대한 NS 레코드 집합을 추가해 별도의 존을 만드는 과정"으로 정의하고, 그 경계를 존 컷(zone cut)이라 부릅니다. 여기서 실무자가 반드시 알아야 할 비대칭이 하나 있습니다. RFC 2181은 "존 컷을 나타내는 NS 레코드는 새로 만들어진 자식 존의 소유"라고 규정합니다. 즉 부모(TLD)가 가진 NS 목록과 자식 존이 스스로 답하는 NS 목록은 다를 수 있고, 실제 권한은 자식 쪽입니다.
여기서 두 가지 흔한 사고가 나옵니다.
- 레지스트라에서 네임서버를 바꿨는데 반영이 안 된다. 바뀐 것은 부모의 위임입니다. 자식 존이 여전히 옛 서버 목록을 답하고 있으면 리졸버가 무엇을 캐시했느냐에 따라 결과가 갈립니다.
- 네임서버 목록에 실제로 응답하지 않는 서버가 섞여 있다. 리졸버는 응답 없는 서버를 만나면 재시도하므로, 이름은 결국 풀리지만 간헐적으로 느려집니다.
글루(glue)가 필요한 이유도 여기서 나옵니다. RFC 1034는 존 아래쪽 경계를 설명하는 NS 레코드들이 "그 존의 권한 데이터가 아니다"라고 말하고, 하위 존 네임서버의 주소 레코드를 부모가 함께 제공한다고 기술합니다. 네임서버의 이름이 자기가 담당하는 존 아래에 있으면, 주소를 알기 위해 그 존에 물어야 하고 그 존에 물으려면 주소가 필요한 순환이 생깁니다. 부모가 주는 주소 레코드가 이 순환을 끊습니다.
부모와 자식을 각각 물어 비교하는 것이 진단의 핵심입니다.
dig NS example.com
dig +trace example.com
dig +nssearch example.com
dig SOA example.com @ns1.example.com
dig SOA example.com @ns2.example.com
+trace는 매뉴얼 표현으로 "루트 네임서버부터의 위임 경로를 추적"합니다. +nssearch는 "해당 존의 권한 네임서버를 찾으려 시도"합니다. 마지막 두 줄로 모든 권한 서버의 SOA 일련번호가 같은지 확인하세요. 번호가 어긋나 있으면 존 전송이 밀린 것이고, 사용자는 서버에 따라 다른 답을 받고 있는 중입니다.
5. dig 읽는 법
기본형은 dig @server name type입니다. 매뉴얼은 각 자리를 "질의할 네임서버의 이름이나 주소", "조회할 레코드의 이름", "필요한 질의의 타입"으로 설명합니다.
출력은 헤더, 질문, 답, 권한, 추가 구역과 통계로 나뉩니다. 읽는 순서는 status → flags → 각 구역입니다.
status에 오는 값의 의미는 RFC 1035의 응답 코드입니다.
NOERROR— 오류 없음. 다만 답 구역이 비어 있으면 NODATA이며 이름은 존재합니다.NXDOMAIN— 이름 오류. 그 이름이 존재하지 않습니다.SERVFAIL— 서버 실패. 이름이 없다는 뜻이 아니라 답을 만들지 못했다는 뜻입니다. 상위 도달 실패나 DNSSEC 검증 실패가 대표적입니다.REFUSED— 거부. 정책상 답하지 않겠다는 뜻이며, 재귀를 허용하지 않는 리졸버에 외부에서 물었을 때 나옵니다.
flags에 오는 글자도 각각 뜻이 있습니다.
qr— 응답 메시지라는 표시입니다.aa— 권한 있는 답입니다. 이 글자가 없으면 캐시에서 온 답입니다.tc— 잘렸습니다. TCP로 다시 물어야 합니다.rd/ra— 재귀를 요청했다 / 재귀가 가능하다.ad— DNSSEC 검증을 통과했음을 리졸버가 표시한 것입니다.cd— 검증을 끄고 물었다는 표시입니다.
자주 쓰는 조합을 목적별로 정리하면 다음과 같습니다.
dig +short A example.com
dig +noall +answer example.com
dig +noall +authority +additional example.com
dig @1.1.1.1 example.com
dig +norecurse @10.0.0.53 example.com
dig -x 93.184.216.34
dig -t MX example.com
dig +dnssec example.com
dig +cd example.com
dig +multiline SOA example.com
dig +tcp example.com
dig +time=2 +tries=1 example.com
dig +stats example.com
dig +yaml example.com
각 옵션의 근거는 매뉴얼 문장 그대로입니다. +short는 "간결한 답을 낼지 전환"하고, +norecurse는 "질의의 RD(recursion desired) 비트를 끄며", +dnssec는 "DNSSEC OK(DO) 비트를 설정해 DNSSEC 레코드를 요청"하고, +cd는 "질의에 CD(checking disabled) 비트를 설정"합니다. -x는 "주소를 이름으로 매핑하는 간이 역방향 조회"이고, +time=T와 +tries=T는 대기 시간과 시도 횟수를, +subnet은 "EDNS CLIENT-SUBNET 옵션을 지정한 주소로 전송"합니다.
+norecurse의 쓰임을 하나 짚어 둡니다. 리졸버에 재귀를 끄고 물으면 그 리졸버의 캐시에 있는 것만 답합니다. 캐시 적중 여부를 확인하거나, 캐시를 오염시키지 않고 상태를 들여다볼 때 씁니다.
반대로 +trace에는 함정이 있습니다. +trace는 루트부터 각 단계를 직접 질의하므로 여러분의 리졸버를 우회합니다. 그래서 +trace는 정상인데 애플리케이션은 실패하는 상황이 흔합니다. 이때 범인은 권한 서버가 아니라 리졸버 구간입니다.
6. 흔한 장애 — 전파 지연, 스테일 캐시, CNAME 제약
전파 지연. 앞에서 본 것처럼 전파는 만료입니다. "48시간 걸린다"는 안내는 대부분 낡은 TTL 설정을 전제로 한 보수적 표현입니다. 변경 전에 TTL을 낮추지 않았다면 최대 기존 TTL만큼 기다리는 것 외에 방법이 없고, 권한 서버에서 이미 새 값이 나오는데 사용자만 옛 값을 본다면 그것은 정상 동작입니다.
스테일 캐시. 캐시는 한 곳에 있지 않습니다. 최소한 다음 위치를 각각 확인해야 합니다.
- 사내 재귀 리졸버의 캐시
- 호스트의 로컬 캐시(systemd-resolved 등)
- 런타임 자체의 캐시(일부 런타임은 자체 이름 캐시를 가집니다)
비우는 명령은 계층마다 다릅니다.
sudo rndc flush
sudo rndc flushname api.example.com
sudo rndc flushtree example.com
sudo rndc dumpdb -cache
resolvectl flush-caches
resolvectl query api.example.com
resolvectl statistics
rndc flush는 "서버의 캐시를 비우고", flushname은 "주어진 이름을 뷰의 DNS 캐시에서, 해당하는 경우 네임서버 주소 데이터베이스와 불량 서버 캐시, SERVFAIL 캐시에서" 제거합니다. flushtree는 "주어진 이름과 그 모든 하위 도메인"을 같은 범위에서 제거합니다. resolvectl flush-caches는 "서비스가 로컬에 유지하는 모든 DNS 레코드 캐시를 비웁니다". 전체 비우기보다 이름 단위 비우기를 먼저 시도하세요. 캐시를 통째로 버리면 그 뒤 몇 분간 상위 질의가 폭증합니다.
CNAME 제약. 세 가지가 반복됩니다. 정점에 CNAME을 둘 수 없고, MX와 NS와 SRV의 대상이 별칭이면 안 되며, CNAME이 있는 이름에는 다른 레코드를 함께 둘 수 없습니다. 앞의 두 개는 규격 위반이라도 당장은 동작하는 것처럼 보이는 경우가 있어 더 위험합니다. 동작하는 것처럼 보이는 규격 위반이 가장 늦게, 가장 나쁜 시점에 드러납니다.
NODATA를 NXDOMAIN으로 오해. NOERROR인데 답이 비어 있으면 이름은 있습니다. AAAA만 없는 이름에 IPv6로 접속을 시도하는 클라이언트가 대표적인 사례이며, 이때 이름 자체를 지웠다 다시 만드는 식의 대응은 아무 효과가 없습니다.
TCP 차단. DNSSEC 서명이나 레코드 수 때문에 응답이 커지면 TC 비트가 서고 TCP로 재질의합니다. 방화벽이 UDP 53만 열어 두었다면 평소에는 멀쩡하다가 특정 이름에서만 실패합니다. 판별은 간단합니다.
dig +tcp example.com
dig +dnssec +tcp example.com
이름은 풀리는데 접속이 안 됨. dig가 정상 응답하면 DNS 구간은 무죄입니다. 그다음은 연결 계층의 문제이며 진단 도구가 달라집니다.
7. DNSSEC — 무엇을 보장하고 무엇을 보장하지 않는가
RFC 4033의 표현을 그대로 옮기면 DNSSEC은 "DNS에 데이터 출처 인증과 데이터 무결성을 제공"합니다. 그리고 같은 문서는 제공하지 않는 것을 명시합니다. "DNSSEC은 기밀성, 접근 제어 목록, 또는 질의자를 구별하는 다른 수단을 제공하도록 설계되지 않았다." 나아가 "DNSSEC은 서비스 거부 공격에 대한 보호를 제공하지 않는다"고 하며, 오히려 암호 연산에 기반한 새로운 부류의 서비스 거부 공격을 만든다고 경고합니다. 도청 방지를 원한다면 다른 계층의 대책이 필요합니다.
레코드는 네 종류입니다.
- DNSKEY — 존의 공개 키를 담습니다.
- RRSIG — 레코드 집합에 대한 디지털 서명입니다.
- DS — 부모 존의 위임 지점에 놓여 자식의 키를 가리킵니다.
- NSEC — 이름이나 타입이 존재하지 않음을 인증합니다.
신뢰 체인은 DNSKEY 집합과 DS 집합이 번갈아 이어지며 각 고리가 다음 고리를 보증하는 구조이고, 검증은 트러스트 앵커에서 시작합니다. 검증 결과는 네 가지 상태로 정리됩니다. 트러스트 앵커와 체인이 있고 모든 서명을 검증한 Secure, 서명되지 않았다는 사실이 증명된 Insecure, 서명되어야 하는데 검증에 실패한 Bogus, 해당 구간이 안전하다고 알려 줄 트러스트 앵커가 없는 Indeterminate입니다.
운영자에게 중요한 것은 실패가 SERVFAIL로 나타난다는 점입니다. 존 내용은 그대로인데 서명 유효 기간이 지나면, 검증하는 리졸버에서만 조회가 실패합니다. 검증하지 않는 리졸버에서는 멀쩡하게 보입니다. 판별은 검증을 끄고 물어보는 것입니다.
dig +dnssec example.com
dig +cd example.com
delv example.com
+cd를 붙였더니 답이 나온다면 그것은 DNSSEC 검증 실패입니다. delv는 매뉴얼이 "DNS 질의를 보내고 결과를 검증하는 도구"라고 설명하는 전용 도구입니다.
사내 환경에서는 한 가지가 더 걸립니다. RFC 4033은 부모로부터 인증 체인이 오지 않는 서명된 존을 "보안의 섬(island of security)"이라 부르며, 그런 존은 대역 외 수단으로 키를 신뢰할 수 있을 때만 인증된다고 설명합니다. 서명되지 않은 내부 존을 검증 리졸버가 다루는 방식이 여기에 걸리므로, 내부 존을 별도로 처리하도록 리졸버 설정을 맞춰야 합니다.
8. 사내 DNS 운영
분할 지평. 같은 이름에 대해 내부와 외부에 다른 답을 주는 구성입니다. 편리하지만 "어디서 물었느냐"가 답을 바꾸므로, 장애 신고를 받을 때 질문자의 위치를 먼저 확인해야 합니다. 신고자가 VPN에 붙어 있었는지 아닌지가 결론을 바꿉니다.
포워더인가 재귀인가. RFC 8499는 포워더를 "권한 네임서버 체인을 직접 사용하는 대신 질의를 해석하는 데 쓰이는 네임서버"로 정의합니다. 사내 리졸버가 상위로 포워딩하는 구성이라면, 상위가 죽었을 때 사내 리졸버는 살아 있으면서 아무것도 답하지 못하는 상태가 됩니다. 헬스 체크를 프로세스 생존이 아니라 실제 질의 성공으로 잡아야 하는 이유입니다.
리졸버 이중화의 현실. 앞서 본 대로 nameserver는 3개까지만 쓰이고, 기본 타임아웃은 5초, 기본 시도 횟수는 2회입니다. 첫 서버가 응답하지 않으면 사용자는 그 시간을 그대로 체감합니다. 사내 표준 resolv.conf에는 타임아웃과 시도 횟수를 명시하고, 필요하면 rotate로 부하를 분산하세요.
변경과 검증. 존을 바꾼 뒤에는 모든 권한 서버에서 같은 답이 나오는지 확인합니다.
sudo rndc reload
sudo rndc status
sudo rndc querylog on
dig SOA example.com @ns1.example.com +short
dig SOA example.com @ns2.example.com +short
sudo rndc querylog off
rndc reload는 "설정 파일과 존을 다시 읽고", rndc reconfig는 "설정 파일을 다시 읽고 새 존을 적재하지만 파일이 바뀌었더라도 기존 존은 다시 읽지 않습니다". 이 차이를 모르면 "리로드했는데 왜 안 바뀌느냐"가 반복됩니다. querylog는 질의 로깅을 켜고 끄며, 부하가 큰 서버에서는 켠 채로 두지 말고 조사 구간에만 켜세요.
컨테이너 환경. 오케스트레이터가 넣어 주는 resolv.conf에는 검색 도메인이 여러 개, ndots가 큰 값으로 들어가는 경우가 많습니다. 결과적으로 외부 이름 하나를 푸는 데 실패 질의가 여러 번 선행합니다. 애플리케이션 설정의 외부 도메인에 후행 점을 붙이는 것만으로 상당 부분이 사라집니다.
무엇을 감시할 것인가. 다음 네 가지면 대부분의 사고를 먼저 발견할 수 있습니다.
- 모든 권한 서버의 SOA 일련번호 일치
- 서명 유효 기간의 잔여 일수(서명을 쓴다면)
- 재귀 리졸버의 질의 지연과 SERVFAIL 비율
- 도메인과 위임 정보의 만료일
9. 진단 순서 요약
증상을 받았을 때 다음 순서로 좁힙니다. 각 단계는 앞 단계를 배제하는 데 의미가 있습니다.
- 애플리케이션인가 시스템인가.
getent hosts와dig의 결과를 비교합니다. 다르면 스텁 리졸버 구간입니다. - 어느 리졸버인가.
/etc/resolv.conf와resolvectl status로 실제로 쓰는 서버를 확인하고, 그 서버에 직접 물어봅니다. - 캐시인가 권한인가. 리졸버 답과 권한 서버 답을 비교합니다. 다르면 캐시이고, TTL이 남은 시간을 알려 줍니다.
- 위임인가.
dig +trace와 부모·자식 NS 비교로 위임을 확인합니다. - 검증인가. SERVFAIL이라면
+cd로 다시 물어봅니다. 답이 나오면 DNSSEC 검증 실패입니다. - DNS가 아닌가. 이름이 정상적으로 풀린다면 이 시점에서 DNS 조사를 종료하고 연결 계층으로 넘깁니다.
getent hosts example.com
dig +short example.com
dig @1.1.1.1 +short example.com
dig @ns1.example.com +short example.com
dig +trace example.com
dig +cd example.com
퀴즈: 실력을 확인해 보세요
퀴즈 1: dig는 정상 응답하는데 애플리케이션에서만 "이름을 찾을 수 없음"이 납니다. 가장 먼저 확인할 것은?
정답: 스텁 리졸버 경로입니다. dig는 이름 서비스 스위치를 거치지 않고 /etc/resolv.conf만 쓰기 때문에 두 경로가 다릅니다
설명: 확인 순서는 getent와 dig를 나란히 놓는 것에서 시작합니다.
getent hosts api.example.com
dig +short api.example.com
cat /etc/resolv.conf
getent만 실패한다면 이름 서비스 스위치 설정, /etc/hosts, 컨테이너에 마운트된 resolv.conf, 검색 도메인과 ndots 설정을 봅니다. 애플리케이션이 자체 리졸버를 내장한 경우도 있으므로 런타임의 이름 해석 설정도 함께 확인하세요.
퀴즈 2: A 레코드를 바꿨는데 일부 사용자만 여전히 옛 주소로 갑니다. 무엇이 일어나고 있으며 다음 변경 때는 무엇을 다르게 해야 하나요?
정답: 리졸버 캐시가 아직 만료되지 않은 것입니다. 다음에는 변경보다 최소한 기존 TTL만큼 앞서서 TTL을 낮춰 두어야 합니다
설명: DNS에는 푸시가 없습니다. 변경이 퍼지는 것이 아니라 캐시가 만료되는 것입니다. 남은 시간은 리졸버마다 다르며, 관찰할 수 있습니다.
dig @1.1.1.1 +noall +answer +ttlunits example.com
dig @ns1.example.com +noall +answer example.com
리졸버 쪽 TTL은 반복 질의할수록 줄어들고, 권한 서버 쪽은 항상 원래 값입니다. 이 둘이 다른 것은 정상입니다.
퀴즈 3: 사내 리졸버에서는 SERVFAIL, 공용 리졸버에서는 정상입니다. 무엇을 의심하고 어떻게 한 번에 판별하나요?
정답: DNSSEC 검증 실패를 의심하고, 검증을 끄고 물어봐서 판별합니다
설명: SERVFAIL은 "그 이름이 없다"가 아니라 "답을 만들지 못했다"입니다. 검증 실패와 상위 도달 실패가 대표적인 원인입니다.
dig @10.0.0.53 example.com
dig @10.0.0.53 +cd example.com
delv example.com
+cd를 붙였을 때 답이 나오면 검증 단계에서 막힌 것입니다. 서명 유효 기간 만료나 DS 불일치를 먼저 확인하세요. +cd를 붙여도 실패한다면 검증이 아니라 도달성이나 상위 응답의 문제입니다.
퀴즈 4: 루트 도메인 example.com을 CDN 호스트 이름으로 바꾸려고 CNAME을 넣었더니 존이 거부됩니다. 규격상 이유는?
정답: 존 정점에는 SOA와 NS가 반드시 있어야 하는데, CNAME은 같은 이름에 다른 데이터와 공존할 수 없기 때문입니다
설명: RFC 1034는 어떤 노드에 CNAME이 있으면 다른 데이터가 있어서는 안 된다고 규정합니다. 그리고 존 정점은 SOA와 권한 NS 레코드 집합의 소유자로 정의됩니다. 두 규칙이 동시에 성립할 수 없습니다.
같은 이유로 다음도 규격 위반입니다.
example.com. IN MX 10 mail-alias.example.com.
mail-alias IN CNAME real-mail.example.net.
RFC 2181은 NS와 MX의 대상이 별칭이어서는 안 된다고 규정하고, RFC 2782는 SRV의 대상에 대해 같은 요구를 합니다.
퀴즈 5: 응답이 NOERROR인데 ANSWER 섹션이 비어 있습니다. 이 이름은 존재하지 않는 것인가요?
정답: 존재합니다. 이것은 NODATA이며, 이름은 있고 질의한 타입만 없는 상태입니다
설명: RFC 2308은 NXDOMAIN과 NODATA를 구분합니다. NXDOMAIN은 이름 자체가 없는 것이고, NODATA는 응답 코드가 정상인데 답 구역에 관련 레코드가 없는 것입니다.
dig AAAA example.com
dig A example.com
dig ANY example.com
두 경우 모두 네거티브 응답으로 캐시되며, 캐시 시간은 SOA의 MINIMUM 필드와 SOA 레코드 자신의 TTL 중 작은 값입니다. 그래서 잘못된 레코드를 고쳐도 한동안 실패가 이어질 수 있습니다.
퀴즈 6: dig +trace는 올바른 주소를 보여 주는데 서버의 애플리케이션은 여전히 옛 주소로 붙습니다. 어디를 봐야 하나요?
정답: 리졸버 구간입니다. +trace는 루트부터 직접 질의하므로 그 호스트가 실제로 쓰는 리졸버를 우회합니다
설명: +trace가 정상이라는 것은 권한 서버 쪽이 정상이라는 뜻일 뿐입니다. 실제 경로를 그대로 재현하려면 그 호스트가 쓰는 리졸버에 직접 물어야 합니다.
cat /etc/resolv.conf
dig @10.0.0.53 +noall +answer example.com
dig @10.0.0.53 +norecurse example.com
sudo rndc flushname example.com
+norecurse는 그 리졸버의 캐시에 있는 것만 답하게 하므로, 캐시가 원인인지 즉시 확인할 수 있습니다. 로컬 캐시가 따로 있다면 그쪽도 함께 비웁니다.
마치며
DNS 장애 대응이 어려운 이유는 어렵기 때문이 아니라, 어느 구간을 보고 있는지 말하지 않은 채로 이야기하기 때문입니다. 리졸버가 답한 것인지 권한 서버가 답한 것인지, 캐시에서 온 것인지 방금 조회한 것인지, 검증을 거친 것인지 아닌지를 매번 명시하면 대화가 급격히 짧아집니다.
당장 몸에 붙일 것은 세 가지입니다. getent와 dig를 나란히 물어 스텁 리졸버 구간을 먼저 배제하고, 리졸버와 권한 서버에 각각 물어 캐시와 원본을 구분하고, 레코드를 바꾸기 전에 기존 TTL만큼 앞서서 TTL을 낮춰 두세요. 이 세 가지만으로 DNS 관련 사고 대부분이 사라지거나, 최소한 몇 분 안에 어느 구간의 문제인지 결론이 납니다.
참고 자료
- RFC 1034 — Domain Names: Concepts and Facilities — 해석 알고리즘, CNAME 단독 규칙, 위임과 글루, TTL 정의 (2026-08-15 확인)
- RFC 1035 — Domain Names: Implementation and Specification — 메시지 구역과 헤더 비트, 응답 코드, 레이블·이름·UDP 크기 상한 (2026-08-15 확인)
- RFC 8499 — DNS Terminology — 스텁 리졸버, 재귀 리졸버, 권한 서버, 위임, 존 컷, 포워더, 존 정점의 정의 (2026-08-15 확인)
- RFC 2181 — Clarifications to the DNS Specification — 레코드 집합의 TTL 동일 규칙, TTL 상한, NS·MX 대상의 별칭 금지, 존 컷 NS의 소유 (2026-08-15 확인)
- RFC 2308 — Negative Caching of DNS Queries — NXDOMAIN과 NODATA의 구분, 네거티브 TTL 계산, 권고 값 (2026-08-15 확인)
- RFC 4033 — DNS Security Introduction and Requirements — DNSSEC이 제공하는 것과 제공하지 않는 것, 네 가지 레코드, 신뢰 체인, 검증 상태 (2026-08-15 확인)
- RFC 6891 — Extension Mechanisms for DNS (EDNS(0)) — 512옥텟 제한과 OPT 레코드, 페이로드 크기 권고 (2026-08-15 확인)
- RFC 8659 — DNS Certification Authority Authorization (CAA) — CAA 태그와 트리 탐색 규칙 (2026-08-15 확인)
- RFC 2782 — A DNS RR for specifying the location of services (SRV) — SRV 형식과 대상의 별칭 금지 (2026-08-15 확인)
- BIND 9 매뉴얼 — dig, delv — 이 글에 나오는 모든
dig옵션의 근거 (2026-08-15 확인) - rndc(8) 매뉴얼 — flush, flushname, flushtree, dumpdb, reload, reconfig, querylog (2026-08-15 확인)
- resolvectl(1) 매뉴얼 — query, status, statistics, flush-caches (2026-08-15 확인)
- resolv.conf(5) 매뉴얼 — nameserver·search 상한, ndots·timeout·attempts의 기본값과 최대값 (2026-08-15 확인)
- 다섯 구간으로 나눈 진단 순서와 변경 절차의 단계 구분은 위 자료에 그대로 나오는 것이 아니라, 문서의 규칙을 바탕으로 이 글에서 정리한 절차입니다.
이어서 읽기
- 이전 글: 리눅스 방화벽과 접근 제어 완전 가이드
- 시리즈 처음: 리눅스 장애 대응 명령어 완전 가이드
- DNS 해석 순서 디버깅 — 호스트에서 이름이 풀리는 순서
- DNS 심층 분석 — 프로토콜과 메시지 구조
- DNS와 애니캐스트 — 전 세계에 같은 주소를 두는 방식
- CIDR 계산기 — 역방향 존 범위 계산
- Linux 터미널 —
dig명령 연습
The Complete Guide to DNS: Following a Name All the Way to an Address
- Introduction
- 1. The resolution path — the five segments a query crosses
- 2. Record types — what goes where
- 3. TTL and caching — what "propagation" actually is
- 4. Delegation and name servers — where authority splits
- 5. How to read dig
- 6. Common failures — propagation delay, stale caches, CNAME constraints
- 7. DNSSEC — what it guarantees and what it does not
- 8. Running DNS inside a company
- 9. The diagnostic order, summarised
- Quiz: check your understanding
- Closing
- References
- Further reading
Introduction
The moment somebody says "looks like a DNS problem" in an incident call, the investigation usually stops right there. DNS is not one system: it is five segments of very different character, chained together. A diagnosis that does not name the segment is not a diagnosis.
This blog already has DNS Deep Dive and Debugging DNS Resolution Order. The first covers protocol structure, the second covers the resolution order on a Linux host. This guide fills the gap between them. It is written for the operator who has to change a record, wait for propagation, flush a cache, and work out which segment is guilty — procedures and decision criteria.
Commands assume BIND 9 dig, the glibc stub resolver, and systemd-resolved. Every option here was checked against the manual pages and RFC text, and the sources are listed at the end with URLs and the date they were checked.
dig -v
resolvectl status
1. The resolution path — the five segments a query crosses
Turning one name into one address crosses the following five segments. Each fails for different reasons and with different symptoms.
- The application and the stub resolver. When a program calls a name resolution function, the system stub resolver handles it. RFC 8499 defines a stub resolver as "a resolver that cannot perform all resolution itself. Stub resolvers generally depend on a recursive resolver to undertake the actual resolution function."
/etc/hostsand the name service switch configuration also sit in this segment. - Resolver selection.
/etc/resolv.confdecides which server the query goes to and which search domains get appended. - The recursive resolver cache. If the answer is cached, it ends here. Most queries end in this segment — which is exactly why most change-related incidents happen here too.
- Following the delegation. On a cache miss the resolver starts at the root, passes through the TLD, and walks down to the authoritative server. RFC 1034 describes this as looking "for locally-available name server RRs, starting at SNAME, then the parent domain name of SNAME, the grandparent, and so on toward the root," falling back to a "safety belt" configuration containing the root servers.
- The authoritative answer. RFC 8499 defines an authoritative server as "a server that knows the content of a DNS zone from local knowledge, and thus can answer queries about that zone without needing to query other servers."
The reason to split the path this way is that each segment can be questioned separately.
getent hosts api.internal.example.com
cat /etc/resolv.conf
resolvectl status
dig @127.0.0.53 api.internal.example.com
dig @10.0.0.53 api.internal.example.com
dig @ns1.example.com api.internal.example.com
getent goes through the name service switch; dig does not. If those two disagree, the problem is not DNS — it is the stub resolver segment.
The directives in /etc/resolv.conf are as follows. The values are from the manual page.
nameserver— the address of a server to query. Only three are used.search— the list of domains appended to short names. Up to 6 domains and 256 characters total in glibc 2.25 and earlier; unlimited since 2.26.options ndots:n— the threshold for how many dots a name must contain before an initial absolute query is made instead of appending the search domains. Default 1, capped at 15.options timeout:n— how long to wait for a response. Default 5 seconds, capped at 30.options attempts:n— how many times to query before giving up. Default 2, capped at 5.options rotate— round-robin selection among the listed name servers.options single-request— performs the IPv6 and IPv4 requests sequentially instead of in parallel.
Two calculations fall out of this. First, if the first name server is dead, users feel the default five seconds directly. Unless you shorten the timeout and the attempt count, your redundancy exists on paper only. Second, a high ndots means even names containing a few dots get the search domains tried first, so resolving one external name is preceded by several failing queries. That is the classic reason name resolution is slow inside containers.
You can tell whether the search list is involved by splitting it out.
dig +search api
dig +nosearch api
dig api.internal.example.com.
As in that last line, a trailing dot makes the name absolute and skips the search list. Writing external domains with a trailing dot in application configuration removes the wasted queries.
2. Record types — what goes where
| Type | What it holds | The constraint that bites in operations |
|---|---|---|
| A / AAAA | IPv4 / IPv6 address | The basics. With both present the client chooses |
| CNAME | An alias to another name | Cannot coexist with other data at the same name |
| MX | Mail receiving server | The target must not be an alias |
| NS | Authoritative servers for a subzone | The target must not be an alias |
| SOA | Zone authority and the negative cache TTL | Must exist at the zone apex |
| TXT | Free text | Ownership proofs and mail policy ride on it |
| PTR | Address to name | Lives under in-addr.arpa / ip6.arpa |
| SRV | Host and port of a service | The target must not be an alias |
| CAA | CAs allowed to issue certificates | Located by climbing the tree upward |
Several of these are worth quoting the source for.
A CNAME has to be alone. RFC 1034 states flatly that "if a CNAME RR is present at a node, no other data should be present," so that the data for a canonical name and its aliases cannot diverge. And RFC 8499 defines the zone apex as "the point in the tree at an owner of an SOA and corresponding authoritative NS RRset." The apex necessarily has an SOA and NS records, so the apex cannot hold a CNAME. That is the exact basis for the wall you hit trying to point a root domain at a CDN.
NS and MX targets must not be aliases. RFC 2181 rules that "the domain name used as the value of a NS resource record, or part of the value of a MX resource record must not be an alias." SRV is the same: RFC 2782 requires of the target that "there MUST be one or more address records for this name, the name MUST NOT be an alias." The SRV format is _Service._Proto.Name TTL Class SRV Priority Weight Port Target; clients try the lowest-numbered priority they can reach, and among equal priorities selection is proportional to weight.
CAA is found by climbing upward. RFC 8659 defines the search as one that "climbs the DNS name tree from the specified label up to, but not including, the DNS root until a CAA RRset is found." The tags are issue, issuewild, and iodef, and if no tag restricts issuance, CAA does not restrict issuance. This is where "we never put a CAA on that subdomain, yet issuance was blocked" comes from.
There are size limits worth remembering too. RFC 1035 set the ceilings at 63 octets for labels, 255 octets for names, and 512 octets for UDP messages. The 512-octet limit is extended by EDNS(0) in RFC 6891, where the requestor writes the largest UDP payload it can reassemble into the CLASS field of the OPT record. That document offers 4096 octets as a starting point and a fallback around 1280 to 1410 bytes. A response larger than the advertised size sets the TC bit and has to be asked again over TCP.
3. TTL and caching — what "propagation" actually is
DNS has no push. Changes do not spread; caches expire. Understanding that one sentence removes most propagation incidents.
RFC 1034 defines the TTL as "how long a RR can be cached before it should be discarded." RFC 2181 adds the rules that bite in practice. Every record in one RRSet must carry the same TTL, and a server must never send an RRSet whose TTLs are not all equal. TTL is also an unsigned 32-bit value with a maximum of 2147483647, and values received with the most significant bit set are to be treated as zero.
Absence is cached too. RFC 2308 distinguishes two kinds.
- NXDOMAIN — the response code is Name Error, and the queried domain does not exist.
- NODATA — the response code is NOERROR but there are no relevant answers in the answer section. The name exists; only that type does not.
The cache lifetime for a negative answer is taken "from the minimum of the SOA.MINIMUM field and SOA's TTL." The same document leaves an operational recommendation: "Values of one to three hours have been found to work well and would make sensible a default. Values exceeding one day have been found to be problematic." When a typo in a record is corrected and lookups keep failing for half a day, this value is usually the reason.
Plan the change around the TTL.
- Lower the TTL (say to 300 seconds) at least one old TTL before the planned change.
- Wait until the old TTL has fully elapsed. Skip this wait and you leave resolvers whose caches never saw the lowered TTL.
- Make the change.
- Verify against the authoritative servers and against several resolvers separately.
- Once things are stable, restore the original TTL.
The time remaining in a cache is observable.
dig +noall +answer +ttlunits example.com
dig +noall +answer example.com
dig @1.1.1.1 +noall +answer example.com
dig @ns1.example.com +noall +answer example.com
Ask a resolver repeatedly and you can watch the TTL count down. The seconds remaining are exactly the time left before that resolver expires the entry. Ask the authoritative server directly and you always get the configured value.
4. Delegation and name servers — where authority splits
RFC 8499 defines delegation as the process of creating a separate zone by adding an NS RRset in the parent zone for the child origin, and calls the boundary a zone cut. There is one asymmetry here every operator must know. RFC 2181 rules that "the NS records that indicate a zone cut are the property of the child zone created." In other words the NS list held by the parent (the TLD) and the NS list the child zone answers with can differ, and the real authority is the child side.
Two common incidents come out of this.
- The name servers were changed at the registrar but nothing seems to take effect. What changed is the parent delegation. If the child zone still answers with the old server list, the result depends on what each resolver cached.
- The name server list contains a server that does not actually answer. The resolver retries when it hits a silent server, so names still resolve but intermittently slowly.
This is also where glue comes from. RFC 1034 says the NS records describing the bottom edge of a zone "are NOT part of the authoritative data of the zone," and that the parent supplies address records for the delegated name servers. When a name server name lives under the very zone it serves, you need its address to query the zone and you need to query the zone to learn its address. The address records the parent hands out break that loop.
The core of diagnosis is asking the parent and the child separately and comparing.
dig NS example.com
dig +trace example.com
dig +nssearch example.com
dig SOA example.com @ns1.example.com
dig SOA example.com @ns2.example.com
In the words of the manual, +trace "toggles tracing of the delegation path from the root name servers," and with +nssearch "dig attempts to find the authoritative name servers for the zone." Use the last two lines to check that every authoritative server reports the same SOA serial. Mismatched serials mean a zone transfer is lagging, and users are getting different answers depending on which server they reached.
5. How to read dig
The basic form is dig @server name type. The manual describes the three slots as "the name or IP address of the name server to query," "the name of the resource record that is to be looked up," and what "indicates what type of query is required."
Output is divided into the header, question, answer, authority and additional sections, plus statistics. Read it in the order status, then flags, then the sections.
The values in status are the response codes from RFC 1035.
NOERROR— no error condition. But an empty answer section means NODATA, and the name exists.NXDOMAIN— Name Error. That name does not exist.SERVFAIL— server failure. It does not mean the name is absent, it means the server could not produce an answer. Failure to reach upstream and DNSSEC validation failure are the classic causes.REFUSED— refused. A policy decision not to answer, which is what you get asking a resolver that does not offer recursion to you.
The letters in flags each mean something too.
qr— this message is a response.aa— authoritative answer. Without this letter, the answer came from cache.tc— truncated. Ask again over TCP.rd/ra— recursion desired / recursion available.ad— the resolver is signalling that the data passed DNSSEC validation.cd— the query was made with checking disabled.
The combinations worth keeping, organised by purpose:
dig +short A example.com
dig +noall +answer example.com
dig +noall +authority +additional example.com
dig @1.1.1.1 example.com
dig +norecurse @10.0.0.53 example.com
dig -x 93.184.216.34
dig -t MX example.com
dig +dnssec example.com
dig +cd example.com
dig +multiline SOA example.com
dig +tcp example.com
dig +time=2 +tries=1 example.com
dig +stats example.com
dig +yaml example.com
Each option comes straight from the manual. +short "toggles whether a terse answer is provided," +norecurse "disables the RD (recursion desired) bit in the query," +dnssec "requests that DNSSEC records be sent by setting the DNSSEC OK (DO) bit," and +cd "sets the CD (checking disabled) bit in the query." -x "sets simplified reverse lookups, for mapping addresses to names," +time=T and +tries=T set the wait and the retry count, and +subnet "sends an EDNS CLIENT-SUBNET option with the specified IP address."
One note on where +norecurse earns its keep. Query a resolver with recursion off and it answers only from its own cache. Use it to check whether an entry is cached, or to inspect state without warming the cache.
+trace has the opposite hazard. Because +trace queries each step from the root itself, it bypasses your resolver. So "+trace looks right but the application still fails" is common — and in that case the culprit is the resolver segment, not the authoritative server.
6. Common failures — propagation delay, stale caches, CNAME constraints
Propagation delay. As above, propagation is expiry. The "it takes 48 hours" advice is mostly a conservative statement built on stale TTL settings. If you did not lower the TTL before the change, there is nothing to do but wait out the old TTL, and if the authoritative server already returns the new value while users still see the old one, that is correct behaviour.
Stale caches. Caches do not live in one place. At minimum, check each of these.
- The cache in the internal recursive resolver
- The local cache on the host (systemd-resolved and the like)
- The runtime own cache (some runtimes keep their own name cache)
The command differs per layer.
sudo rndc flush
sudo rndc flushname api.example.com
sudo rndc flushtree example.com
sudo rndc dumpdb -cache
resolvectl flush-caches
resolvectl query api.example.com
resolvectl statistics
rndc flush "flushes the server's cache," while flushname "flushes the given name from the view's DNS cache and, if applicable, from the view's nameserver address database, bad server cache, and SERVFAIL cache." flushtree removes "the given name, and all of its subdomains" from the same set. resolvectl flush-caches "flushes all DNS resource record caches the service maintains locally." Try the per-name flush before the whole-cache flush. Throwing the whole cache away spikes upstream queries for the next several minutes.
CNAME constraints. Three of them recur. No CNAME at the apex; no alias as the target of MX, NS or SRV; and no other records alongside a name that has a CNAME. The first two are more dangerous because a violation can appear to work for a while. A specification violation that appears to work surfaces at the latest and worst possible moment.
Mistaking NODATA for NXDOMAIN. If the status is NOERROR and the answer is empty, the name exists. The textbook case is a client trying IPv6 against a name that simply has no AAAA, and deleting and recreating the name achieves precisely nothing.
Blocked TCP. When DNSSEC signatures or record counts make a response large, the TC bit is set and the query is repeated over TCP. If the firewall opened only UDP 53, everything looks fine until one particular name fails. Telling that apart is simple.
dig +tcp example.com
dig +dnssec +tcp example.com
The name resolves but the connection fails. If dig answers correctly, the DNS segment is innocent. What follows is a connectivity problem and needs different tools.
7. DNSSEC — what it guarantees and what it does not
In the words of RFC 4033, DNSSEC provides "data origin authentication and data integrity to the Domain Name System." The same document is explicit about what it does not provide: "DNSSEC is not designed to provide confidentiality, access control lists, or other means of differentiating between inquirers." It goes further, stating that "DNSSEC provides no protection against denial of service attacks," and warns that it creates a new class of denial of service attacks based on cryptographic operations. If you want protection against eavesdropping you need a measure at another layer.
There are four record types.
- DNSKEY — carries the public keys of the zone.
- RRSIG — the digital signature over an RRset.
- DS — sits at the delegation point in the parent zone and points at the child key.
- NSEC — authenticates the non-existence of a name or a type.
The chain of trust is an alternating sequence of DNSKEY and DS RRsets in which each link vouches for the next, and validation starts from a trust anchor. Validation lands in one of four states: Secure with a trust anchor, a chain of trust, and all signatures verified; Insecure with signed proof that the data is not signed; Bogus when data that should be signed fails to validate; and Indeterminate when no trust anchor says that part of the tree is secure.
What matters to the operator is that failure shows up as SERVFAIL. The zone contents are unchanged, but once the signature validity period passes, lookups fail only on validating resolvers. On non-validating resolvers everything looks fine. You tell them apart by asking with validation off.
dig +dnssec example.com
dig +cd example.com
delv example.com
If adding +cd produces an answer, this is a DNSSEC validation failure. delv is the purpose-built tool the manual describes as "a tool for sending DNS queries and validating the results."
Internal environments hit one more thing. RFC 4033 calls a signed zone with no authentication chain from its delegating parent an "island of security," and explains that such a zone can only be authenticated if its keys can be authenticated out of band. How a validating resolver treats an unsigned internal zone falls under this, so the resolver configuration has to handle internal zones explicitly.
8. Running DNS inside a company
Split horizon. One name answered differently inside and outside. Convenient, but the answer now depends on where the question was asked from, so the first thing to establish on a report is the location of the reporter. Whether they were on the VPN changes the conclusion.
Forwarder or recursion. RFC 8499 defines a forwarder as "a nameserver used to resolve queries instead of directly using the authoritative nameserver chain." If your internal resolver forwards upstream, then when the upstream dies the internal resolver ends up alive and unable to answer anything. That is why the health check has to be a successful query, not a live process.
The reality of resolver redundancy. As above, only three nameserver entries are used, the default timeout is 5 seconds, and the default attempt count is 2. If the first server does not answer, users feel that time. Put the timeout and the attempt count in the standard company resolv.conf, and use rotate to spread the load when it helps.
Change and verification. After changing a zone, check that every authoritative server gives the same answer.
sudo rndc reload
sudo rndc status
sudo rndc querylog on
dig SOA example.com @ns1.example.com +short
dig SOA example.com @ns2.example.com +short
sudo rndc querylog off
rndc reload "reloads the configuration file and zones," whereas rndc reconfig "reloads the configuration file and loads new zones, but does not reload existing zone files even if they have changed." Not knowing that difference produces a recurring "I reloaded, why did nothing change." querylog turns query logging on and off, and on a busy server you turn it on for the investigation window only, not permanently.
Container environments. The resolv.conf the orchestrator injects often carries several search domains and a high ndots. The result is that resolving one external name is preceded by several failing queries. Adding a trailing dot to external domains in application configuration removes a large part of it.
What to monitor. These four catch most incidents before users do.
- Matching SOA serials across every authoritative server
- Days remaining on signature validity, if you sign
- Query latency and SERVFAIL ratio on the recursive resolver
- Expiry dates of the domain and the delegation
9. The diagnostic order, summarised
Given a symptom, narrow it in this order. Each step earns its place by ruling the previous one out.
- Application or system. Compare
getent hostsagainstdig. A difference means the stub resolver segment. - Which resolver. Confirm the server actually in use with
/etc/resolv.confandresolvectl status, then query that server directly. - Cache or authority. Compare the resolver answer with the authoritative answer. A difference means cache, and the TTL tells you how much time is left.
- Delegation. Check the delegation with
dig +traceand by comparing parent and child NS. - Validation. On SERVFAIL, ask again with
+cd. An answer means DNSSEC validation failure. - Not DNS. If the name resolves correctly, close the DNS investigation here and hand it to the connectivity layer.
getent hosts example.com
dig +short example.com
dig @1.1.1.1 +short example.com
dig @ns1.example.com +short example.com
dig +trace example.com
dig +cd example.com
Quiz: check your understanding
Quiz 1: dig answers correctly but only the application reports "name not found". What do you check first?
Answer: The stub resolver path. dig does not go through the name service switch and reads only /etc/resolv.conf, so the two paths differ
Why: Start by putting getent and dig side by side.
getent hosts api.example.com
dig +short api.example.com
cat /etc/resolv.conf
If only getent fails, look at the name service switch configuration, /etc/hosts, the resolv.conf mounted into the container, and the search domain and ndots settings. Some applications embed their own resolver, so check the runtime name resolution settings as well.
Quiz 2: You changed an A record and some users still reach the old address. What is happening, and what will you do differently next time?
Answer: Resolver caches have not expired yet. Next time, lower the TTL at least one old TTL before the change
Why: DNS has no push. Changes do not spread; caches expire. The time remaining differs per resolver, and it is observable.
dig @1.1.1.1 +noall +answer +ttlunits example.com
dig @ns1.example.com +noall +answer example.com
The resolver-side TTL counts down as you repeat the query, while the authoritative side always shows the configured value. Those two differing is normal.
Quiz 3: The internal resolver returns SERVFAIL while a public resolver answers fine. What do you suspect, and how do you settle it in one step?
Answer: Suspect DNSSEC validation failure, and settle it by asking again with validation disabled
Why: SERVFAIL does not mean the name is absent; it means the server could not produce an answer. Validation failure and failure to reach upstream are the classic causes.
dig @10.0.0.53 example.com
dig @10.0.0.53 +cd example.com
delv example.com
If adding +cd produces an answer, you were blocked at the validation step — check signature expiry and DS mismatch first. If it still fails with +cd, the problem is reachability or the upstream response, not validation.
Quiz 4: You tried to point the root domain example.com at a CDN host name with a CNAME and the zone was rejected. What is the specification reason?
Answer: The zone apex must carry an SOA and NS records, and a CNAME cannot coexist with other data at the same name
Why: RFC 1034 rules that if a CNAME is present at a node, no other data should be present. And the zone apex is defined as the owner of an SOA and the corresponding authoritative NS RRset. The two rules cannot hold at once.
The same reasoning makes this a violation:
example.com. IN MX 10 mail-alias.example.com.
mail-alias IN CNAME real-mail.example.net.
RFC 2181 rules that NS and MX targets must not be aliases, and RFC 2782 places the same requirement on SRV targets.
Quiz 5: The response is NOERROR but the ANSWER section is empty. Does that mean the name does not exist?
Answer: It exists. This is NODATA — the name is there and only the queried type is missing
Why: RFC 2308 separates NXDOMAIN from NODATA. NXDOMAIN means the name itself does not exist; NODATA means the response code is NOERROR with no relevant answers in the answer section.
dig AAAA example.com
dig A example.com
dig ANY example.com
Both are cached as negative responses, for the smaller of the SOA MINIMUM field and the TTL of the SOA record itself. That is why failures can persist for a while after you fix the wrong record.
Quiz 6: dig +trace shows the right address but the application on the server still connects to the old one. Where do you look?
Answer: The resolver segment. +trace queries from the root itself and bypasses the resolver that host actually uses
Why: A clean +trace only tells you the authoritative side is fine. To reproduce the real path you have to ask the resolver that host uses.
cat /etc/resolv.conf
dig @10.0.0.53 +noall +answer example.com
dig @10.0.0.53 +norecurse example.com
sudo rndc flushname example.com
+norecurse makes the resolver answer only from its own cache, so you learn immediately whether the cache is the cause. If there is a separate local cache, flush that too.
Closing
DNS incidents are hard not because DNS is hard, but because people discuss them without saying which segment they are looking at. State every time whether the answer came from a resolver or an authoritative server, from cache or a fresh lookup, validated or not, and the conversation gets dramatically shorter.
Three habits to build immediately. Ask getent and dig side by side to rule out the stub resolver segment first, query the resolver and the authoritative server separately to separate cache from origin, and lower the TTL at least one old TTL before you change a record. Those three alone make most DNS incidents disappear, or at least resolve the question of which segment is guilty within minutes.
References
- RFC 1034 — Domain Names: Concepts and Facilities — the resolution algorithm, the CNAME exclusivity rule, delegation and glue, the definition of TTL (verified 2026-08-15)
- RFC 1035 — Domain Names: Implementation and Specification — message sections and header bits, response codes, label, name and UDP size ceilings (verified 2026-08-15)
- RFC 8499 — DNS Terminology — definitions of stub resolver, recursive resolver, authoritative server, delegation, zone cut, forwarder and zone apex (verified 2026-08-15)
- RFC 2181 — Clarifications to the DNS Specification — equal TTLs within an RRSet, the TTL ceiling, the alias prohibition for NS and MX targets, ownership of zone-cut NS records (verified 2026-08-15)
- RFC 2308 — Negative Caching of DNS Queries — NXDOMAIN versus NODATA, the negative TTL calculation, the recommended values (verified 2026-08-15)
- RFC 4033 — DNS Security Introduction and Requirements — what DNSSEC does and does not provide, the four record types, the chain of trust, the validation states (verified 2026-08-15)
- RFC 6891 — Extension Mechanisms for DNS (EDNS(0)) — the 512-octet limit and the OPT record, the payload size recommendations (verified 2026-08-15)
- RFC 8659 — DNS Certification Authority Authorization (CAA) — the CAA tags and the tree-climbing rule (verified 2026-08-15)
- RFC 2782 — A DNS RR for specifying the location of services (SRV) — the SRV format and the alias prohibition on targets (verified 2026-08-15)
- BIND 9 manual pages — dig, delv — the source for every
digoption in this guide (verified 2026-08-15) - rndc(8) manual page — flush, flushname, flushtree, dumpdb, reload, reconfig, querylog (verified 2026-08-15)
- resolvectl(1) manual page — query, status, statistics, flush-caches (verified 2026-08-15)
- resolv.conf(5) manual page — the nameserver and search ceilings, the defaults and maximums for ndots, timeout and attempts (verified 2026-08-15)
- The five-segment split and the staged change procedure are not lifted from the sources above; they are the arrangement this guide puts on top of the documented rules.
Further reading
- Previous: The Complete Guide to Linux Firewalls and Access Control
- Series start: The Complete Guide to Linux Incident Response Commands
- Debugging DNS Resolution Order — the order in which a host resolves a name
- DNS Deep Dive — the protocol and the message structure
- DNS and Anycast — putting the same address everywhere in the world
- CIDR Calculator — working out reverse zone ranges
- Linux Terminal — practising
dig