수정 약 27분

CAA 레코드 설정하고 확인하는 법

이 글의 순서 13개 절
  1. 누가 언제 확인하나
  2. 레코드 형식
  3. 어느 레코드가 적용되나
  4. 와일드카드와 issuewild
  5. CA별 값
  6. 설정 예
  7. accounturi와 validationmethods
  8. 조회가 실패하면
  9. 에러 메시지 읽기
  10. 확인하기
  11. 점검 순서
  12. 연표
  13. 참조

잘 돌던 자동 갱신이 어느 날 이런 에러로 멈춘다.

While processing CAA for www.example.com: CAA record for example.com prevents issuance

서버도 갱신 스크립트도 그대로다. 바뀐 건 DNS다. 누군가 상위 도메인에 CAA 레코드를 넣었거나, DNS 업체나 CDN을 옮기면서 다른 CA만 허용하는 레코드가 생겼다. CAA는 인증서를 발급하는 순간에만 확인된다. 레코드를 넣은 날에는 아무 일도 없다가 다음 갱신에서 터진다.

CAA(Certification Authority Authorization)는 어느 CA가 이 도메인에 인증서를 내줄 수 있는지 DNS에 적어 두는 레코드다. 레코드가 없으면 모든 공인 CA가 발급할 수 있고, 있으면 issue(와일드카드는 issuewild)에 적힌 CA만 발급한다. 공인 CA는 2017-09-08부터 발급 전에 이 레코드를 확인해야 한다.

누가 언제 확인하나

CA는 인증서에 넣을 dNSName마다 CAA를 조회하고(BR 4.2.2.1), 레코드가 허용하지 않으면 발급하지 않는다. 의무는 CA/B Forum BR을 따르는 공인 CA에 걸린다. 사설 CA는 대상이 아니다.

브라우저는 CAA를 보지 않는다. RFC 8659는 인증서를 검증하는 쪽이 CAA를 쓰면 안 된다(MUST NOT)고 적는다. CAA를 바꿔도 지금 쓰는 인증서는 그대로 동작한다.

허용한 CA의 잘못된 발급은 막지 못한다(RFC 8659 §5.3). 이 틈을 줄이는 게 뒤에 나오는 accounturi다.

BR은 CAA를 확인한 뒤 레코드 TTL과 8시간 중 긴 시간 안에 발급하도록 정한다. Let’s Encrypt는 도메인 인증(authorization)을 받은 지 7시간이 넘었으면 주문을 마무리(finalize)할 때 CAA를 다시 확인한다. 그래서 검증은 통과했는데 마지막 단계에서 CAA로 실패할 수 있다. 2025-03-15부터는 여러 관측 지점(MPIC)에서 확인하므로 특정 국가에서만 응답하는 DNS도 여기서 걸린다.

레코드 형식

CAA <flags> <tag> <value>
example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
example.com.  3600  IN  CAA  0 issuewild "letsencrypt.org"
example.com.  3600  IN  CAA  0 iodef "mailto:security@example.com"
태그 대상 비고
issue 모든 TLS 인증서 (와일드카드 포함) 기본 태그
issuewild 와일드카드 (*.example.com) 있으면 와일드카드에서 issue보다 우선
iodef 정책 위반 발급 요청의 보고처 mailto:·https: URL. 발급을 제한하지 않는다
issuemail S/MIME(이메일) 인증서 RFC 9495(2023-10). issue와 서로 영향을 주지 않는다

값은 "letsencrypt.org; validationmethods=dns-01"처럼 CA 식별 도메인 하나와 ; 뒤의 파라미터로 이뤄진다. 같은 태그를 여러 줄 쓰면 더해진다. 한 줄이라도 허용하면 그 CA는 발급할 수 있다. 문법에 맞지 않는 issue 값은 빈 값, 즉 아무 CA도 허용하지 않는 줄로 취급된다(RFC 8659 §4.2). 그 줄 하나뿐이면 발급 금지가 된다.

플래그는 0 아니면 128이다. 128은 critical 비트로, CA가 모르는 태그에 붙어 있으면 그 CA는 발급하지 않는다. issue처럼 모든 CA가 아는 태그에 붙이면 효과가 없다. 1을 critical로 잘못 아는 경우가 흔해서 Let’s Encrypt는 1도 critical로 취급한다(boulder caa.go). 그래서 모르는 태그에 1을 붙이면 Let’s Encrypt 발급이 막힌다. 이유가 없으면 0만 쓴다.

어느 레코드가 적용되나

RFC 8659 §3의 규칙은 짧다. 인증서의 이름에서 CAA를 조회하고, 없으면 왼쪽 레이블을 하나 떼고 다시 조회한다. 처음 나온 CAA 집합 하나가 전부다. TLD까지 올라간다.

www.shop.example.com  → 없음
shop.example.com      → 없음
example.com           → 0 issue "letsencrypt.org"   ← 여기서 멈춘다

와일드카드 *.shop.example.com이면 *.를 뗀 shop.example.com부터 시작한다.

상위 집합과 합쳐지지 않는다. shop.example.com에 CAA가 하나라도 있으면 example.com은 보지 않는다. 하위 도메인만 다른 CA를 쓰게 할 때 이 성질을 쓴다. Let’s Encrypt 문서의 예처럼 example.org는 Let’s Encrypt만 허용하고, 클라우드에 둔 api.example.org에는 따로 레코드를 넣어 그 클라우드의 CA를 허용한다. 반대로 하위에 레코드를 만들면서 필요한 CA를 빠뜨리면 그 이름만 막힌다.

제한하지 않는 집합도 탐색을 멈춘다. iodef만, 또는 issuemail만 있는 집합은 탐색을 끝내지만 아무 CA도 막지 않는다(RFC 8659 §3, Ballot 219). 상위에 issue "letsencrypt.org"를 걸고 하위에 보고용 iodef 한 줄만 넣으면 그 하위 이름은 제한이 풀린다. 하위에 넣을 때는 issue도 함께 넣는다.

TLD도 탐색 대상이다. 2026-10-04에 dig CAA et +short를 실행하면 .et TLD 자체에서 CAA 레코드가 20줄 넘게 나온다. 자기 CAA가 없는 .et 도메인은 이 집합을 따른다.

CNAME이 있으면

각 단계의 조회는 일반 DNS 조회처럼 CNAME을 따라간다. 이름이 CNAME이면 그 단계의 결과는 CNAME 대상에 있는 CAA다. 대상에 없으면 원래 이름의 상위로 올라간다. 대상의 상위는 보지 않는다.

RFC 6844와 8659가 가장 크게 다른 곳이 여기다. 6844의 원래 알고리즘은 CNAME 대상의 상위까지 올라가서, 호스팅 업체가 자기 도메인에 건 CAA가 고객 도메인까지 제한했다. 8659가 이를 바꿨고(§7), BR은 그보다 앞서 Ballot 214로 6844 정오표를 받아들였다.

아래 caa-climb.sh 스크립트로 2026-10-04에 본 실제 예다.

== community.letsencrypt.org
community.letsencrypt.org. 300	IN	CNAME	letsencrypt.hosted-by-discourse.com.
letsencrypt.hosted-by-discourse.com. 300 IN CAA	0 issue "letsencrypt.org"

CNAME 대상의 CAA가 적용된다. 상위인 letsencrypt.org의 CAA는 보지 않는다.

== www.mozilla.org
www.mozilla.org.	40	IN	CNAME	www-mozilla.fastly-edge.com.
== mozilla.org
mozilla.org.		3600	IN	CAA	0 issue "letsencrypt.org"
mozilla.org.		3600	IN	CAA	0 issue "digicert.com"
(이하 생략)

CNAME 대상에 CAA가 없으니 원래 이름의 상위인 mozilla.org로 올라간다. 대상의 상위인 fastly-edge.com에는 0 issue "letsencrypt.org;validationmethods=dns-01" 같은 레코드가 있지만 적용되지 않는다. RFC 6844의 원래 알고리즘이었다면 이 집합이 적용됐을 것이다.

CDN이나 SaaS를 CNAME으로 붙인 이름은 CNAME 대상 쪽 CAA를 따른다. 내 도메인에 CAA가 없어도 적용된다. Cloudflare 문서도 같은 설명이다. CNAME이 있는 이름에는 다른 레코드를 함께 둘 수 없으니, 그 이름에 CAA를 넣어 덮어쓸 수도 없다.

와일드카드와 issuewild

RFC 8659 §4.3에 따르면 와일드카드가 아닌 이름에는 issuewild를 무시한다. 와일드카드 이름이면 집합에 issuewild가 하나라도 있을 때 issue를 무시한다.

CAA 집합 example.com·www.example.com *.example.com
issue "A" A만 A만
issue "A" + issuewild "B" A만 B만
issuewild "B"만 제한 없음 B만
issue "A" + issuewild ";" A만 아무도 못 함

함정은 세 번째 줄이다. 와일드카드만 묶으려고 issuewild 한 줄만 넣으면 일반 인증서는 어느 CA든 받을 수 있다. RFC 8659에 나오는 예 그대로다.

apex도 걸린다. 와일드카드 인증서를 받을 때는 -d example.com -d '*.example.com'처럼 apex를 함께 넣는 경우가 많다. 이때 example.com은 issue로 판정된다. CAA가 issue "digicert.com", issuewild "letsencrypt.org" 두 줄이라면 *.example.com은 통과하고 example.com에서 막혀 인증서 전체가 발급되지 않는다. 이런 인증서를 Let’s Encrypt로 받으려면 issue와 issuewild 양쪽에 Let’s Encrypt를 넣는다. 와일드카드와 SAN 중 무엇을 쓸지는 와일드카드 vs SAN 인증서에 있다.

CA별 값

issue에 적는 값은 CA가 정한다. BR은 CA가 그 목록을 CP/CPS 4.2절에 적도록 요구한다. 아래는 2026-10-04에 각 CA 문서에서 확인한 값이다.

CA CAA 값 비고
Let’s Encrypt letsencrypt.org CP/CPS 4.2.1에 명시
Google Trust Services pki.goog
DigiCert (GeoTrust·Thawte·RapidSSL 포함) digicert.com www.digicert.com·thawte.com·geotrust.com·rapidssl.com·symantec.com 등도 같은 뜻
Sectigo sectigo.com trust-provider.com·usertrust.com도 인정
GlobalSign globalsign.com
Amazon (ACM) amazon.com amazontrust.com·awstrust.com·amazonaws.com 중 하나면 된다
ZeroSSL sectigo.com ZeroSSL 문서가 이 값을 안내한다

한 값을 여러 CA가 인정하기도 한다. DigiCert 문서의 유효 값 목록에는 Amazon 값 네 개도 들어 있고, 모두 같은 뜻이라고 적혀 있다. Amazon 값을 허용하면 DigiCert도 발급할 수 있다. ZeroSSL 문서는 CAA에 sectigo.com이 없으면 발급할 수 없다고 적는다.

옛 이름이 남은 레코드도 있다. cloudflare.com과 mozilla.org의 CAA에 보이는 comodoca.com은 Sectigo 지식베이스(2026-10-01 갱신)의 인정 목록에 없다. 새로 적을 때는 문서에 있는 현재 값을 쓴다.

2026-10-04에 조회한 실제 레코드다.

$ dig CAA google.com +short
0 issue "pki.goog"

$ dig CAA github.com +short
0 issue "letsencrypt.org"
0 issuewild "sectigo.com"
0 issue "digicert.com"
0 issue "sectigo.com"
0 issuewild "digicert.com"
0 issue "globalsign.com"
0 issuewild "letsencrypt.org"

github.com은 일반 인증서에 네 곳, 와일드카드에 세 곳을 허용한다. issuewild가 있으므로 GlobalSign은 와일드카드를 발급할 수 없다. 출력 순서는 의미가 없다. 무료 발급처의 차이는 무료 CA 개요에 있다.

설정 예

존 파일 형식으로 적는다. DNS 업체 화면은 보통 플래그, 태그, 값을 칸마다 따로 받는다.

Let’s Encrypt만 허용

example.com.  3600  IN  CAA  0 issue "letsencrypt.org"

issuewild가 없으므로 와일드카드도 Let’s Encrypt만 받는다. 하위 도메인 전체에 적용된다.

일반은 다른 CA, 와일드카드만 Let’s Encrypt

example.com.  3600  IN  CAA  0 issue "digicert.com"
example.com.  3600  IN  CAA  0 issuewild "letsencrypt.org"

*.example.com은 Let’s Encrypt만, example.com과 www.example.com은 DigiCert만 받는다. Let’s Encrypt 와일드카드에 apex를 같이 넣는다면 0 issue "letsencrypt.org"도 추가한다.

발급 전면 금지

example.com.      3600  IN  CAA  0 issue ";"
app.example.com.  3600  IN  CAA  0 issue "letsencrypt.org"

첫 줄만 있으면 그 도메인과 모든 하위 도메인, 와일드카드까지 막힌다. 웹에 쓰지 않는 도메인(메일 전용, 보유만 하는 도메인)에 넣는다. 두 번째 줄처럼 인증서가 필요한 하위 이름에 자기 집합을 두면 탐색이 거기서 멈춰 그 이름만 열린다. ZeroSSL 안내 문서도 같은 구조의 예를 든다.

iodef로 보고받기

example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
example.com.  3600  IN  CAA  0 iodef "mailto:security@example.com"

기대는 낮춰 둔다. BR은 CA가 iodef 태그를 처리하도록 요구하지만 그 내용대로 행동할 의무는 없고, 막힌 요청을 보고하는 건 권고(SHOULD)다. Let’s Encrypt는 iodef를 인식만 하고 알림을 보내지 않는다(boulder caa.go). AWS ACM 문서도 iodef를 현재 무시한다고 적는다. 실제로 발급된 인증서를 알고 싶다면 CT 로그를 감시하는 편이 낫다. iodef는 issue와 같은 이름에 둔다.

CDN과 오리진이 다른 CA를 쓸 때

CDN 엣지와 오리진 서버가 같은 이름에 서로 다른 CA 인증서를 쓰면 두 CA를 모두 허용한다.

; 오리진은 certbot(Let's Encrypt), 엣지는 AWS CloudFront(ACM)
example.com.  3600  IN  CAA  0 issue "letsencrypt.org"
example.com.  3600  IN  CAA  0 issue "amazon.com"

DNS까지 Cloudflare에 둔 경우, Universal SSL을 쓰는 존에 CAA를 하나라도 넣으면 Cloudflare가 자기 CA용 CAA를 자동으로 추가한다. 대시보드에는 안 보이고 dig로만 보인다. Advanced Certificate Manager 인증서에는 적용되지 않는다.

서브도메인만 Cloudflare에 둔 경우에는 상위 도메인의 CAA를 상속한다. 상위에 CAA가 있다면 Cloudflare 문서의 값 letsencrypt.org, pki.goog; cansignhttpexchanges=yes, ssl.com, sectigo.com을 직접 허용한다.

AWS ACM은 Amazon 값이 없으면 발급하지 않는다. issuewild를 쓰는 존이라면 거기에도 Amazon 값이 있어야 와일드카드가 나온다.

클라우드가 발급하고 갱신하는 인증서 전반은 클라우드 관리형 인증서 가이드에 있다.

accounturi와 validationmethods

RFC 8657(2019-11)이 정한 두 파라미터다.

파라미터 뜻 RFC 8657의 처리 규칙
accounturi 그 CA의 특정 계정만 허용 URI가 요청 계정과 맞아야 그 줄이 허용으로 인정된다. 잘못되거나 CA가 모르는 URI, 두 번 적힌 경우는 아무도 만족할 수 없는 줄이 된다
validationmethods 허용할 도메인 검증 방식 나열한 방식으로 검증할 때만 허용. ACME 방식 이름(dns-01 등)이나 CA 전용 이름을 쓴다

Let’s Encrypt라면 이렇게 계정과 검증 방식까지 묶는다.

example.com.  3600  IN  CAA  0 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890; validationmethods=dns-01"

계정 1234567890이 DNS-01로 검증할 때만 허용한다. 한 줄에 적은 파라미터는 모두 만족해야 하고, 줄을 나누면 그중 하나만 맞아도 된다(레코드는 더해진다). Let’s Encrypt 문서의 validationmethods 값은 http-01, dns-01, tls-alpn-01이다. 계정 URL은 Certbot 1.23.0 이상에서 이렇게 본다.

sudo certbot show_account
Account details for server https://acme-v02.api.letsencrypt.org/directory:
  Account URL: https://acme-v02.api.letsencrypt.org/acme/acct/1234567890

계정을 새로 만들거나 서버를 옮기면 새 계정은 막힌다. 이 운영상 주의는 인증서 유효기간 47일 시대에 적었다.

2027-03-15부터는 CA 의무

지금까지 이 파라미터는 CA가 지원할 때만 효과가 있었다. issue의 파라미터는 CA가 의미를 정하는 영역이라 지원하지 않는 CA는 무시해도 됐다. 그래서 RFC 8657은 CA가 지원을 밝히지 않았다면 도메인 쪽이 이 제한이 먹힌다고 가정하면 안 된다(MUST NOT)고 적는다.

CA/B Forum Ballot SC098v2 “Process RFC 8657 CAA Parameters”가 통과하면서 이게 바뀐다. 인증서 발급자 23곳 중 22곳이 찬성하고 1곳(HARICA)이 반대했다. 인증서 소비자 3곳(Apple, Google, Mozilla)은 모두 찬성했다. IPR 검토(2026-05-13 ~ 2026-06-12)를 거쳐 BR 2.2.8에 반영됐고, 2026-06-16에 발효됐다. 현행 BR(2.3.1) 4.2.2.1.2는 지금은 처리를 권고(SHOULD)하고, 2027-03-15부터 의무(MUST)로 한다.

같은 날부터 적용되는 세부 규칙도 있다.

  • ACME를 쓰지 않는 CA는 자기 accounturi 형식을 CP/CPS 4.2절에 정의해야 한다
  • IANA ACME 검증 방식 목록에 없는 방식은 ca-tbr- 뒤에 BR 3.2.2.4의 하위 절 번호를 붙인 이름으로 처리한다. ca-tbr-7은 BR 3.2.2.4.7(DNS 변경 방식)이다
  • ACME CA는 accounturi가 조직의 상위 계정을 가리킬 때, 그 계정이 승인한 하위 ACME 계정의 발급을 허용할 수 있다. 대응 관계와 승인 기록을 감사 가능하게 남기는 조건이다

그러니 지금 accounturi를 쓴다면 지원을 문서로 밝힌 CA에만 기대한다. Let’s Encrypt는 CAA 문서에 계정 URI 형식과 검증 방식 이름을 적어 두었다. 2027-03-15 이후에는 BR을 따르는 모든 공인 CA가 처리해야 한다. 다만 파라미터 없이 함께 허용한 다른 CA에는 계정 제한이 걸리지 않는다. 또 RFC 8657은 DNSSEC로 서명하지 않은 도메인이라면 CA의 검증 경로 전체를 가로채는 공격자에게는 이 파라미터가 추가 방어가 되지 않는다고 적는다. 관리 수단으로는 여전히 유효하다.

조회가 실패하면

CAA 조회가 실패하면 CA는 레코드가 없는 건지 숨겨진 건지 알 수 없다. BR 4.2.2.1은 다음 세 조건을 모두 갖췄을 때 실패를 발급 허용으로 볼 수 있게 한다.

  1. 실패가 CA 인프라 바깥에서 났다
  2. 한 번 이상 다시 조회했다
  3. 그 도메인이 DNSSEC 기준 “Insecure”(RFC 4035 §4.3, 서명되지 않았음이 증명된 상태)임을 확인했다

여기에 Ballot SC-085v2가 더해졌다. 2026-03-15부터 CA의 주 관측 지점은 CAA 조회에 DNSSEC 검증을 해야 하고, 내부 정책으로 끌 수 없다. DNSSEC 검증 오류(SERVFAIL 등)는 발급 허용으로 볼 수 없다. 이 규정들은 SC100(2026-09-07, BR 2.3.0)에서 BR 4.2.2.2로 모였다.

서명한 존에서 CAA 조회가 깨지면 발급은 멈춘다. 서명하지 않은 존은 BR상 조건부 허용이 가능하지만 CA가 더 엄격해도 된다.

CA 조회 실패 시
Let’s Encrypt 오류가 나면 발급해도 되는지 알 수 없다고 보고 실패 처리
GlobalSign 서명된 존의 시간 초과·SERVFAIL·REFUSED는 차단. 서명 안 된 존은 시간 초과·SERVFAIL이어도 발급 허용

조회가 깨지는 흔한 원인은 이렇다.

  • DNSSEC 서명이 잘못됐다. Let’s Encrypt 문서가 꼽는 SERVFAIL의 가장 흔한 원인이다. CAA 응답은 대개 빈 응답인데, 빈 응답의 서명만 잘못 만드는 네임서버가 있었다(PowerDNS 4.0.3 이하)
  • 네임서버가 모르는 타입에 NOTIMP나 REFUSED로 답한다. NOERROR와 빈 응답이 맞다. CAA를 따로 “지원”할 필요는 없다
  • 방화벽이나 DDoS 장비가 모르는 타입을 버린다. 시간 초과나 SERVFAIL로 나타난다
  • 하위 도메인을 사설 IP 네임서버로 위임했다. CA는 SERVFAIL을 받고, RFC 8659는 이를 발급 금지로 볼 수 있다고 한다. 공개 네임서버를 둔 split-horizon DNS를 권한다

에러 메시지 읽기

Let’s Encrypt(boulder)의 문제 유형은 CAA 차단이면 urn:ietf:params:acme:error:caa, DNS 오류면 urn:ietf:params:acme:error:dns다.

While processing CAA for www.example.com: CAA record for example.com prevents issuance

앞의 이름은 인증서에 넣은 이름이고, 뒤의 이름은 막는 CAA 집합을 찾은 탐색 단계다. 둘이 다르면 상위 도메인의 CAA가 원인이다. 위 예라면 example.com의 레코드를 고친다. 뒤의 이름이 CNAME이면 실제 레코드는 CNAME 대상에 있다.

Error finalizing order :: rechecking caa: During secondary validation: While processing CAA for www.example.com: CAA record for example.com prevents issuance

2025-10 Let’s Encrypt 커뮤니티에 올라온 메시지다(도메인만 바꿨다). rechecking caa는 마무리 단계의 재확인에서 막혔다는 뜻이고, During secondary validation은 주 관측 지점은 통과했지만 다른 지점에서 막혔다는 뜻이다. 그 사용자는 같은 날 CAA를 막 바꾼 상태였다. 변경이 아직 모든 곳에 퍼지지 않은 경우다.

DNS 오류는 이렇게 나온다(boulder bdns/problem.go). 실제로는 앞에 While processing CAA for ...:가 붙는다.

DNS problem: SERVFAIL looking up CAA for example.com - the domain's nameservers may be malfunctioning
DNS problem: query timed out looking up CAA for example.com
DNS problem: looking up CAA for example.com: DNSSEC: Bogus

마지막 형식은 리졸버가 확장 DNS 오류(EDE)를 돌려줄 때다. DNSSEC:로 시작하면 서명 문제다.

CA 메시지 뜻
AWS ACM One or more domain names have failed validation due to a Certification Authority Authorization (CAA) error CAA 차단. 검증이 끝난 뒤 났다면 CAA를 고치고 인증서를 다시 요청한다
GlobalSign "example.com.": "issue"="another-ca.com" 형태 앞의 이름(상위일 수 있음)에 GlobalSign이 없는 CAA가 있다
GlobalSign The quorum requirements were not met 여러 관측 지점의 결과가 주 지점과 맞지 않았다

GlobalSign은 CAA 조회 결과를 최대 1시간 캐시한다고 적는다. 고친 직후의 재시도가 바로 통하지 않을 수 있다. 다른 갱신 실패 원인은 Let’s Encrypt 자동갱신이 실패하는 흔한 이유에 있다.

확인하기

한 이름만

dig CAA example.com +short

빈 출력은 그 이름에 CAA가 없다는 뜻일 뿐이고, 적용되는 집합은 상위에 있을 수 있다. 조회가 실패해도 +short는 빈 출력이니, 응답 코드는 +short를 빼고 status:로 본다.

상위로 올라가며

CA의 탐색을 흉내 내는 스크립트다. 처음 CAA가 나오는 곳에서 멈춘다.

#!/bin/bash
# 사용법: ./caa-climb.sh www.example.com
name=${1%.}
while :; do
  echo "== $name"
  ans=$(dig +nocmd +noall +answer CAA "$name")
  [ -n "$ans" ] && echo "$ans"
  echo "$ans" | grep -qE 'IN[[:space:]]+CAA' && break   # 처음 나온 CAA 집합에서 멈춘다
  [ "$name" = "${name#*.}" ] && { echo "(끝까지 없음 — CAA 제한 없음)"; break; }
  name=${name#*.}
done
$ ./caa-climb.sh mail.google.com
== mail.google.com
== google.com
google.com.		86400	IN	CAA	0 issue "pki.goog"

CNAME이 있으면 대상 쪽 결과가 같이 찍힌다(앞의 community.letsencrypt.org, www.mozilla.org 예). 이 스크립트는 조회 실패(SERVFAIL, 시간 초과)도 “없음”으로 보고 넘어간다. CA는 그렇게 보지 않는다. 결과가 이상하면 아래 두 방법으로 응답 코드를 본다.

권한 네임서버의 답

리졸버 캐시를 거치지 않고 루트부터 따라가 권한 네임서버의 답을 본다. 레코드를 바꾼 직후 존에 들어갔는지 확인할 때 쓴다.

dig +trace CAA google.com
google.com.		86400	IN	CAA	0 issue "pki.goog"
;; Received 66 bytes from 216.239.32.10#53(ns1.google.com) in 95 ms

+trace는 CNAME을 따라가지 않는다. www.mozilla.org에 실행하면 CNAME www-mozilla.fastly-edge.com.에서 끝난다. 대상 이름으로 다시 조회한다.

CAA를 바꾼 직후에는 리졸버마다 옛 값이 TTL 동안 남는다. CA는 여러 지점에서 확인하므로 이 사이에 발급하면 During secondary validation 실패가 날 수 있다. 전파 확인은 DNS 전파 확인에 따로 적었다.

DNSSEC

dig @1.1.1.1 +dnssec CAA example.com | grep -E 'status:|flags:'

검증하는 리졸버의 답에 ad 플래그가 있으면 서명이 검증된 응답이다. 2026-10-04에 cloudflare.com은 flags: qr rd ra ad, DS 레코드가 없는(서명하지 않은) google.com은 flags: qr rd ra로 나왔다. SERVFAIL이면 검증을 끈(+cd) 조회와 비교한다. DNSSEC 검증이 실패하는 dnssec-failed.org로 보면 이렇다.

$ dig @1.1.1.1 CAA dnssec-failed.org | grep status
;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 7948
$ dig @1.1.1.1 CAA dnssec-failed.org +cd | grep status
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12318

+cd에서만 NOERROR면 서명 문제다. 2026-03-15 이후로 CA는 이 상태에서 발급하지 않는다. 서명 체인은 Let’s Encrypt 문서가 권하는 dnsviz.net으로 본다.

certdesk로 보기

DNS 전파 확인 도구의 CAA 빠른 입력 버튼은 입력란의 _acme-challenge., _dmarc., www.를 떼고 종류를 CAA로 바꾼다. 권한 네임서버와 국내 통신 3사, 공용 DNS 4곳의 응답을 나란히 보여 주고, SERVFAIL 같은 응답 코드와 CNAME도 표시한다. 입력한 이름만 조회하고 상위로 올라가지는 않으니 탐색 단계마다 이름을 바꿔 조회한다.

진단 툴의 DNS 항목은 상위로 올라가며 찾은 CAA의 issue 값과, 그 값을 어느 이름에서 상속했는지 보여 준다. 지금 인증서의 발급자가 목록에 있는지도 표시하는데, 발급자 이름으로 추정한 값이고 issuewild는 보지 않는다. TLD 단계는 조회하지 않으니 .et 같은 예외는 dig로 본다.

API와 MCP에서는 check_dns가 같은 탐색 결과(foundAt, issue, issuewild, 원본 레코드)를 돌려주고, check_dns_propagation은 type에 CAA를 받는다.

점검 순서

1. 에러의 두 이름 ("CAA for A ... record for B") — B 가 막는 집합의 위치
   └ "secondary validation" → 최근 변경·전파·지역 제한
   └ "DNS problem"          → 레코드 내용이 아니라 조회 실패 → 4번
2. B 의 CAA 집합 (caa-climb.sh)
   └ CA 값 없음 → issue 추가 / 와일드카드 → issuewild (apex 포함이면 issue 도)
   └ accounturi·validationmethods → 계정 URL·검증 방식 / 모르는 태그에 128·1 → 0
3. 고친 뒤 dig +trace 로 권한 서버 반영 확인 → TTL 이 지나고 재시도
4. 조회 실패 → @1.1.1.1 과 +cd 비교(DNSSEC), NOTIMP·시간 초과는 DNS 업체에 수정 요청

CAA를 바꿨다면 바로 테스트 발급으로 확인해 둔다. Let’s Encrypt라면 스테이징 환경을 쓴다. 갱신일에 알게 되는 것보다 싸다.

연표

날짜 일
2013-01 RFC 6844, CAA의 첫 표준
2015-04-15 Ballot 125 발효. CA는 CAA 확인 여부를 CP/CPS에 적어야 했다 (확인 의무는 아님)
2017-09-08 Ballot 187 발효. 발급 전 CAA 확인 의무
2017-10-27 Ballot 214 발효. CNAME 처리를 RFC 6844 정오표대로 정정
2019-11 RFC 8659 (RFC 6844 대체), RFC 8657 (accounturi·validationmethods)
2025-03-15 여러 관측 지점(MPIC)에서 CAA 확인 결과를 교차 확인
2026-03-15 CAA 조회에 DNSSEC 검증 의무 (SC-085)
2027-03-15 accounturi·validationmethods 처리 의무 (SC098)

참조