인증서를 갱신했는데 일부 사용자만 오류가 난다면
이 글의 순서 12개 절
금요일 오후에 인증서를 갱신했다. 브라우저로 사이트를 열어 새 만료일까지 확인하고 작업을 닫았다. 월요일 아침에 문의가 들어온다. 일부 사용자에게만 이 화면이 뜬다고 한다.
NET::ERR_CERT_DATE_INVALID
내 PC에서는 멀쩡하다. 문의한 사용자도 새로고침하면 될 때가 있다고 한다. 서버 주소를 하나씩 지정해 붙어 보면 한 주소만 만료된 인증서를 낸다.
이름은 하나인데 그 뒤의 서버는 여럿이다. A/AAAA 레코드가 여럿이거나, 로드밸런서 뒤에 노드가 여럿이거나, CDN이나 GeoDNS가 지역마다 다른 서버를 준다. 갱신하면서 그중 하나를 빠뜨리면 그 노드로 가는 사용자만 실패한다.
글에 나오는 출력은 2026-10-15에 OpenSSL 4.0.3, curl 8.7.1(macOS), dig 9.10.6으로 실행한 결과다.
왜 사용자마다 다른가
어느 주소로 갈지는 클라이언트가 정한다
DNS는 주소를 여럿 줄 수 있지만 순서는 보장하지 않는다(RFC 1794). 리졸버와 OS가 순서를 바꾸고, 클라이언트는 그중 하나에 붙는다. 고르는 기준은 규격에 있다.
IPv6가 먼저다. 주소 선택 규격 RFC 6724의 기본 정책표는 일반 IPv6 주소(::/0)에 우선순위 40,
IPv4(::ffff:0:0/96)에 35를 준다. 이 표를 따르는 클라이언트는 IPv6 경로가 있으면 AAAA 주소부터 시도한다.
연결되면 거기서 끝이다. Happy Eyeballs(RFC 8305)는 정렬한 주소를 권장 250ms 간격으로 하나씩 시도하다가 하나가 성공하면(대개 TCP 핸드셰이크 완료) 나머지를 취소한다. 인증서 검증은 연결된 뒤의 일이다. 이 방식대로라면 인증서 오류가 났다고 다른 주소로 다시 붙지 않는다. 아래 재현에서 curl도 그랬다.
매번 같은 주소로 가는 것도 아니다. RFC 8305는 이 방식이 같은 이름에 다른 주소로 붙을 가능성을 높인다고 적는다. “새로고침하면 될 때도 있다”는 말이 따라오는 이유다. 같은 RFC는 Happy Eyeballs가 한쪽 주소 체계의 장애를 가릴 수 있으니 주소 체계마다 바깥에서 감시하라고 권한다.
한 번 붙으면 그 노드에 머문다. HTTP/2 연결은 계속 유지되고, 클라이언트는 같은 호스트와 포트에 연결을 둘 이상 열지 않는 것이 원칙이다(RFC 9113 §9.1). 정상 노드에 붙은 브라우저는 그 연결이 살아 있는 동안 계속 정상이다. 갱신 직후 브라우저로 확인한 운영자가 다른 노드의 문제를 못 보는 이유다.
구조마다 인증서가 있는 곳이 다르다
| 구조 | 인증서가 있는 곳 | 노드 차이가 보이는 모습 |
|---|---|---|
| DNS 라운드로빈 (서버마다 A/AAAA) | 서버마다 | 주소마다 다르다 |
| L4 로드밸런서 (TCP를 그대로 넘김) | 백엔드마다 | 주소는 하나, 연결마다 다르다 |
| L7 로드밸런서·리버스 프록시 (TLS 종료) | 로드밸런서 | 로드밸런서가 여러 대면 그 노드마다 |
| CDN·GeoDNS | 엣지 | 지역·리졸버마다 다르다 |
L7에서 TLS를 끝내면 사용자는 백엔드 인증서를 보지 않는다. 로드밸런서가 백엔드 인증서를 검증하지 않는 경우도 있다. AWS 문서에도 Application Load Balancer는 대상의 인증서를 검증하지 않으니 자체 서명 인증서나 만료된 인증서를 써도 된다고 나온다. 그래서 백엔드 인증서가 만료돼도 아무 경고가 없다가, 우회하려고 백엔드를 직접 노출하는 순간 그대로 드러난다.
노드 둘로 재현
로컬에 같은 이름(www.example.com)으로 노드 둘을 띄웠다. IPv4(127.0.0.1)에는 새 인증서를,
IPv6(::1)에는 2026-10-14에 만료된 옛 인증서를 올렸다. 인증서는 테스트 CA로 발급했고 포트는 8443을 썼다.
두 주소에서 받은 서버 인증서는 이렇다. 받는 방법은 아래 nodes.sh와 같다.
127.0.0.1 serial=0A5D598A1257C88474C57216B00D2B3B notAfter=Jan 12 00:00:00 2027 GMT sha256=08:46:1F:D2:...
::1 serial=5B2F19A70701AE4F71B8961091C776CE notAfter=Oct 14 00:00:00 2026 GMT sha256=E7:03:60:64:...
curl에 두 주소를 다 주면(--resolve는 이름을 지정한 주소로 묶는다) IPv6를 먼저 고르고, 인증서 오류에서
그대로 멈춘다. IPv4로 넘어가지 않는다.
$ curl -sS -o /dev/null -w '%{remote_ip} %{http_code}\n' --cacert ca.pem \
--resolve 'www.example.com:8443:[::1],127.0.0.1' https://www.example.com:8443/
curl: (60) SSL certificate problem: certificate has expired
...
::1 000
$ curl -4 -sS -o /dev/null -w '%{remote_ip} %{http_code}\n' --cacert ca.pem \
--resolve 'www.example.com:8443:[::1],127.0.0.1' https://www.example.com:8443/
127.0.0.1 200
이름은 같은데 주소 체계 하나로 결과가 갈린다. IPv4만 되는 사무실에서 확인하면 정상이고, IPv6가 되는 사용자는 실패한다. 이 구성이 바깥에 있으면 맨 앞의 문의가 들어온다.
노드 찾기
dig +short A www.example.com
dig +short AAAA www.example.com
+short는 CNAME도 섞어 낸다. 스크립트에서는 주소 줄만 거른다.
$ dig +short A www.naver.com
www.naver.com.nheos.com.
223.130.192.248
223.130.200.236
223.130.192.247
223.130.200.219
이 목록이 전부가 아닐 수 있다. CDN과 GeoDNS는 묻는 리졸버의 위치에 따라 다른 주소를 주므로 리졸버마다 답이 다르다. 리졸버가 사용자 쪽 네트워크 정보를 권한 서버에 넘기는 ECS(RFC 7871)도 이런 응답에 쓰인다. L4 로드밸런서 뒤의 백엔드는 아예 DNS에 없다. 주소 하나 뒤에 여럿이 있으니 아래 반복 접속으로 본다.
실제 노드 목록은 콘솔에 있다. 로드밸런서의 대상 목록, CDN의 인증서 설정, 오토스케일링 그룹의 인스턴스 목록을 기준으로 삼는다.
같은 순간 국내 통신 3사와 공용 DNS에 www.apple.com을 묻고, 받은 엣지 주소마다 인증서를 받았다.
#!/bin/bash
# 사용법: ./edges.sh www.example.com — 리졸버마다 받은 첫 주소와 그 주소의 인증서
host=$1
for r in 168.126.63.1 210.220.163.82 164.124.101.2 8.8.8.8 1.1.1.1; do
ip=$(dig @"$r" +short A "$host" | grep -E '^[0-9.]+$' | head -1)
printf '%-15s %-15s ' "$r" "$ip"
echo | openssl s_client -connect "$ip:443" -servername "$host" 2>/dev/null \
| openssl x509 -noout -serial -enddate | tr '\n' ' '
echo
done
$ ./edges.sh www.apple.com
168.126.63.1 104.94.216.37 serial=6C869EE9086B3B0B1552C0AB9A6133C9 notAfter=Dec 16 18:31:25 2026 GMT
210.220.163.82 23.49.205.28 serial=6C869EE9086B3B0B1552C0AB9A6133C9 notAfter=Dec 16 18:31:25 2026 GMT
164.124.101.2 104.94.216.37 serial=6C869EE9086B3B0B1552C0AB9A6133C9 notAfter=Dec 16 18:31:25 2026 GMT
8.8.8.8 104.94.216.37 serial=6C869EE9086B3B0B1552C0AB9A6133C9 notAfter=Dec 16 18:31:25 2026 GMT
1.1.1.1 23.49.205.28 serial=6C869EE9086B3B0B1552C0AB9A6133C9 notAfter=Dec 16 18:31:25 2026 GMT
KT, LG U+, Google 리졸버는 104.94.216.37, SK브로드밴드와 Cloudflare 리졸버는 23.49.205.28 엣지로
보냈다. 인증서는 같았다. 쓰는 리졸버에 따라 사용자가 다른 엣지로 가니 엣지마다 확인해야 한다. 리졸버마다
답이 다른 이유는 DNS 전파 확인에 적었다.
주소마다 인증서 받기
-connect에 IP를, -servername에 원래 이름을 준다. IPv6 주소는 대괄호로 감싼다.
#!/bin/bash
# 사용법: ./nodes.sh www.example.com [포트]
host=$1; port=${2:-443}
for ip in $(dig +short A "$host" | grep -E '^[0-9.]+$') $(dig +short AAAA "$host" | grep ':'); do
case $ip in *:*) addr="[$ip]" ;; *) addr=$ip ;; esac
printf '%-26s ' "$ip"
echo | openssl s_client -connect "$addr:$port" -servername "$host" 2>/dev/null \
| openssl x509 -noout -serial -enddate -fingerprint -sha256 2>/dev/null \
| sed 's/^sha256 Fingerprint=/sha256=/' | tr '\n' ' '
echo
done
$ ./nodes.sh example.com
172.66.147.243 serial=01EEE6AABB521D5E14FC315FD9985690 notAfter=Dec 25 22:56:35 2026 GMT sha256=85:CA:6A:B0:...
104.20.23.154 serial=01EEE6AABB521D5E14FC315FD9985690 notAfter=Dec 25 22:56:35 2026 GMT sha256=85:CA:6A:B0:...
2606:4700:10::6814:179a
2606:4700:10::ac42:93f3
IPv4 두 곳은 같은 인증서다. IPv6 두 줄이 빈 건 서버 탓이 아니다. 이 망에 IPv6 경로가 없다.
$ openssl s_client -connect "[2606:4700:10::6814:179a]:443" -servername example.com </dev/null
...:error:80000041:system library:BIO_connect:No route to host:crypto/bio/bio_sock2.c:183:calling connect()
노드끼리 비교할 값은 이렇다.
| 값 | 뜻 | 쓰임 |
|---|---|---|
sha256 지문 |
인증서 전체의 해시 | 같은 인증서인지 판정. 하나라도 다르면 다른 인증서다 |
serial |
CA가 매긴 일련번호 | 사람이 대조하기 쉽다. CA의 발급 기록과 맞춰 본다 |
notAfter |
만료 시각 | 다른 인증서일 때 급한지 판단 |
curl로 특정 노드를 사용자처럼 열어 볼 수도 있다. -w '%{remote_ip}'는 실제로 붙은 주소를 찍는다.
curl -sS -o /dev/null -w '%{remote_ip}\n' --resolve www.example.com:443:203.0.113.11 https://www.example.com/
주소 하나 뒤에 여러 대
주소는 하나인데 가끔 실패한다면 L4 로드밸런서 뒤의 백엔드를 본다. L4 로드밸런서가 TCP를 그대로 넘기면 백엔드가 각자 인증서를 낸다. 주소가 하나라 위 루프로는 보이지 않는다. 같은 주소에 여러 번 붙어 지문이 어떻게 나뉘는지 본다.
#!/bin/bash
# 사용법: ./repeat.sh <IP> <이름> [횟수]
ip=$1; host=$2; n=${3:-20}
for i in $(seq "$n"); do
echo | openssl s_client -connect "$ip:443" -servername "$host" 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256
done | sort | uniq -c
$ ./repeat.sh 104.20.23.154 example.com 20
20 sha256 Fingerprint=85:CA:6A:B0:68:E9:BC:CE:88:B6:C4:AA:3C:47:F7:D1:72:28:13:4A:45:7F:87:0D:38:00:E6:22:3A:0D:F0:7A
줄이 둘 이상이면 주소 하나 뒤에서 인증서가 갈린다. 한 줄이라고 안심할 수는 없다. 클라이언트 주소로
백엔드를 고르는 분산 방식(예: nginx stream 모듈의 hash $remote_addr)이면 같은 자리에서 몇 번을 붙어도 같은
백엔드로 간다. 이럴 때는 안쪽에서 백엔드 주소로 직접 붙는다.
다르다고 다 장애는 아니다
지문이 다르면 인증서가 다르다는 것까지만 알 수 있다. 그게 옛 인증서인지는 따로 봐야 한다. 정상인데도 노드마다 인증서가 다른 경우가 있다.
노드마다 다른 인증서를 쓰는 서비스가 있다. smtp.gmail.com의 587 포트(STARTTLS)에 주소 다섯 곳으로
붙었더니 세 곳은 WE2가, 두 곳은 WR2가 발급한 인증서를 냈다. 이름과 만료일(2026-12-11)은 같았다.
142.251.170.109 serial=1702BEE8068714AC0A68A6D7576E815E notAfter=Dec 11 20:01:45 2026 GMT issuer=C=US, O=Google Trust Services, CN=WE2
64.233.187.108 serial=1702BEE8068714AC0A68A6D7576E815E notAfter=Dec 11 20:01:45 2026 GMT issuer=C=US, O=Google Trust Services, CN=WE2
172.217.221.109 serial=703FE5A750E977800A989775B39E493C notAfter=Dec 11 20:01:39 2026 GMT issuer=C=US, O=Google Trust Services, CN=WR2
192.178.226.109 serial=703FE5A750E977800A989775B39E493C notAfter=Dec 11 20:01:39 2026 GMT issuer=C=US, O=Google Trust Services, CN=WR2
64.233.189.109 serial=1702BEE8068714AC0A68A6D7576E815E notAfter=Dec 11 20:01:45 2026 GMT issuer=C=US, O=Google Trust Services, CN=WE2
같은 노드도 클라이언트에 따라 다른 인증서를 낸다. 키 종류별로 인증서를 둘 이상 두는 서버가 있어서다.
example.com의 같은 주소에 기본 설정으로 한 번, RSA 암호군만 허용한 TLS 1.2로 한 번 붙었다.
$ echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -serial -issuer
serial=01EEE6AABB521D5E14FC315FD9985690
issuer=C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
$ echo | openssl s_client -connect example.com:443 -servername example.com \
-tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 2>/dev/null | openssl x509 -noout -serial -issuer
serial=62036C9F540965765E0ECC173CD1509C
issuer=C=US, O=SSL Corporation, CN=Cloudflare TLS Issuing RSA CA 3
그래서 노드를 비교할 때는 같은 옵션으로 붙어야 한다. 도구마다 내미는 알고리즘이 다르면 같은 노드도 다른 인증서로 보인다.
노드마다 따로 발급하는 구성도 있다. 서버마다 자동 갱신을 따로 돌리면 키와 serial이 노드마다 다르다. 모두 유효하면 장애는 아니다. 다만 갱신 시점도 노드마다 달라서 한 대의 갱신이 멈춰도 다른 노드는 멀쩡해 보인다.
장애인지는 노드마다 이름과 체인 검증이 통과하는지, 만료까지 여유가 있는지로 가린다.
# 이름·체인 검증 — 실패하면 0 이 아닌 코드로 끝난다
echo | openssl s_client -connect 203.0.113.11:443 -servername www.example.com \
-verify_hostname www.example.com -verify_return_error -brief
# 30일(2592000초) 안에 만료되면 'Certificate will expire'
echo | openssl s_client -connect 203.0.113.11:443 -servername www.example.com 2>/dev/null \
| openssl x509 -noout -checkend 2592000
검증이 통과하고 만료까지 여유가 있으면 serial이 달라도 급하지 않다. 하나라도 실패하면 그 노드가 원인이다.
IPv6 쪽만 다를 때
IPv4와 IPv6를 다른 설정 블록, 다른 장비, 다른 로드밸런서가 받는 구성에서 생긴다. 갱신 절차가 IPv4 쪽만 다루면 IPv6 쪽에 옛 인증서가 남는다. 앞에서 본 RFC 6724 순서 때문에 피해는 주로 IPv6가 되는 사용자에게 간다. 운영자 사무실이 IPv4만 쓰면 아무도 모른다.
확인은 IPv6 경로가 있는 곳에서 한다. 경로가 없으면 위처럼 No route to host로 끝나는데, 이 결과는 서버에
대해 아무것도 알려 주지 않는다.
curl -6 결과를 믿기 전에 붙은 주소를 본다. 내 macOS(IPv6 경로가 없는 망)에서
curl -6 -v https://example.com/은 Trying [::ffff:104.20.23.154]:443처럼 IPv4-mapped 주소로 붙었다.
실제로는 IPv4다. -w '%{remote_ip}'가 ::ffff:로 시작하면 IPv6 경로가 아니다.
AAAA를 지울 계획이 아니라면 IPv6 리스너도 인증서 배포 목록에 넣는다.
CDN이나 GeoDNS를 쓸 때
특정 지역이나 통신사에서만 실패한다면 엣지를 의심한다. TLS를 CDN 엣지에서 끝내면 사용자가 보는 인증서는 엣지의 것이다. 원서버 인증서를 갱신해도 엣지는 그대로다. CDN에 인증서를 직접 올려(업로드·가져오기) 쓰는 구성이면 CDN마다, 환경마다 따로 교체한다. 클라우드가 대신 갱신하는 인증서와 가져온 인증서의 차이는 클라우드 관리형 인증서 가이드에 있다.
GeoDNS나 여러 CDN을 섞는 구성이면 지역마다 다른 엣지가 응답하고, 내 자리에서는 그중 일부만 보인다. 위
edges.sh처럼 리졸버를 바꿔 가며 주소와 인증서를 모으고, 다른 지역에서 본 결과가 필요하면 그 지역의
서버나 공용 측정망을 쓴다. 엣지 주소는 바뀔 수 있으니 기준은 IP 목록이 아니라 CDN 콘솔의 인증서 목록과
배포 상태다.
교체 절차
갱신을 끝냈다고 하려면 파일을 바꾼 것으로는 모자라다. 모든 종료 지점이 새 인증서를 내야 한다.
- 종료 지점 목록을 만든다. 이름마다 TLS를 끝내는 곳을 적는다. A/AAAA 주소, 로드밸런서 리스너, L4 뒤 백엔드, CDN, IPv6 리스너, 443 외 포트(메일, API)까지 적는다. 서버 목록이 아니라 인증서가 놓이는 곳의 목록이다.
- 새 인증서를 검증한다. 이름, 체인, 키 짝을 배포 전에 확인한다. 명령은 Nginx 인증서 교체와 무중단 리로드에 있다.
- 노드마다 배포하고 리로드한다. 목록 순서대로 한 곳씩 체크한다.
- 주소마다 기대한 인증서인지 확인한다. 새 인증서 파일의 지문을 모든 주소의 지문과 비교한다. 아래 스크립트는 하나라도 다르거나 접속에 실패하면 종료 코드 1로 끝난다.
#!/bin/bash
# 사용법: ./verify-rollout.sh www.example.com /etc/letsencrypt/live/www.example.com/cert.pem [포트]
host=$1; port=${3:-443}
want=$(openssl x509 -in "$2" -noout -fingerprint -sha256 | cut -d= -f2)
rc=0
for ip in $(dig +short A "$host" | grep -E '^[0-9.]+$') $(dig +short AAAA "$host" | grep ':'); do
case $ip in *:*) addr="[$ip]" ;; *) addr=$ip ;; esac
got=$(echo | openssl s_client -connect "$addr:$port" -servername "$host" 2>/dev/null \
| openssl x509 -noout -fingerprint -sha256 2>/dev/null | cut -d= -f2)
if [ "$got" = "$want" ]; then echo "OK $ip"
elif [ -z "$got" ]; then echo "FAIL $ip (접속 실패 — 이 자리에서 경로가 없는지 확인)"; rc=1
else echo "DIFF $ip $got"; rc=1
fi
done
exit $rc
example.com에서 받은 인증서를 기대값으로 주고 돌린 결과다.
$ ./verify-rollout.sh example.com example.pem; echo "exit=$?"
OK 172.66.147.243
OK 104.20.23.154
FAIL 2606:4700:10::ac42:93f3 (접속 실패 — 이 자리에서 경로가 없는지 확인)
FAIL 2606:4700:10::6814:179a (접속 실패 — 이 자리에서 경로가 없는지 확인)
exit=1
IPv6 두 줄 때문에 실패로 끝났다. 의도한 동작이다. 확인하지 못한 노드를 완료로 세지 않는다. IPv6가 되는 곳에서 다시 돌린다. L4 뒤 백엔드는 안쪽에서, CDN과 GeoDNS는 다른 지역에서 같은 확인을 한다.
- 모두 OK일 때만 완료 처리한다. 작업 기록에 새 지문과 확인한 주소 목록을 남긴다. 이 기록이 다음 갱신 때 0번 목록이 된다.
- 옛 인증서 폐기는 마지막이다. 키 유출처럼 폐기가 필요한 경우에도 모든 노드가 새 인증서로 바뀐 것을 확인한 뒤에 한다. 빠진 노드가 폐기된 인증서를 계속 내면 폐기를 확인하는 클라이언트에서 그 노드만 실패한다.
자동화
노드마다 따로 발급하지 않는다. 노드마다 ACME 클라이언트를 돌리면 문제가 생긴다.
발급 한도에 걸린다. Let’s Encrypt는 같은 이름 목록으로 7일에 5장까지 발급한다. ARI로 조율한 갱신이 아니면 갱신도 이 한도에 들어간다. 노드가 다섯 대를 넘으면 한 주에 다 받을 수 없다.
HTTP-01 검증도 어긋난다. Let’s Encrypt 문서는 웹서버가 여럿이면 검증 파일이 모든 서버에 있어야 한다고 적는다. 검증 요청이 여러 관측 지점에서 여러 번 올 수 있어서, 다른 노드로 간 요청은 파일을 찾지 못한다. DNS-01은 웹서버가 여럿이어도 잘 동작한다.
실패도 조용하다. 앞에서 본 것처럼 노드마다 갱신 시점이 달라서 한 대가 멈춰도 눈에 띄지 않는다.
한 곳에서(DNS-01) 발급하고 설정 관리 도구로 모든 노드에 배포하면 모든 노드의 지문이 같아진다. 그래야
verify-rollout.sh처럼 기대값 하나로 비교할 수 있다.
배포 뒤 확인은 파이프라인에 넣는다. Certbot의 deploy 훅이나 배포 작업의 마지막 단계에서
verify-rollout.sh를 돌리고, 0이 아니면 알림을 보낸다. 사람이 브라우저로 한 번 열어 보는 확인은 그 사람이
붙은 노드 하나만 본다.
바깥에서도 주기적으로 본다. 배포 직후 확인은 배포 경로의 문제를 잡고, 주기 감시는 나중에 생긴 문제(노드 교체, 오토스케일링으로 새로 뜬 노드의 옛 이미지)를 잡는다. CI라면 certdesk API의 판정 사유로 거를 수 있다.
curl -s "https://certdesk.dev/api/v1/certificate?domain=www.example.com" \
| jq -e '[.reasons[].code] | (index("NODE_MISMATCH") == null and index("NODE_EXPIRING") == null)'
jq -e는 결과가 false면 종료 코드 1을 낸다. 만료 감시 전반은
인증서 만료 알림 체계 직접 만들기에 있다.
certdesk에서 보이는 모습
certdesk 진단은 노드를 두 겹으로 비교한다.
먼저 한국 측정 지점 A가 받은 A/AAAA 주소끼리 비교한다. 측정에 쓴 주소 말고 최대 4곳에서 서버 인증서를 받아 SHA-256 지문을 맞춰 보고, 다르면 상세에 그 주소와 만료일, 발급자를 보여 준다. 결과 화면에는 ‘주소별 인증서 (N곳)’ 행이 생긴다. 측정 지점에 IPv6 경로가 없으면 IPv6 주소는 비교에서 빼고 ’참고’로 알린다.
그다음 측정 지점끼리 비교한다. 한국 측정 지점 A·B(KT 서울)와 해외(도쿄)가 받은 인증서를 견준다.
다를 때 붙는 사유와 등급은 이렇다.
| 경우 | 사유 | 등급 |
|---|---|---|
| 주소(노드)마다 인증서가 다름 | 주소(노드)마다 다른 인증서를 내줌 | 주의 |
| 다른 노드의 인증서가 만료 기준 안 | 일부 주소(노드)가 … 만료되는 인증서를 내줌 | 주의, 만료·임박이면 위험 |
| IPv4와 IPv6의 인증서가 다름 | IPv4와 IPv6에서 서로 다른 인증서를 내줌 | 주의 |
| 측정 지점마다 인증서가 다름 | 측정 지점마다 다른 인증서를 받음 | 참고 |
| 다른 지점의 인증서가 만료 기준 안 | 일부 측정 지점이 … 만료되는 인증서를 받음 | 주의·위험 |
2026-10-15에 www.google.com을 API로 조회한 결과다(일부 필드 생략).
{
"target": "www.google.com",
"status": "OK",
"reasons": [
{ "code": "IPV6_NOT_MEASURED", "severity": "info" },
{ "code": "DIFFERENT_CERTS", "severity": "info",
"detail": "한국 측정 지점 B (Korea Telecom, 서울): 2026-12-12 만료 · WR2 / 해외 측정 지점 (Oracle, 도쿄): 2026-12-12 만료 · WR2" }
],
"served": {
"ip": "142.251.155.119",
"notAfter": "2026-12-21T18:41:20Z",
"nodes": [
{ "ip": "142.251.150.119", "family": "ipv4", "same": true },
{ "ip": "142.251.156.119", "family": "ipv4", "same": true },
{ "ip": "142.251.152.119", "family": "ipv4", "same": true },
{ "ip": "142.251.154.119", "family": "ipv4", "same": true }
]
}
}
측정 지점 A가 본 다섯 주소는 같은 인증서였고, 측정 지점 B와 도쿄는 만료일이 다른 인증서를 받았다. 둘 다 만료까지 여유가 있어서 ’참고’로 끝났다. 앞에서 본 ’다르다고 다 장애는 아니다’의 경우다. 같은 상황에서 다른 쪽 인증서가 만료 기준 안이면 판정이 ’주의’나 ’위험’으로 바뀐다.
한계도 있다. 측정 지점마다 그 자리의 DNS 답만 보므로 다른 지역에만 나오는 주소는 측정 지점 비교로만 잡힌다. 주소가 다섯 곳을 넘으면 일부만 비교하고, L4 로드밸런서 뒤 백엔드는 주소당 한 번만 붙어서 놓칠 수 있다. 측정 지점 간 비교는 443 포트만 한다.
점검 순서
1. 증상의 모양
└ 일부 사용자·새로고침하면 될 때도 → 일부 노드 (2번)
└ IPv6 되는 망만 → AAAA 쪽 (IPv6 절)
└ 특정 지역·통신사만 → CDN·GeoDNS (리졸버·지역별)
2. 노드 나열 — dig A/AAAA, 리졸버별, 콘솔의 대상 목록
3. 주소마다 지문·serial·만료일 (nodes.sh) — 같은 옵션으로
└ 주소가 하나 → 반복 접속(repeat.sh), 안쪽에서 백엔드 직접
4. 다른 노드가 있으면 그 노드만 검증 (-verify_hostname -verify_return_error, -checkend)
└ 통과·여유 있음 → 장애 아님, 갱신 시점만 관리
└ 실패·만료 → 그 노드에 배포
5. 교체 후 verify-rollout.sh 가 모든 주소에서 OK 일 때 완료
6. 옛 인증서 폐기는 그다음
날짜 오류의 다른 원인(기기 시계, 만료된 중간 인증서)과 가르는 법은 NET::ERR_CERT_DATE_INVALID 원인별 해결에 있다. 메일 서버처럼 443 외 포트를 쓰는 노드는 메일 서버 인증서 점검의 포트별 확인을 함께 한다.
참조
- RFC 1794: DNS Support for Load Balancing
- RFC 6724: Default Address Selection for IPv6 (§2.1 정책표, §6 규칙)
- RFC 8305: Happy Eyeballs Version 2
- RFC 9113: HTTP/2 (§9.1 Connection Management)
- RFC 7871: Client Subnet in DNS Queries
- OpenSSL: openssl-s_client 문서 원본
- OpenSSL: openssl-x509 문서 원본 (-checkend)
- curl: man page (–resolve, -w)
- nginx: ngx_stream_upstream_module (hash)
- AWS: Target groups for your Application Load Balancers
- Let’s Encrypt: Rate Limits
- Let’s Encrypt: Challenge Types
- jq 1.8 Manual (–exit-status)