홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

와일드카드가 못 덮는 것 — Traefik DNS-01과 Caddy on-demand TLS를 가른 지점

정기창·2026년 8월 31일

지난 글에서 와일드카드 인증서를 DNS-01로 받는 방법과, 그 과정에서 필요한 IAM 권한을 좁히는 이야기를 했습니다. 그런데 인증서를 손에 넣고 나서 정작 중요한 질문이 남았습니다. 이 인증서가 무엇을 덮지 못하는가입니다.

와일드카드는 정확히 한 단계만 덮습니다

*.example.com 인증서가 유효한 범위는 생각보다 좁습니다.

호스트 *.example.com로 덮이나
acme.example.com 덮임
example.com (apex) 안 덮임 — SAN에 따로 넣어야 합니다
app.acme.example.com 안 덮임 — 두 단계입니다
workspace.customer.co.kr 애초에 무관 — 다른 도메인입니다

마지막 행이 문제였습니다. 기업 고객이 자기 도메인으로 서비스를 쓰고 싶어 하는 요구는 B2B SaaS에서 흔합니다. 그런데 그건 와일드카드로 접근할 수 있는 문제가 아닙니다. 고객 수만큼의 서로 무관한 도메인이 생기는 것이고, 그 도메인들의 DNS는 우리 소유가 아닙니다.

즉 와일드카드는 이 요구에 대해 구조적으로 잘못된 도구입니다. 성능이나 비용 문제가 아니라 범위가 애초에 안 맞습니다. 이걸 깨닫고 나서야 제가 짓고 있던 것이 나중에 버려질 중간 구조물일 수 있다는 생각이 들었습니다.

임의의 도메인을 받으려면 발급 시점이 달라야 합니다

고객 도메인을 지원하는 전통적인 방법은 이렇습니다. 고객이 도메인을 등록하면 우리 쪽에서 인증서를 발급하고, 프록시 설정에 그 도메인을 추가하고, 프록시를 리로드합니다. 도메인이 늘어날 때마다 이 과정이 반복됩니다.

동작은 합니다. 다만 도메인 추가가 배포 행위가 됩니다. 고객이 늘수록 설정 파일이 커지고, 자동화를 짜야 하고, 그 자동화가 실패했을 때 복구 절차가 필요해집니다.

Caddy의 on-demand TLS는 이 순서를 뒤집습니다. 인증서를 미리 발급하지 않고 TLS 핸드셰이크가 들어오는 순간 발급합니다. 처음 보는 SNI가 오면 그때 ACME를 돌리는 것입니다.

기존 방식
  고객 도메인 등록 → 인증서 발급 → 설정 추가 → 리로드 → 접속 가능

on-demand TLS
  고객 도메인 등록 → (DB에 저장) → 접속 시도 → 그 자리에서 발급 → 접속 가능

설정 파일에 도메인이 등장하지 않습니다. 프록시 리로드도 없습니다. 도메인 추가가 애플리케이션 데이터 변경으로 내려옵니다.

그대로 열면 위험합니다 — ask가 필수인 이유

여기서 바로 걸리는 문제가 있습니다. "처음 보는 도메인이면 발급한다"를 조건 없이 켜면, 아무나 자기 도메인을 우리 서버 IP로 향하게 해놓고 접속하는 것만으로 인증서 발급을 유발할 수 있습니다. Let's Encrypt 발급 한도를 소진시키는 것은 물론이고 그 자체가 남용입니다.

그래서 Caddy는 ask를 요구합니다. 발급 직전에 우리 API에 "이 도메인 발급해도 됩니까"를 물어보고, 2xx가 와야 진행합니다.

{
	on_demand_tls {
		ask http://api:4000/tenants/domains/verify
	}
}

https:// {
	tls { on_demand }
	reverse_proxy web:3000
}

ask 엔드포인트는 ?domain= 쿼리를 받아서, 그 도메인이 우리 DB에 등록된 고객 도메인인지 확인하고 200이나 403을 돌려주면 됩니다. 결국 인증서 발급 정책이 애플리케이션 코드로 내려옵니다. 이게 이 방식의 핵심 이점이라는 생각이 들었습니다.

폐지된 옵션에 걸렸는데, 그게 오히려 증거였습니다

예전 문서와 블로그 글에는 on_demand_tls 안에 interval과 burst로 발급 빈도를 제한하는 예제가 많습니다. 그대로 넣었더니 설정 검증이 실패했습니다. 최신 Caddy에서 이 두 옵션은 제거되었고 ask만 남았습니다.

처음엔 실패한 줄 알았는데, 에러 메시지를 다시 읽어보니 그게 아니었습니다. "이 위치에 전역 블록을 쓸 수 없다"가 아니라 "이 옵션을 모른다"였습니다. 즉 블록 자체는 정상적으로 파싱된 것이고, 제가 확인하고 싶었던 바로 그 사실이 에러를 통해 증명된 셈이었습니다. 실패 메시지를 끝까지 읽는 습관이 시간을 아꼈습니다.

Coolify 안에서 프록시를 바꿀 수 있는가

여기까지는 Caddy 일반론입니다. 실제 문제는 이미 Coolify로 운영 중인 서버에서 이게 되느냐였습니다. 새 서버를 세우거나 프레임워크를 갈아엎어야 한다면 얘기가 완전히 달라집니다.

확인해보니 네 가지가 다행히 맞아떨어졌습니다.

확인 항목 결과
Coolify가 Caddy를 지원하나 프록시 종류에 CADDY가 정식으로 있습니다
구현체 caddy-docker-proxy — 라벨 기반으로 Caddy 설정을 만듭니다
전역 옵션을 넣을 자리가 있나 기본 Caddyfile이 import /dynamic/*.caddy 한 줄뿐입니다
런타임 병합 후에도 남나 병합된 설정에서 on_demand 보존을 확인했습니다

세 번째가 특히 중요했습니다. 전역 옵션 블록은 Caddyfile의 맨 앞에 와야 하는데, 기본 파일이 import 한 줄뿐이라 우리 조각이 자연스럽게 최상단에 놓입니다. 기본 설정에 다른 내용이 들어 있었다면 이 방법은 못 썼을 것입니다.

디스크를 고치면 되돌아옵니다

한 가지 함정을 미리 확인해둔 것이 도움이 됐습니다. Coolify는 프록시 설정을 데이터베이스에 저장해두고 필요할 때 디스크로 다시 씁니다. 서버에 SSH로 들어가 설정 파일을 직접 고치면 다음 재생성 때 조용히 원복됩니다.

그래서 변경은 UI(또는 그것이 쓰는 저장 경로)를 거쳐야 합니다. 이걸 모르고 파일만 고쳤다면 "분명 고쳤는데 왜 안 되지"로 한참을 헤맸을 것 같습니다.

그래서 무엇을 얻고 무엇을 잃는가

항목 Traefik + 와일드카드 DNS-01 Caddy + on-demand TLS
서브도메인 ({테넌트}.example.com) 인증서 1장으로 전부 도메인별 발급
고객 소유 도메인 불가 — 별도 메커니즘 필요 같은 메커니즘으로 처리
도메인 추가 절차 DNS 레코드 + (경우에 따라) 설정 DB 등록만
DNS API 권한 필요 (DNS-01) 불필요 (HTTP-01)
첫 접속 지연 없음 있음 — 핸드셰이크 중 발급
남용 방어 구조상 불필요 ask 엔드포인트를 직접 구현

표만 보면 Caddy 쪽이 좋아 보이지만, 실제 전환 비용은 표에 안 나옵니다.

443은 하나뿐입니다

가장 큰 대가는 이것이었습니다. Caddy로 간다는 것은 Traefik을 교체한다는 뜻입니다. 같은 IP의 443 포트를 둘이 나눠 쓸 수 없기 때문입니다. 나란히 두고 천천히 옮기는 선택지가 없습니다.

그래서 Traefik 미들웨어에 의존하던 것들을 전부 Caddy 문법으로 다시 써야 합니다. Basic Auth 하나만 해도 basic_auth로 옮겨야 하고, 그 사이에 잠깐이지만 서비스가 끊깁니다. 운영 중인 서비스라면 이 순단을 언제 낼 것인지가 별도의 문제가 됩니다.

정리하면

처음에는 "와일드카드 인증서를 어떻게 받나"라는 기술 질문으로 시작했습니다. 답을 찾고 나서야 그 답이 내가 풀려던 문제의 절반만 푼다는 것을 알았습니다.

그리고 나머지 절반을 푸는 도구는 같은 계열의 개선이 아니라 프록시를 통째로 바꾸는 결정이었습니다. 이런 경우 먼저 만든 쪽이 나중에 버려지기 쉽습니다. 그래서 다음 글에서는 "그럼 무엇을 먼저 정해야 두 번 짓지 않는가"를 정리해보려 합니다. 결론부터 말하면 정해야 할 것은 기술이 아니라 목적이었습니다.

CaddyTraefikTLSCoolify멀티테넌트인프라

관련 글

와일드카드 인증서는 HTTP-01으로 못 받습니다 — DNS-01이 강제하는 IAM 결정

멀티테넌트 서브도메인을 위해 와일드카드 인증서를 발급하려다 알게 된 것은, 문제가 TLS가 아니라 DNS 쓰기 권한이라는 점이었습니다. 프록시에 Route53 권한을 주는 순간 생기는 폭발 반경과 그것을 좁히는 방법을 정리했습니다.

관련도 96%

무중단 서버 이전: DNS 컷오버와 Let's Encrypt 타이밍 함정

이미지를 옮기고 DNS만 바꾸면 끝일 줄 알았는데, Let's Encrypt 인증서가 발급되지 않았습니다. 무중단 이전의 설계와, TTL을 미리 낮추지 않아 인증서 발급이 지연된 함정을 로그와 함께 정리했습니다.

관련도 94%

EC2를 띄우기 전에 AWS CLI로 먼저 잰 것들 — 계획서가 틀려 있던 8가지

Coolify를 EC2에 올리기 전, 읽기 전용 describe만으로 계정을 먼저 쟀습니다. 계획서의 1순위 단계는 불필요했고, 정작 필수였던 것들은 계획서에 아예 없었습니다. 비용 0인 준비만 먼저 끝낸 과정을 정리했습니다.

관련도 91%