와일드카드 인증서란

와일드카드 인증서는 SAN(Subject Alternative Name) 목록에 *.example.com 같은 이름이 들어 있는 인증서입니다. 브라우저가 shop.example.com에 접속하면 그 이름이 인증서의 항목 중 하나와 맞는지 확인하는데, *.example.com이 여기에 맞습니다.

맞추는 규칙은 생각보다 좁습니다. 와일드카드 문제 대부분은 이 규칙을 실제보다 넓게 생각해서 생깁니다.

  • *는 맨 왼쪽 라벨 전체여야 합니다. *.example.com은 되지만 shop*.example.com이나 www.*.example.com은 공인 CA가 발급하지 않습니다.
  • *는 정확히 한 단계만 대신합니다. 0단계에도, 2단계에도 맞지 않습니다.

SAN 목록이 *.example.com 하나뿐인 인증서라면:

이름커버 여부
www.example.com됨
api.example.com됨
example.com안 됨 (* 자리에 라벨이 0개)
dev.api.example.com안 됨 (라벨 2개)
example.net안 됨 (다른 도메인)

세 번째와 네 번째 줄의 해결책은 같습니다. 그 이름도 인증서에 넣으면 됩니다. 한 인증서에 example.com, *.example.com, *.api.example.com을 함께 담을 수 있습니다. *.*.example.com처럼 쓰는 것만 안 됩니다.

내 인증서가 실제로 무엇을 커버하는지 확인

서버가 쓰고 있다고 생각하는 파일이 아니라, 실제 접속에서 내려오는 SAN 목록을 읽어야 합니다.

openssl s_client -connect www.example.com:443 -servername www.example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -ext subjectAltName

루트 도메인까지 포함한 와일드카드라면 대략 이렇게 나옵니다.

subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R11
X509v3 Subject Alternative Name:
    DNS:*.example.com, DNS:example.com

-servername은 SNI를 보냅니다. 이것이 없으면 많은 서버가 기본 인증서를 돌려주고, 엉뚱한 인증서를 보게 됩니다. SSL 점검은 같은 SAN 목록을 우리 네트워크 밖에서 보여 주므로, CDN이나 로드밸런서가 원 서버와 다른 인증서를 내고 있는지 의심될 때 유용합니다.

와일드카드가 커버한다고 생각한 이름에서 NET::ERR_CERT_COMMON_NAME_INVALID가 난다면 거의 언제나 루트 도메인이거나 두 단계 아래 이름입니다. 원인 전체는 CN 불일치 가이드에 정리해 두었습니다.

Let’s Encrypt로 무료 와일드카드 받기

Let’s Encrypt는 와일드카드를 발급하지만 DNS-01 챌린지로만 가능합니다. HTTP-01은 웹 서버 하나를 제어한다는 증명일 뿐, 가능한 모든 서브도메인을 제어한다는 증명이 아니라서 와일드카드에는 받아 주지 않습니다. TLS-ALPN-01도 마찬가지입니다.

DNS-01은 이렇게 동작합니다. CA가 ACME 클라이언트에 토큰을 주면, 클라이언트가 그 토큰으로 만든 값을 _acme-challenge.example.com에 TXT 레코드로 게시하고, CA가 그 값을 조회합니다. 이름에 주의하세요. *.example.com용 레코드는 *를 뺀 _acme-challenge.example.com에 둡니다. 루트 도메인 example.com용도 같은 이름입니다. 그래서 둘을 한 인증서로 신청하면 그 이름에 서로 다른 TXT 값 두 개가 동시에 있어야 합니다. 레코드를 추가하지 않고 “덮어쓰는” 클라이언트나 스크립트는 둘 중 하나의 인증에 실패하는데, 와일드카드+루트 주문이 첫 시도에 실패하는 가장 흔한 이유가 이것입니다.

certbot과 DNS 플러그인

수동 모드(--manual --preferred-challenges dns)는 한 번은 되지만 갱신할 때마다 TXT 레코드를 다시 손으로 넣어야 해서 자동 갱신이 안 됩니다. 계속 쓸 인증서라면 DNS 플러그인을 쓰세요. Cloudflare라면:

# /root/.secrets/cloudflare.ini  (chmod 600)
dns_cloudflare_api_token = <해당 영역에 Zone:DNS:Edit 권한이 있는 토큰>
certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

'*.example.com'은 따옴표로 감싸세요. 그대로 두면 셸이 현재 디렉터리의 파일 이름으로 별표를 펼칠 수 있습니다.

certbot에는 Route 53, Google Cloud DNS, DigitalOcean 등용 플러그인이 따로 있고, 별도 패키지(python3-certbot-dns-route53 등)나 snap 플러그인으로 설치합니다.

acme.sh

acme.sh는 dns_* 훅으로 많은 DNS 업체를 지원합니다.

export CF_Token="<token>"
acme.sh --issue --dns dns_cf -d example.com -d '*.example.com'

DNS 업체에 API가 없을 때

국내 등록대행사나 호스팅 관리 화면 중에는 API가 없는 곳이 많습니다. 현실적인 선택지는 두 가지입니다.

첫째는 CNAME 위임입니다. 고정 레코드 하나, _acme-challenge.example.com. CNAME example-com.acme.example.net.을 만들어 API가 있는 영역이나 acme-dns 같은 전용 서버를 가리키게 합니다. Let’s Encrypt는 TXT를 조회할 때 CNAME을 따라가므로, ACME 클라이언트는 위임받은 영역에만 쓰면 됩니다. 피해 범위도 줄어듭니다. 서버에 둔 API 자격 증명으로는 챌린지 레코드만 바꿀 수 있고, 영역 전체를 건드릴 수는 없습니다.

둘째는 DNS 호스팅을 API가 있는 업체로 옮기는 것입니다. 무료 자동 와일드카드가 목적이라면 이쪽이 더 단순한 경우가 많습니다.

CA를 탓하기 전에 TXT 레코드부터

검증이 실패하면 공용 리졸버가 실제로 무엇을 돌려주는지 보세요.

dig +short TXT _acme-challenge.example.com @1.1.1.1
dig +short TXT _acme-challenge.example.com @8.8.8.8

빈 응답이면 대개 클라이언트가 너무 일찍 확인했거나(certbot의 --dns-cloudflare-propagation-seconds로 대기 시간을 늘립니다), 다른 영역에 레코드를 만들었거나, 등록대행사에 위임된 네임서버가 내가 수정한 곳이 아닌 경우입니다. DNS 조회로 위임된 네임서버와 TXT 값을 바깥에서 확인할 수 있습니다. 일반 인증서에는 Let’s Encrypt를 허용하지만 와일드카드는 막는 CAA 레코드도 원인이 됩니다. CAA 레코드 가이드의 issuewild 부분을 보세요.

와일드카드냐, 이름 목록이냐

와일드카드 대신 이름을 여러 개 나열한 인증서도 됩니다. Let’s Encrypt는 인증서당 이름 100개까지 허용합니다. 어느 쪽이 맞는지는 서브도메인이 어떻게 생기느냐에 달려 있습니다.

서브도메인이 자주, 혹은 자동으로 생긴다면 와일드카드가 맞습니다. 고객별 호스트명을 주는 SaaS, 미리보기 환경, newteam.example.com을 추가할 때마다 재발급하고 싶지 않은 플랫폼이 그렇습니다. 개별 호스트명이 Certificate Transparency 로그에 남지 않는다는 점도 있습니다. 공인 인증서는 모두 CT 로그에 기록되고, SAN 목록에 admin.example.com, vpn.example.com, jenkins.example.com이 있으면 로그를 검색하는 누구에게나 내부 이름 체계를 공개하는 셈입니다. 와일드카드는 *.example.com만 남깁니다. 다만 이것은 작은 프라이버시 이득일 뿐 보안 통제는 아닙니다. 이름은 DNS나 링크로도 새어 나갑니다.

안정적인 서비스 몇 개를 서로 다른 서버에서 돌리거나, HTTP-01을 쓸 수 있고 웹 서버에 DNS API 키를 주고 싶지 않다면 이름 목록이 낫습니다.

보안상의 대가: 키 하나, 서버 여러 대

인증서가 암호학적으로 약한 것은 아닙니다. 대가는 개인키가 어디에 놓이느냐에서 생깁니다.

*.example.com 하나로 홍보 사이트, API, 스테이징 서버, 잊고 있던 워드프레스까지 운영한다면 네 곳 모두에 같은 개인키가 있습니다. 가장 허술한 곳에서 키를 훔친 사람은 login.example.com이나 api.example.com의 유효한 인증서를 내밀 수 있습니다. 물론 트래픽을 자기 쪽으로 끌어올 수단(탈취한 DNS 레코드, 악성 와이파이, 장악한 공유기)도 있어야 합니다. 폐기하려면 모든 호스트의 인증서를 한꺼번에 바꿔야 합니다.

같은 문제의 조용한 변형도 있습니다. 여러 이름에 유효한 인증서를 웹 서버와 메일·FTP 서버가 함께 쓰면, 공격자가 브라우저의 TLS 연결을 다른 서비스로 돌리는 교차 프로토콜 공격이 가능하다는 연구가 발표된 적이 있습니다. 교훈은 같습니다. 노출 수준이 크게 다른 서비스에 같은 와일드카드 키를 두지 마세요.

실무에서는 이렇게 합니다.

  • 와일드카드는 이득이 있는 곳(동적으로 생기는 이름, 여러 호스트의 TLS를 한곳에서 종료하는 엣지 프록시)에 쓰고, 따로 노출되는 메일·VPN·관리자 페이지에는 별도 인증서를 줍니다.
  • 키를 백엔드마다 복사하지 말고, 리버스 프록시나 로드밸런서 한 계층에서 와일드카드를 종료합니다.
  • 상용 와일드카드를 사더라도 개인키 생성과 보관은 내 책임입니다. 팀끼리 “공유”한다며 키를 메일로 돌리거나 저장소에 넣지 마세요.

EV 인증서는 와일드카드로 발급할 수 없습니다. EV 규정이 금지합니다. OV와 DV 와일드카드는 상용으로 모두 구할 수 있습니다.

Cloudflare와 와일드카드

도메인을 Cloudflare 프록시로 쓰면 방문자가 보는 것은 내 인증서가 아니라 Cloudflare 엣지 인증서입니다. 무료 Universal SSL 인증서는 example.com과 *.example.com, 즉 서브도메인 한 단계까지만 커버합니다.

그래서 dev.api.example.com을 프록시로 켜면 원 서버에 그 이름의 완벽한 인증서가 있어도 엣지에서 인증서 오류가 납니다. 방법은 *.api.example.com을 포함한 Advanced Certificate(유료 부가 기능), 같은 부가 기능에 들어 있어 프록시된 호스트명마다 인증서를 발급하는 Total TLS, 또는 그 레코드를 DNS 전용으로 바꿔 방문자가 원 서버와 그 인증서로 직접 오게 하는 것입니다.

Cloudflare와 원 서버 사이 구간에는 Cloudflare Origin CA가 와일드카드 원 서버 인증서를 무료로 발급해 줍니다. 브라우저는 이 인증서를 신뢰하지 않으므로 트래픽이 Cloudflare를 거치는 동안에만 쓸 수 있습니다. 엣지 인증서와 원 서버 인증서가 어떻게 맞물리는지는 SSL 모드에 따라 다릅니다. Cloudflare SSL 모드를 보세요.

갱신

와일드카드도 다른 인증서처럼 갱신하지만 실패 지점이 하나 더 있습니다. DNS API 자격 증명입니다. 토큰은 교체되고, 담당자가 떠나면 폐기되고, 엉뚱한 영역 권한으로 만들어지기도 합니다. 인증서는 만료될 때까지 잘 동작하므로, 몇 달 뒤 만료 경고가 첫 신호가 되는 경우가 많습니다.

설정이 아니라 전체 경로를 시험하세요.

certbot renew --dry-run

스테이징 CA를 상대로 실제 DNS-01 챌린지를 돌립니다. 자격 증명이나 전파 대기 시간 초과로 실패한다면 지금 고치세요. 2026년부터 인증서 최대 유효 기간이 줄어들고 있어(만료 체크리스트 참고) 갱신이 잦아지고 망가진 토큰도 더 빨리 드러납니다. 다만 무언가가 지켜보고 있을 때만 그렇습니다. 갱신할 때마다 와일드카드 사본이 있는 모든 곳에 새 인증서가 들어갔는지 확인하세요. 와일드카드는 단일 이름 인증서보다 배포된 곳이 많고, 그래서 한두 곳을 빠뜨리기 쉽습니다.

내 도메인에서 바로 확인

무료, 가입 불필요. 이 가이드가 다루는 항목을 바로 검사하고 조치 방법을 알려드립니다.