RSS
웹 인프라

Vercel 커스텀 도메인에 HTTPS 인증서가 안 붙을 때: 도메인을 DNS보다 먼저 추가한 경우

작성자
듀오랩스 대표·7분 읽기

고객사 업무 시스템을 고객사 도메인의 서브도메인(shop.고객사도메인)에 붙이는 일이었습니다. 기존 홈페이지를 맡은 호스팅 업체에 CNAME 한 줄을 부탁했고, 이틀 뒤 「넣었다」는 답이 왔습니다. 그런데 주소를 열면 연결이 안 된다는 연락이 이어서 왔습니다.

결론부터 말하면 DNS는 처음부터 맞았습니다. 빠져 있던 것은 HTTPS 인증서였고, 그 원인은 제가 도메인을 붙인 순서에 있었습니다.

DNS가 무죄라는 확인부터

「연결이 안 된다」는 말을 들으면 DNS부터 의심하게 됩니다. 이번에도 그랬습니다. 다만 동네 DNS 캐시 하나로 판단하면 틀리기 쉬워서, 묻는 곳을 셋으로 나눴습니다.

dig +short @8.8.8.8 shop.example.com          # 구글 공개 DNS
dig +short @1.1.1.1 shop.example.com          # Cloudflare 공개 DNS
dig @<호스팅 업체의 네임서버> shop.example.com +noall +answer   # 정본

세 곳 모두 Vercel이 준 CNAME 값을 돌려줬습니다. 네임서버가 직접 답한 TTL은 1800초였고, 공개 DNS 두 곳도 이미 새 값을 들고 있었습니다. Vercel 쪽에 물어도 같았습니다.

vercel domains verify shop.example.com
# "status": "ok", "reason": "configured_correctly"

DNS도, Vercel의 도메인 인식도 정상이었습니다. 그러면 막힌 곳은 그 뒤쪽입니다.

http는 닿고 https만 끊기는 증상

접속을 http와 https로 나눠 보니 둘의 결과가 달랐습니다.

curl -sI http://shop.example.com/
# HTTP/1.1 302 → Vercel 로그인 페이지(배포 보호)

curl -sv https://shop.example.com/ -o /dev/null
# Connected to shop.example.com port 443
# LibreSSL SSL_connect: SSL_ERROR_SYSCALL

http 요청은 Vercel까지 가서 배포 보호의 로그인 화면으로 넘어갔습니다. 요청이 Vercel에 닿는다는 뜻입니다. https는 TCP 연결까지는 되는데 TLS 핸드셰이크에서 끊겼고, openssl s_client로 받아 본 인증서는 비어 있었습니다. 이 서브도메인용 인증서가 아직 없는 것이었습니다.

처음에는 curl이 아무것도 출력하지 않아서 「브라우저나 이 맥의 DNS 캐시가 옛 값을 들고 있는 것 아닌가」 쪽으로 기울었습니다. -v를 붙여 핸드셰이크 단계를 보고 나서야 DNS가 아니라 TLS 문제라는 것이 드러났습니다. 「연결이 안 된다」는 증상 하나에 원인이 여러 층에 걸쳐 있을 수 있다는 것을 다시 확인한 대목입니다.

인증서가 없던 원인, 도메인을 붙인 순서

Vercel은 커스텀 도메인을 프로젝트에 추가하면 Let's Encrypt에 인증서를 요청합니다. Let's Encrypt는 발급하기 전에 그 도메인이 정말 Vercel로 오는지 확인합니다.

제가 한 순서는 이랬습니다.

  1. Vercel 프로젝트에 shop.example.com을 먼저 추가했습니다.
  2. 그때 받은 프로젝트 전용 CNAME 값을 호스팅 업체에 보냈습니다.
  3. 이틀 뒤 업체가 DNS에 넣었습니다.

1번 시점에 그 서브도메인은 아직 와일드카드 레코드 때문에 기존 홈페이지 서버를 가리키고 있었습니다. 인증서 발급을 위한 확인이 그쪽으로 가니 실패할 수밖에 없었습니다. Vercel은 실패하면 간격을 두고 다시 시도하는데, 정확한 재시도 주기는 공개돼 있지 않습니다. DNS가 고쳐진 뒤에도 다음 시도를 기다리는 동안 인증서 없는 상태가 이어졌던 것으로 봅니다.

이 순서는 피하기 어려웠다는 점도 적어 둡니다. 최근 Vercel은 프로젝트마다 다른 CNAME 값(xxxx.vercel-dns-016.com 꼴)을 권하는데, 이 값은 도메인을 프로젝트에 추가해야 받을 수 있습니다. DNS를 먼저 넣으려면 값이 없고, 값을 받으려면 도메인을 먼저 추가해야 합니다. 범용 값 cname.vercel-dns.com을 쓰면 DNS를 먼저 넣을 수는 있습니다.

발급을 막는 다른 원인이 없는지 확인

바로 재발급을 요청하기 전에 하나를 더 봤습니다. DNS의 CAA 레코드가 인증서를 낼 수 있는 기관을 제한하고 있으면 몇 번을 다시 시도해도 실패합니다.

dig +short example.com CAA          # 비어 있음(제한 없음)
dig +short shop.example.com CAA     # CNAME 을 따라가 Vercel 쪽 CAA 가 나옴
# 0 issue "letsencrypt.org"
# 0 issue "pki.goog" …

상위 도메인에는 CAA가 없었고, 서브도메인은 CNAME을 따라 Vercel 쪽 CAA(Let's Encrypt 등 허용)가 보였습니다. 막는 것은 없었습니다.

해결은 재발급 요청 한 줄

vercel certs issue shop.example.com
# Success! Certificate entry for shop.example.com created [8s]

8초 만에 끝났습니다. DNS가 이미 맞았으니 이번 확인은 바로 통과한 것입니다. 다시 접속해 인증서를 확인했습니다.

echo | openssl s_client -servername shop.example.com -connect shop.example.com:443 \
  | openssl x509 -noout -subject -issuer -enddate
# subject=/CN=shop.example.com
# issuer=/C=US/O=Let's Encrypt/CN=YR2
# notAfter=Jan  6 2027 GMT

https도 이제 Vercel의 로그인 화면으로 넘어갑니다. 아직 공개 전이라 배포 보호를 켜 둔 상태이고, 이것이 정상입니다. 갱신은 Vercel이 만료 전에 알아서 합니다.

다음부터 바꾸는 습관

같은 일을 다시 한다면 이렇게 하겠습니다.

  • 프로젝트 전용 CNAME 값을 써야 한다면, 도메인을 추가해 값을 받고 DNS가 바뀌었다는 연락을 받은 직후에 대시보드의 도메인 화면에서 Refresh를 누르거나 vercel certs issue를 한 번 실행합니다. 재시도를 기다리지 않습니다.
  • 범용 값으로 충분하다면 DNS를 먼저 넣고 도메인을 나중에 추가합니다. 그러면 첫 발급 시도가 바로 통과합니다.
  • 「연결이 안 된다」는 연락에는 DNS(정본 네임서버와 공개 DNS), 플랫폼의 도메인 인식, http, https를 차례로 나눠 봅니다. 어느 층에서 끊겼는지만 알면 고치는 일은 대개 한 줄입니다.
  • 나중에 DNS를 Cloudflare로 옮긴다면 이 줄은 프록시를 끈 DNS only로 둡니다. 프록시를 켜면 Let's Encrypt의 확인이 Cloudflare에서 끝나 Vercel이 인증서를 갱신하지 못할 수 있습니다.

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.