수정 약 25분

DNS 전파 확인하고 기다릴 시간 계산하는 법

이 글의 순서 11개 절
  1. 얼마나 기다리나
  2. 권한 서버와 리졸버
  3. 레코드 값을 바꿀 때
  4. 새 레코드를 만들 때
  5. 네임서버를 바꿀 때
  6. DNSSEC 존을 옮길 때
  7. 캐시 비우기
  8. A 레코드를 옮기는 순서
  9. certdesk DNS 전파 확인
  10. 인증서 작업과 엮일 때
  11. 참조

DNS 레코드를 바꿨다. 내 PC에서는 새 주소가 나오는데 옆자리 PC는 아직 옛 서버로 간다. DNS 사업자 화면에는 “전파에 최대 48시간이 걸릴 수 있다”는 안내가 붙어 있고, 다들 그 말을 믿고 기다린다. 얼마나 기다려야 하는지는 모르는 채로.

DNS에는 변경을 밀어 보내는 경로가 없다. 바뀌는 것은 권한 네임서버의 데이터뿐이고, 나머지는 리졸버가 캐시에 넣어 둔 옛 답이 TTL만큼 지나 저절로 만료되기를 기다리는 시간이다. 그래서 대기 시간은 계산할 수 있다. 아래 TTL과 출력은 2026-10-04에 dig로 직접 조회한 값이다.

얼마나 기다리나

바꾼 것 최악 대기 시간 예 (2026-10-04 측정값 기준) 먼저 볼 것
기존 레코드 값 (A, CNAME, TXT) 권한 서버 동기화 + 변경 시점에 걸려 있던 TTL TTL을 미리 300으로 낮췄다면 변경 후 약 5분 권한 서버 전부가 새 값을 주는지
새 레코드 권한 서버 동기화. 만들기 전에 조회됐다면 ‘없음’ 캐시 min(SOA TTL, SOA MINIMUM)이 더해진다 거의 즉시. 미리 조회됐다면 .com 존 900초, example.com 1800초 만들기 전에 조회하지 않았는지
네임서버 (등록기관) 등록기관 반영 + max(부모 NS TTL, 옛 존 NS TTL) .com 172800초 = 48시간 TLD 서버가 새 NS를 주는지
DNSSEC 존의 DNS 사업자 이전 DS 삭제 후 부모 존의 DS TTL + 네임서버 변경 몫 .com, .kr 86400초 + 위 DS가 새 키와 맞는지. 안 맞으면 기다려도 안 풀린다
DS 수정 직후 해석 실패 캐시 최대 5분 (RFC 9520) 설정을 다시 뒤집지 말고 기다린다
내 PC, 브라우저 OS와 브라우저 캐시 제각각 캐시를 비우고 다시 확인

기준은 언제나 권한 서버다. 권한 서버가 모두 새 값을 주기 전에는 리졸버를 아무리 들여다봐도 소용없다. 리졸버의 TTL 상한(Google은 대체로 6시간)은 표의 시간을 줄이고, OS와 브라우저 캐시는 늘린다. 표의 시간이 지났는데도 옛 값이 나오면 더 기다리지 말고 권한 서버부터 다시 본다.

권한 서버와 리졸버

DNS 서버는 역할이 둘로 나뉜다. 권한 네임서버는 존 데이터를 가진 서버이고, DNS 사업자 관리 화면에서 고치는 것이 여기다. 재귀 리졸버는 사용자 대신 물어 주는 서버다. 통신사 DNS, 그리고 8.8.8.8, 1.1.1.1 같은 공용 DNS가 이쪽이다. 리졸버는 받은 답을 캐시에 넣어 두고 다음 질의에 그대로 준다.

캐시를 얼마나 둘지는 레코드의 TTL이 정한다. RFC 1035는 TTL을 레코드를 캐시해도 되는 시간, 그 뒤에는 원본에 다시 물어야 하는 시간으로 정의한다. 캐시 안의 TTL은 0을 향해 줄어든다(§6.1.3). 리졸버에 물을 때마다 보이는 TTL이 작아지는 이유다.

밀어 보내는 동작이 하나 있긴 하다. 같은 존을 가진 권한 서버끼리다. 주 서버가 존이 바뀌었다고 보조 서버에 알리는 NOTIFY(RFC 1996)는 RFC 스스로 폴링이 아닌 인터럽트 방식이라고 설명한다. 리졸버 캐시까지 닿는 경로는 없다.

그래서 ’전파’라는 말에는 서로 다른 두 가지가 섞여 있다.

구분 무엇 시간을 정하는 것
권한 서버 간 동기화 사업자의 모든 권한 서버가 새 데이터를 갖게 되는 것 사업자 구현 (NOTIFY, SOA REFRESH 등)
리졸버 캐시 만료 옛 답을 캐시한 리졸버가 다시 묻게 되는 것 레코드의 TTL

Let’s Encrypt 문서도 DNS 사업자의 “propagation time”을 레코드를 바꾼 뒤 그 사업자의 모든 서버에서 보이기까지 걸리는 시간이라고 쓴다. 표의 첫 줄이다.

같은 순간에도 리졸버마다 남은 TTL이 다르다

권한 서버의 TTL이 300인 example.com A 레코드를 2026-10-04 14:19(KST)에 certdesk DNS 전파 확인으로 조회했다. 값은 모두 같고 남은 TTL만 다르다.

조회 대상 남은 TTL (초)
권한 서버 2곳 (hera·elliott.ns.cloudflare.com) 300
KT 123
SK브로드밴드 80
LG U+ 269
Cloudflare 120
Google 300
Quad9 15
OpenDNS 16

이 순간 값을 바꿨다면 LG U+ 행의 리졸버는 최대 269초, Quad9 행은 15초 동안 옛 값을 줬을 것이다. 리졸버마다 이 레코드를 캐시에 넣은 시각이 다르기 때문이다.

같은 리졸버 주소라고 캐시가 하나인 것도 아니다. 같은 이름을 연달아 물었다.

for i in 1 2 3 4 5 6; do dig +nocmd @1.1.1.1 example.com A +noall +answer | head -1; done
example.com.		175	IN	A	104.20.23.154
example.com.		130	IN	A	104.20.23.154
example.com.		130	IN	A	172.66.147.243
example.com.		16	IN	A	172.66.147.243
example.com.		41	IN	A	172.66.147.243
example.com.		41	IN	A	172.66.147.243

캐시가 하나라면 TTL은 줄어들기만 하거나, 새로 받아 300으로 돌아가야 한다. 몇 초 사이에 16 다음 41이 나온 것은 답한 캐시가 달랐다는 뜻이다. 8.8.8.8도 294, 300, 199가 섞여 나왔다. “방금 1.1.1.1에서 새 값이 나왔다”가 “1.1.1.1은 다 바뀌었다”는 뜻은 아니다.

사람마다 다른 답을 보는 이유

위 표와 반복 조회에서 본 것처럼 리졸버마다 캐시한 시각이 다르고, 같은 리졸버 주소 뒤에도 캐시가 여럿이다.

권한 서버끼리 아직 동기화 전일 수도 있다. 그러면 어느 권한 서버에 닿았느냐에 따라 답이 다르다. 사업자는 흔히 anycast(여러 서버가 같은 IP를 쓰는 방식)를 써서 IP 하나 뒤에도 서버가 여럿이다(Let’s Encrypt 문서).

처음부터 위치별로 다른 답을 주게 설정된 이름도 있다. CDN이나 지역 기반 DNS다. 리졸버가 사용자 쪽 네트워크 정보를 권한 서버에 넘기는 ECS(EDNS Client Subnet, RFC 7871)도 이런 응답에 쓰인다. 전파와 상관없는 정상 동작이라 기다려도 같아지지 않는다.

여기까지는 모두 서버 쪽 사정이라 내 PC의 캐시를 비워도 그대로다. 내 쪽 문제는 OS와 브라우저가 따로 하는 캐시뿐이다(아래 ‘캐시 비우기’).

레코드 값을 바꿀 때

변경 직전에 옛 값을 캐시한 리졸버는 그 TTL이 끝날 때까지 옛 값을 준다. 그래서 최악 대기 시간은 변경 시점에 그 레코드에 걸려 있던 TTL이다. 새로 정한 TTL이 아니다.

TTL을 바꾸는 것도 같은 규칙을 따른다. TTL 3600을 300으로 낮춰도, 낮추기 직전에 3600으로 캐시한 리졸버는 최대 3600초 동안 옛 값과 옛 TTL을 쥐고 있다. 그래서 TTL은 미리 낮추고, 옛 TTL만큼 기다린 뒤 값을 바꾼다. RFC 1034 §3.6에 이미 적혀 있는 방법이다.

If a change can be anticipated, the TTL can be reduced prior to the change to minimize inconsistency during the change, and then increased back to its former value following the change.

TTL이 아주 길어도 리졸버가 그만큼 다 쓰지는 않는다. RFC 2181 §8이 허용하는 TTL 최댓값은 2^31−1초인데, RFC 8767은 TTL 정의를 고치면서 캐시 상한을 두라고 하고 권장값으로 604,800초(7일)를 든다. Google Public DNS FAQ는 자기 캐시의 TTL이 실제 TTL이 더 길어도 대체로 6시간으로 제한된다고 적는다. 다만 상한은 리졸버마다 다르니 계산은 옛 TTL 그대로 잡는다.

새 레코드를 만들 때

새로 만든 레코드는 원칙적으로 바로 보인다. 아무도 캐시한 적이 없기 때문이다. 예외는 만들기 전에 누군가 이미 물어본 경우다. ’없다’는 답도 캐시된다.

RFC 2308은 ’없음’을 두 가지로 나눈다.

응답 뜻 캐시 단위 (§5)
NXDOMAIN 그 이름 자체가 없다 이름 (모든 타입에 적용)
NODATA 이름은 있는데 그 타입이 없다 (status: NOERROR, ANSWER 0) 이름 + 타입

권한 서버는 부정 응답의 AUTHORITY 섹션에 존의 SOA 레코드를 넣어야 한다(MUST). 그 SOA의 TTL은 SOA 레코드 자신의 TTL과 SOA의 MINIMUM 필드 중 작은 값이고, 리졸버가 ’없음’을 캐시할 수 있는 시간이 된다(§3, §5).

.com에 없는 도메인을 TLD 서버에 직접 물으면 이렇게 나온다.

dig @a.gtld-servers.net this-name-should-not-exist-20261004.com A +norecurse
;; ->>HEADER<<- opcode: QUERY, status: NXDOMAIN, id: 48905
;; flags: qr aa; QUERY: 1, ANSWER: 0, AUTHORITY: 1, ADDITIONAL: 1
...
;; AUTHORITY SECTION:
com.			900	IN	SOA	a.gtld-servers.net. nstld.verisign-grs.com. 1791085514 1800 900 604800 900

SOA의 숫자는 순서대로 SERIAL, REFRESH, RETRY, EXPIRE, MINIMUM이다(RFC 1035 §3.3.13). SOA TTL 900, MINIMUM 900이니 이 ’없음’은 최대 900초 캐시된다.

자기 존의 값은 권한 서버에 SOA를 물어 확인한다. 리졸버에 물으면 남은 시간이 나오기 때문이다.

dig @hera.ns.cloudflare.com example.com SOA +norecurse +noall +answer
example.com.		1800	IN	SOA	elliott.ns.cloudflare.com. dns.cloudflare.com. 2416374680 10000 2400 604800 1800

이 존이면 min(1800, 1800) = 1800초, 30분이다.

인증서 작업에서 자주 밟는 경우가 이것이다. DNS-01용 _acme-challenge TXT를 만들기 전에 확인 삼아 조회하면 그 이름이 NXDOMAIN으로 캐시된다. NXDOMAIN은 이름 단위라 뒤이어 TXT를 만들어도 그 리졸버에서는 위 시간 동안 ’없음’이 나온다. 레코드는 먼저 만들고, 확인은 권한 서버에서 한다.

RFC 2308은 리졸버가 부정 응답 캐시에도 상한을 두라고 하며, 1~3시간이 잘 동작했고 하루를 넘는 값은 문제가 됐다고 적는다.

네임서버를 바꿀 때

DNS 사업자를 옮기면 등록기관(도메인을 산 곳)에서 네임서버를 바꾼다. 이 값은 내 존이 아니라 부모 존, 즉 .com이나 .kr 같은 TLD 존의 위임(NS) 정보로 들어간다. 그래서 기다릴 것이 둘이다. 하나는 등록기관이 레지스트리에 반영하는 시간으로, 등록기관마다 다르다. Cloudflare 설정 문서는 등록기관이 네임서버를 갱신하는 데 최대 24시간을 기다리라고 안내한다. 다른 하나는 리졸버가 캐시한 옛 NS가 만료되는 시간, 곧 TLD가 위임에 붙인 TTL이다.

TLD 서버에 직접 물어 위임 TTL을 쟀다.

dig @a.gtld-servers.net example.com NS +norecurse
;; flags: qr; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 13
...
;; AUTHORITY SECTION:
example.com.		172800	IN	NS	hera.ns.cloudflare.com.
example.com.		172800	IN	NS	elliott.ns.cloudflare.com.

;; ADDITIONAL SECTION:
hera.ns.cloudflare.com.	172800	IN	A	108.162.192.162
...

flags에 aa가 없고 답이 AUTHORITY 섹션에 있다. TLD 서버가 이 이름의 답 대신 “저쪽 서버에 물어보라”는 위임(referral)을 준다는 뜻이다.

TLD 물어본 서버 위임 NS TTL DS TTL
.com a.gtld-servers.net 172800초 (2일) 86400초 (1일, cloudflare.com)
.net a.gtld-servers.net 172800초 (2일) 측정 안 함
.kr b.dns.kr 86400초 (1일, kisa.or.kr) 86400초 (1일, kisa.or.kr)

흔히 듣는 ’최대 48시간’은 .com 위임 TTL 172800초와 숫자가 같다. TLD마다 값이 다르니 내 TLD는 직접 잰다.

dig kr NS +short                          # 그 TLD 의 권한 서버 목록
dig @b.dns.kr kisa.or.kr NS +norecurse     # 위임 NS 와 TTL
dig +trace example.com                    # 루트부터 위임 경로 전체

+trace는 루트 서버부터 위임을 따라가며 단계마다 받은 답을 보여 준다(man dig). 등록기관에서 바꾼 NS가 TLD 단계에 나오면 레지스트리 반영은 끝난 것이다.

레지스트리 쪽 반영 자체는 빠를 수 있다. 같은 날 a.gtld-servers.net이 준 .com SOA serial 1791085769는 Unix 시각으로 03:49:29 UTC였고, 조회 시각과 1분도 차이 나지 않았다. 오래 걸리는 쪽은 캐시다.

부모의 NS와 자식의 NS

내 존 안에도 NS 레코드가 있다. 같은 example.com을 권한 서버에 물으면 TTL이 다르다.

dig @hera.ns.cloudflare.com example.com NS +norecurse +noall +answer
example.com.		86400	IN	NS	elliott.ns.cloudflare.com.
example.com.		86400	IN	NS	hera.ns.cloudflare.com.

RFC 2181 §5.4.1은 리졸버가 캐시에 무엇을 남길지 정할 때 쓰는 신뢰도 순위를 정한다. 권한 응답의 데이터가 위임 응답 AUTHORITY 섹션의 데이터보다 위이고, 부모 존에 있는 자식의 NS는 glue로 분류된다. 리졸버가 자식 존에서 NS를 받으면 부모에게서 받은 NS를 그것으로 바꿀 수 있다는 뜻이다. 그래서 옛 사업자 존의 NS TTL도 계산에 넣고, 최악은 부모 NS TTL과 옛 존 NS TTL 중 큰 값으로 잡는다.

옛 사업자의 존은 남겨 둔다

이 기간에 옛 NS를 기억하는 리졸버는 계속 옛 사업자에게 묻는다. Google Public DNS FAQ는 네임서버를 바꾼 직후 하위 이름부터 캐시를 비우면 옛 DNS 서버의 오래된 데이터로 다시 채워진다고 경고한다. 옛 서버가 실제로 질의를 계속 받는다는 뜻이다.

그러니 옛 사업자의 존을 바로 지우지 않는다. 대기 시간 동안 새 존과 같은 데이터를 유지하고, 그 사이 레코드를 고치면 양쪽을 똑같이 고친다. 옛 존을 지우면 옛 NS를 기억하는 리졸버는 갱신에 실패한다. RFC 8767(serve-stale)은 권한 서버에 닿지 못할 때 리졸버가 만료된 값을 계속 쓰도록 허용하고, 만료 후 보관 기간으로 1~3일을 제안한다. 옛 데이터가 계산보다 더 오래 보일 수 있다.

DNSSEC 존을 옮길 때

DNSSEC을 쓰는 존은 부모 존에 DS 레코드가 있다. DS는 “이 존의 키는 이것”이라는 지문이다. cloudflare.com의 DS와 DNSKEY를 나란히 놓으면 숫자가 맞물린다.

dig @a.gtld-servers.net cloudflare.com DS +norecurse +noall +answer
dig @elliott.ns.cloudflare.com cloudflare.com DNSKEY +norecurse +multi +noall +answer
cloudflare.com.		86400	IN	DS	2371 13 2 32996839A6D808AFE3EB4A795A0E6A7A...

cloudflare.com.		3600 IN	DNSKEY 257 3 13 (
				(공개키 생략)
				) ; KSK; alg = ECDSAP256SHA256 ; key id = 2371

DS의 첫 값 2371과 KSK의 key id 2371이 같다. 이 연결이 끊기면 검증이 실패한다.

DNS 사업자를 옮기면 새 사업자는 자기 키로 서명한다. 등록기관에 옛 DS가 남아 있으면 새 서명은 옛 지문과 맞지 않는다. 검증하는 리졸버는 이 응답을 bogus로 판정하고 SERVFAIL(RCODE 2)을 돌려준다(RFC 4033 §5, RFC 4035 §5.5). 캐시 만료를 기다릴 문제가 아니다. DS를 고치기 전까지 계속 실패한다.

검증 실패는 이렇게 보인다. dnssec-failed.org는 DNSSEC 검증이 실패하는 이름이다.

dig @8.8.8.8 dnssec-failed.org A | grep status
dig @8.8.8.8 dnssec-failed.org A +cd | grep -E 'status|^dnssec'
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 58351

;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7611
dnssec-failed.org.	300	IN	A	96.99.227.255

+cd는 리졸버에 검증을 하지 말라고 요청하는 CD 비트다(man dig). RFC 4035 §5.5는 검증에 실패해도 질의에 CD 비트가 있으면 전체 응답을 돌려주라고 한다. CD 없이는 SERVFAIL인데 CD를 켜면 정상이라면 DNSSEC 문제다. 2026-10-04에 8.8.8.8과 1.1.1.1이 모두 이 모양으로 답했다.

증상이 ’일부 사용자만 안 된다’로 나타나는 이유도 여기 있다. 검증하지 않는 리졸버는 서명을 보지 않고 답을 준다. 2026-10-04 14:16(KST)에 certdesk DNS 전파 확인으로 같은 이름을 조회하니 공용 DNS 4곳(Cloudflare, Google, Quad9, OpenDNS)은 SERVFAIL, 통신 3사 리졸버는 주소를 돌려줬다. 리졸버 설정은 바뀔 수 있으니 그 시점의 측정으로만 본다. 어느 쪽 리졸버를 쓰느냐에 따라 같은 사이트가 열리기도 하고 안 열리기도 한다.

옮기는 순서

두 사업자가 서로의 키를 함께 게시하는 multi-signer 구성(RFC 8901)이 되면 DNSSEC을 켠 채로 옮길 수 있다. Cloudflare 문서는 옛 사업자가 외부 DNSKEY 추가를 지원할 때 이 방식을 안내한다. 지원하지 않으면 잠시 DNSSEC 없이(insecure) 옮기는 수밖에 없다. RFC 6781 §4.3.5.2는 협조하지 않는 사업자 사이의 이전에서 이것을 “유일하게 가능한 방법”으로 적는다.

  1. 등록기관에서 DS를 지운다.
  2. 부모 존의 DS TTL만큼 기다린다(.com, .kr 측정값 86400초). dig example.com DS +short가 빈 출력인지 본다.
  3. 등록기관에서 네임서버를 바꾼다.
  4. 옛 NS가 캐시에서 빠질 시간을 기다린 뒤, 새 사업자에서 서명을 켜고 새 DS를 등록한다.

Cloudflare 문서는 옛 DS TTL이 끝나기 전에 네임서버를 바꾸면 검증하는 리졸버가 SERVFAIL을 돌려준다고 경고한다. 캐시된 DS가 새 키와 맞지 않기 때문이다. 같은 이유로 DNSSEC을 끌 때도 DS를 지운 직후에 서명을 끄지 않는다. 같은 문서의 롤백 절차는 DS를 지운 뒤 DS TTL이 끝날 때까지 서명을 유지하라고 한다.

캐시 비우기

내 PC와 브라우저

서버 쪽이 다 바뀌었는데 내 화면만 옛 값이면 클라이언트 캐시다.

환경 명령 문서의 설명
Windows ipconfig /flushdns DNS 클라이언트 리졸버 캐시를 비우고 재설정한다
Linux (systemd-resolved) resolvectl flush-caches 서비스가 로컬에 둔 DNS 캐시를 모두 비운다. 캐시 내용은 resolvectl show-cache
macOS sudo dscacheutil -flushcache Directory Service 캐시 전체를 비운다 (man dscacheutil)

브라우저도 따로 캐시한다. Chrome은 chrome://net-internals/#dns에 Clear host cache 버튼이 있다. DNS 캐시를 비워도 이미 열린 연결은 옛 서버에 붙은 채 재사용될 수 있으니, chrome://net-internals/#sockets의 Flush socket pools로 연결까지 끊고 다시 연다. 두 버튼 모두 Chromium 소스의 net-internals 페이지에 있다.

공용 DNS

공용 DNS 두 곳은 캐시를 비우는 페이지를 공개한다. Google Public DNS의 Flush Cache는 도메인 소유 확인 없이 reCAPTCHA만 풀면 되고, 이름과 레코드 타입 단위로 비운다. Cloudflare 1.1.1.1의 Purge Cache도 도메인 이름과 레코드 타입을 골라 비운다.

Google FAQ의 주의 사항은 그대로 실무 순서가 된다.

  • 네임서버를 바꾼 직후에는 등록 도메인부터 비운다. 등록·위임된 도메인을 비우면 그 도메인의 네임서버 위임 정보까지 함께 비워진다. www 같은 하위 이름을 먼저 비우면 옛 DNS 서버의 데이터로 다시 채워진다.
  • CNAME이 엮여 있으면 체인의 마지막 CNAME부터 거꾸로 비운다.
  • ECS로 지역별 응답을 주는 도메인은 비울 수 없다. 이런 레코드는 TTL을 15분 이하로 두라고 한다.
  • 모든 하위 이름과 모든 타입을 한 번에 비우는 방법은 없다.

어디까지나 그 서비스의 캐시만 비운다. 통신사나 사내 리졸버의 캐시는 밖에서 비울 수 없으니, 확실한 단축 방법은 TTL을 미리 낮춰 두는 것뿐이다.

A 레코드를 옮기는 순서

A 레코드를 새 서버로 옮기는 경우다. 먼저 권한 서버에서 현재 값과 TTL을 본다.

dig example.com NS +short                       # 권한 서버 이름
dig @hera.ns.cloudflare.com example.com A +norecurse
;; flags: qr aa; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
...
example.com.		300	IN	A	104.20.23.154
example.com.		300	IN	A	172.66.147.243

flags에 aa가 있어야 권한 응답이다. +norecurse는 질의의 RD 비트를 끈다(man dig).

다음으로 TTL을 낮추고 옛 TTL만큼 기다린다. 3600을 300으로 낮췄다면 낮춘 시각에서 3600초가 지나야 모든 캐시가 300 이하로 내려온다. 그다음에 값을 바꾼다. 옛 서버는 켜 둔다.

바꾼 뒤에는 권한 서버 전부를 확인한다. 사업자 내부 동기화가 끝났는지 보는 단계다.

dig +nssearch example.com
SOA elliott.ns.cloudflare.com. dns.cloudflare.com. 2416374680 10000 2400 604800 1800 from server 173.245.58.162 in 8 ms.
SOA elliott.ns.cloudflare.com. dns.cloudflare.com. 2416374680 10000 2400 604800 1800 from server 172.64.32.162 in 8 ms.
...

+nssearch는 그 존의 권한 서버를 찾아 서버마다 가진 SOA를 보여 준다(man dig). serial(세 번째 값)이 모두 같으면 같은 판의 존이다. 바꾼 레코드는 서버마다 직접 물어 확정한다.

for ns in $(dig example.com NS +short); do echo "== $ns"; dig @$ns www.example.com A +norecurse +short; done

anycast로 운영되는 권한 서버라면 이 결과는 내 위치에서 닿는 인스턴스의 답일 뿐이다.

그다음 리졸버를 비교한다.

for r in 8.8.8.8 1.1.1.1 9.9.9.9; do echo "== $r"; dig @$r www.example.com A +noall +answer; done
dig www.example.com A +noall +answer        # 서버 지정 없음 = /etc/resolv.conf 의 리졸버

서버를 지정하지 않은 dig는 /etc/resolv.conf에 적힌 서버에 묻는다(man dig). 남은 TTL이 옛 TTL 이하에서 줄어들고 있으면 정상 진행이다. 조회가 시간 초과로 끝나면 그 리졸버까지 가는 경로 문제이지 DNS 값의 문제가 아니다.

최악 대기 시간이 지나면 옛 서버를 정리하고, TTL을 원래 값으로 올린다. RFC 1034가 말한 “변경 후에는 이전 값으로 되돌린다”다.

certdesk DNS 전파 확인

위 비교를 한 화면에서 하는 도구가 certdesk DNS 전파 확인이다. 소스에 있는 동작만 적는다.

권한 네임서버(최대 4곳), 통신 3사 DNS(KT 168.126.63.1, SK브로드밴드 210.220.163.82, LG U+ 164.124.101.2), 공용 DNS(Cloudflare 1.1.1.1, Google 8.8.8.8, Quad9 9.9.9.9, OpenDNS 208.67.222.222)를 한국 망의 측정 지점에서 동시에 묻는다. 지원하는 레코드 종류는 TXT, CNAME, A, AAAA, MX, NS, CAA, SOA다.

권한 서버는 이름에서 위로 올라가며 NS가 있는 첫 지점을 존으로 잡아 찾는다(이 조회는 1.1.1.1에 한다). 그 NS마다 첫 IPv4 주소에 재귀 없이(RD=0) 묻는다.

시간 제한은 UDP 1.5초(EDNS0)다. 시간 초과면 EDNS 없이 1초로 한 번 더 묻고, 잘린 응답은 TCP로 다시 묻는다. Quad9는 DNS over TLS(853번 포트, dns.quad9.net)로 묻고 연결 제한은 2.5초다.

행마다 ‘권한 서버와 일치’, ‘권한 서버와 다름’, ‘없음 (NXDOMAIN)’, ‘레코드 없음’, 응답 코드(SERVFAIL 등), ‘측정 실패 (시간 초과)’ 중 하나가 나온다. 일치 여부는 TTL을 빼고 값의 집합만 비교한다. TTL 칸은 그 리졸버에 남은 시간, ms 칸은 응답 시간이다.

같은 이름과 타입은 30초 동안 같은 결과를 돌려준다. 몇 초 만에 다시 눌러도 바뀌지 않는다. 측정 지점이 응답하지 않으면 해외 엣지에서 Cloudflare와 Google의 DoH 두 곳만 조회하고, 결과 아래에 그 사실을 적는다. 이때는 통신사와 권한 서버 비교가 없다.

권한 서버 행은 한국에서 닿는 인스턴스 하나의 답이라 CA가 닿는 인스턴스와 다를 수 있다. 지역별로 다른 답을 주는 CDN 이름은 ’권한 서버와 다름’이 나와도 정상일 수 있다. 존 찾기를 1.1.1.1로 하므로 DNSSEC이 깨진 존은 이 단계가 SERVFAIL에 막혀 권한 서버 행이 비어 나온다. 위 dnssec-failed.org 측정이 그랬다. 이때는 dig @<권한 서버> ... +norecurse로 직접 본다.

인증서 작업과 엮일 때

DNS-01

Let’s Encrypt DNS-01은 _acme-challenge.<도메인>에 TXT를 넣으면 Let’s Encrypt가 DNS에서 그 레코드를 조회한다. CA는 내 리졸버로 보지 않는다. 공식 문서가 짚는 함정은 이렇다.

  • 사업자 권한 서버 전부에 반영되기까지 시간이 걸린다. anycast 때문에 내가 닿는 서버와 Let’s Encrypt가 닿는 서버가 달라 다른 답을 받을 수 있다.
  • 사업자 API가 반영 완료를 알려 주지 않으면 클라이언트가 충분히 기다리게 설정해야 한다. 문서는 그 시간을 “often as much as an hour”라고 적는다.
  • 같은 이름에 TXT가 여러 개 있어도 되지만, 오래된 TXT를 지우지 않아 응답이 너무 커지면 거부된다.
  • CNAME이나 NS로 _acme-challenge를 다른 존에 위임할 수 있다. 사업자 반영이 느리면 빠른 쪽으로 위임하는 데도 쓴다.

그래서 확인은 내 PC의 리졸버 대신 권한 서버 전부에 물어서 하고(앞의 +nssearch와 서버별 조회), 검증 요청은 그다음에 보낸다. API 자격증명 만료처럼 다른 원인으로 DNS-01이 실패하는 경우는 Let’s Encrypt 자동갱신이 실패하는 흔한 이유에 있다.

HTTP-01

HTTP-01은 Let’s Encrypt가 http://<도메인>/.well-known/acme-challenge/<토큰>을 가져가 확인한다. 문서에 따르면 여러 관측 지점에서 여러 번 가져갈 수 있고, 웹서버가 여럿이면 모든 서버에 그 파일이 있어야 한다.

A 레코드를 바꾸는 중에는 캐시 상태에 따라 누구는 옛 IP, 누구는 새 IP로 가고 CA도 예외가 아니다. 옛 서버와 새 서버가 문서가 말한 ’여러 웹서버’가 되는 셈이다.

그러니 발급과 갱신은 대기 시간이 끝난 뒤에 한다. 기다릴 수 없으면 DNS-01로 발급한다. 문서는 DNS-01이 웹서버가 여럿이어도, 웹서버가 공개 인터넷에 없어도 쓸 수 있다고 적는다. AAAA 레코드를 쓴다면 같이 바꿨는지도 본다. A만 바꾸면 IPv6로 들어오는 요청은 옛 서버로 간다.

새 서버의 인증서는 DNS보다 먼저

DNS를 먼저 바꾸고 새 서버에서 인증서를 받으려 하면, 그 사이 새 IP를 받은 사용자는 인증서가 없거나 이름이 맞지 않는 서버에 접속한다. 순서를 뒤집는다. 새 서버에 인증서를 먼저 설치하는데, DNS-01로 발급하거나 기존 인증서와 키를 옮긴다. DNS를 바꾸기 전에 새 서버를 이름으로 시험한다. --resolve는 DNS를 거치지 않고 지정한 IP로 그 이름을 써서 붙는다.

curl -sSv -o /dev/null --resolve www.example.com:443:203.0.113.10 https://www.example.com/

그다음 DNS를 바꾸고, 최악 대기 시간 동안 옛 서버도 유효한 인증서로 계속 서비스한다.

이름 불일치 에러가 나면 NET::ERR_CERT_COMMON_NAME_INVALID 원인별 해결로 원인을 가린다. DNS 사업자를 옮길 때는 CAA 레코드도 같은 존 데이터라 빠뜨리지 않고 옮긴다. CAA가 발급에 주는 영향은 CAA 레코드 가이드에 있다.

참조