Traefik는 ACME를 통해 인증서를 자동으로 발급하는 것으로 유명합니다. 하지만 때로는 그 대신 직접 보유한 인증서를 사용해야 할 때가 있습니다. GetHTTPS에서 발급받은 인증서, 사내/내부 CA가 발급한 인증서, 또는 다른 곳에서 관리하는 와일드카드 인증서 같은 것 말이죠. 이 가이드는 Traefik v3에서 그 방법을 정확히 알려 줍니다.
먼저 이해해야 할 핵심은 이것입니다. Docker 라벨로는 인증서 파일을 연결할 수 없습니다. Traefik 라벨은 라우팅을 제어하고 TLS를 활성화하지만, 인증서 자체는 동적 설정으로서 파일 provider에서 와야 합니다. 거의 모든 사람이 처음에 여기서 걸려 넘어집니다. 이 가이드에서는 올바른 방법으로 진행하겠습니다.
아직 인증서가 없다면 5분 만에 무료로 발급받으세요.
언제 사용해야 하나 (Traefik 자동 ACME와 비교)
| 자동 ACME 사용 (Let’s Encrypt) | 직접 보유한 인증서 사용 (이 가이드) |
|---|---|
| 공개 웹사이트, 인터넷에서 접근 가능 | 내부/인트라넷 서비스 (공개 DNS 없음) |
| 표준 DV 인증서로 충분함 | 특정 CA, 와일드카드, 또는 OV/EV 인증서가 필요함 |
| Traefik가 HTTP/DNS 챌린지를 완료할 수 있음 | 망분리(에어갭) 또는 방화벽 뒤에 있는 호스트 |
| 인증서 관리를 전혀 하고 싶지 않음 | 이미 GetHTTPS나 다른 곳에서 받은 인증서가 있음 |
자동 ACME가 상황에 맞는다면 Traefik의 certificatesResolvers가 더 간단합니다. 이 가이드는 “이미 fullchain.pem + privkey.pem이 있고, Traefik가 이를 제공하도록 하고 싶다”는 경우를 위한 것입니다.
사전 준비물
- Traefik v3가 Docker 컨테이너로 실행 중 (아래 설정은 Docker를 사용하지만, 동적 설정 부분은 어떤 구성에도 적용됩니다)
- 인증서 파일 — 두 개:
fullchain.pem— 인증서 + 중간 체인privkey.pem— 개인 키
- 호스트를 가리키는 도메인 (또는 내부용으로 로컬 hosts 파일 항목)
Traefik에는 어떤 파일이 필요할까요? Traefik는
certFile에 전체 체인을,keyFile에 키를 원합니다. 리프(leaf) 인증서만 들어 있는cert.pem을 사용하면 클라이언트에서 “체인 불완전(incomplete chain)” 오류가 발생합니다. Traefik는 파일 provider 인증서에 중간 인증서를 자동으로 덧붙이지 않습니다.fullchain.pem을 사용하세요. 인증서 형식 설명 →
Traefik 설정은 어떻게 나뉘나
Traefik에는 두 가지 종류의 설정이 있으며, 인증서는 두 번째 종류에 들어 있습니다.
- 정적 설정(Static configuration) — 시작 시 설정됩니다 (entrypoint, provider). 변경하려면 재시작이 필요합니다. 이는
traefik.yml또는 컨테이너 명령 플래그에 들어갑니다. - 동적 설정(Dynamic configuration) — 재시작 없이 실시간으로 다시 로드됩니다 (router, 서비스, TLS 인증서). 파일 provider가 읽는 것이 바로 이것입니다.
여러분의 인증서는 동적 설정입니다. 그래서 라벨이 인증서를 담을 수 없는 것입니다. 라벨은 Docker provider에서 읽지만, tls.certificates는 파일 provider의 개념이기 때문입니다.
1단계: 인증서 파일 배치
컨테이너에 마운트할 디렉터리에 파일을 넣으세요.
mkdir -p ./traefik/certs
cp fullchain.pem ./traefik/certs/
cp privkey.pem ./traefik/certs/
# 개인 키 권한을 제한
chmod 600 ./traefik/certs/privkey.pem
chmod 644 ./traefik/certs/fullchain.pem
2단계: 동적 설정 작성
./traefik/dynamic/certs.yml을 만드세요. 여기에서 인증서를 선언합니다.
# ./traefik/dynamic/certs.yml (동적 설정 — 파일 provider)
tls:
certificates:
- certFile: /certs/fullchain.pem
keyFile: /certs/privkey.pem
# 선택 사항: 더 구체적인 인증서와 일치하지 않는 모든 호스트에 대해
# 이 인증서를 기본값으로 지정 (예: 와일드카드 또는 내부 CA).
stores:
default:
defaultCertificate:
certFile: /certs/fullchain.pem
keyFile: /certs/privkey.pem
경로(/certs/...)는 컨테이너 내부의 경로입니다. 다음 단계에서 이를 마운트합니다. Traefik는 SNI를 사용해 요청마다 올바른 인증서를 선택하며, 인증서의 SAN을 요청된 호스트명과 비교합니다. defaultCertificate는 다른 어떤 것도 일치하지 않을 때의 대체값입니다.
3단계: Traefik 설정 (정적 설정 + 마운트)
완전한 docker-compose.yml은 다음과 같습니다. entrypoint와 파일 provider는 정적 설정(명령 플래그로 전달)이고, 인증서는 방금 작성한 동적 파일에서 로드됩니다.
services:
traefik:
image: traefik:v3.3
container_name: traefik
restart: unless-stopped
command:
# ─── Entrypoints ───────────────────────────────
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
# 모든 HTTP → HTTPS를 entrypoint 수준에서 리디렉션
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.web.http.redirections.entrypoint.permanent=true"
# ─── Providers ─────────────────────────────────
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
# 파일 provider가 동적 설정 디렉터리를 감시
- "--providers.file.directory=/dynamic"
- "--providers.file.watch=true"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./traefik/certs:/certs:ro"
- "./traefik/dynamic:/dynamic:ro"
# ─── 예시 백엔드 서비스 ──────────────────────
whoami:
image: traefik/whoami
container_name: whoami
labels:
- "traefik.enable=true"
# Router: 호스트와 일치시키고, HTTPS entrypoint에서 제공
- "traefik.http.routers.whoami.rule=Host(`example.com`)"
- "traefik.http.routers.whoami.entrypoints=websecure"
# 이 router에서 TLS 활성화 — 인증서는 라벨이 아니라
# 동적 설정에서 가져온다.
- "traefik.http.routers.whoami.tls=true"
핵심 줄은 traefik.http.routers.whoami.tls=true입니다. 이는 Traefik에 “이 router를 TLS로 제공하라”고 알려 줍니다. 그러면 Traefik는 SNI를 통해 파일 provider 스토어에서 일치하는 인증서를 고릅니다. tls.certfile 같은 라벨은 없습니다. router에는 그런 것이 존재하지 않습니다.
4단계: 시작 및 확인
docker compose up -d
docker compose logs -f traefik
파일 provider가 인증서를 로드하는지 지켜보세요. TLS 오류가 보이지 않아야 합니다. 그런 다음 확인하세요.
브라우저 확인
https://example.com을 방문하세요. 자물쇠 아이콘을 클릭합니다.
- 발급자(Issued by) — 여러분의 CA (GetHTTPS의 경우 Let’s Encrypt, 또는 여러분의 내부 CA)
- 유효 기간(Valid) — 시작 및 종료 날짜
- 도메인 — URL과 일치
명령줄 확인
# Traefik가 자체 서명된 기본 인증서가 아닌 여러분의 인증서를 제공하는지 확인
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
CN=TRAEFIK DEFAULT CERT가 보인다면 Traefik가 내장 자리표시자(placeholder)로 대체된 것이며, 여러분의 인증서가 로드되지 않은 것입니다. 아래 Troubleshooting 절을 참고하세요.
5단계 (선택 사항): TLS 강화
프로토콜 버전과 암호화 알고리즘을 제어하려면 동적 설정에 TLS 옵션 블록을 추가하고 router에서 이를 참조하세요. certs.yml에 추가합니다.
tls:
options:
modern:
minVersion: VersionTLS12
sniStrict: true
그런 다음 router 라벨에 추가합니다.
- "traefik.http.routers.whoami.tls.options=modern@file"
@file 접미사는 이 옵션 세트가 파일 provider에서 온다는 것을 Traefik에 알려 줍니다. 이렇게 하면 TLS 1.2와 1.3으로 제한되고, 일치하는 SNI가 없는 연결은 거부됩니다.
HSTS: HTTPS가 모든 곳에서 작동한다고 확신이 들면 Traefik
headers미들웨어를 통해Strict-Transport-Security헤더를 추가하세요. HSTS 가이드 →
갱신하는 방법
Traefik는 여러분이 직접 제공한 인증서를 자동으로 갱신하지 않습니다. 이것이 직접 인증서를 가져올 때의 절충점입니다. 인증서 만료가 임박하면(Let’s Encrypt의 경우 90일 중 60일째):
- GetHTTPS(또는 여러분의 갱신 도구)에서 새 인증서를 받으세요.
- 파일을 제자리에 덮어쓰세요.
cp new-fullchain.pem ./traefik/certs/fullchain.pem cp new-privkey.pem ./traefik/certs/privkey.pem - 그게 전부입니다.
--providers.file.watch=true가 설정되어 있으므로, Traefik가 파일 변경을 감지해 인증서를 핫 리로드합니다. 재시작도, 다운타임도 없습니다. 이는 사용자 지정 인증서에 있어 Traefik의 가장 멋진 특성 중 하나입니다.
Troubleshooting
Traefik가 내 인증서 대신 “TRAEFIK DEFAULT CERT”를 제공함
원인: Traefik가 인증서를 로드할 수 없어서 자체 서명된 자리표시자로 대체된 것입니다. 거의 항상 경로 또는 마운트 문제입니다.
해결:
# 1. 파일이 컨테이너 내부의 예상 경로에 존재하는지 확인
docker compose exec traefik ls -l /certs
# 2. 동적 파일이 마운트되어 읽을 수 있는지 확인
docker compose exec traefik cat /dynamic/certs.yml
# 3. 실제 원인을 로그에서 확인
docker compose logs traefik | grep -i "certificate\|tls\|error"
certs.yml의 certFile/keyFile 경로는 호스트 경로가 아니라 컨테이너 경로(/certs/...)와 일치해야 합니다.
”unable to find certificate for domain” / 잘못된 인증서가 제공됨
원인: 어떤 인증서의 SAN도 요청된 호스트명과 일치하지 않고, defaultCertificate도 설정되어 있지 않습니다.
해결: 호스트명을 인증서에 추가하거나, stores.default 블록(2단계)에 defaultCertificate를 설정해 Traefik가 대체값을 갖도록 하세요.
router가 404를 반환하고, 인증서는 확인조차 되지 않음
원인: router 규칙이 일치하지 않거나, 서비스가 노출되지 않았습니다. 라우팅이 일치하기 전까지 TLS는 무관합니다.
해결:
# router가 존재하고 활성화되어 있는지 확인
docker compose exec traefik wget -qO- http://localhost:8080/api/http/routers 2>/dev/null | grep whoami
백엔드 컨테이너에 traefik.enable=true가 설정되어 있고, Host() 규칙이 테스트 중인 도메인과 일치하는지 확인하세요.
certs.yml 변경 사항이 적용되지 않음
원인: 파일 provider가 감시하고 있지 않거나, 재시작이 필요한 정적 설정을 편집했습니다.
해결: --providers.file.watch=true가 정적 설정에 있는지 확인하세요. 기억하세요. entrypoint/provider는 정적(재시작 필요)이고, router와 tls.certificates는 동적(핫 리로드)입니다.
클라이언트에서 “x509: certificate signed by unknown authority” 발생
원인: 체인 불완전 — certFile에 fullchain.pem 대신 리프 인증서만 들어 있는 cert.pem을 사용했습니다.
해결: certFile이 전체 체인을 가리키도록 하세요. 파일이 따로 분리되어 있다면 연결하세요(리프 먼저, 그다음 중간 인증서).
cat cert.pem intermediate.pem > fullchain.pem
자주 묻는 질문
Docker 라벨로 인증서 파일을 지정할 수 있나요?
아니요. 이것이 가장 흔한 오해입니다. 라벨은 TLS를 활성화하기 위해 traefik.http.routers.<name>.tls=true를 설정하지만, 인증서 파일은 파일 provider(tls.certificates)를 통해 동적 설정에서 선언해야 합니다. .pem 파일을 가리키는 router 라벨은 없습니다.
인증서가 있으면 router에 tls=true가 필요한가요?
네. tls.certificates에 인증서를 선언하면 인증서가 사용 가능해지지만, 실제로 HTTPS로 제공하려면 각 router에 여전히 tls=true가 있어야 합니다(또는 TLS가 활성화된 entrypoint에서 수신 대기해야 합니다). 인증서만 정의한다고 해서 router가 TLS로 전환되지는 않습니다.
자동 ACME와 사용자 지정 인증서를 함께 사용할 수 있나요?
네. Traefik는 일부 router에는 certificatesResolver를, 다른 router에는 파일 provider 인증서를 사용할 수 있습니다. 요청별로, SNI에 의한 명시적 tls.certificates 일치가 해당 호스트명에 대해 ACME보다 우선합니다.
Kubernetes에서는 인증서를 어디에 두나요?
파일 provider가 아닙니다. Kubernetes에서는 인증서를 TLS Secrets로 제공하고 IngressRoute/Ingress에서 이를 참조합니다. 이 가이드의 파일 provider 방식은 Docker 및 독립 실행형(비 K8s) 구성을 위한 것입니다.
Traefik가 내 사용자 지정 인증서를 자동으로 갱신하나요?
아니요. 자동 갱신은 Traefik가 ACME를 통해 직접 발급한 인증서에만 적용됩니다. 직접 보유한 인증서의 경우 파일을 교체해야 하지만, --providers.file.watch=true 덕분에 Traefik가 다운타임 없이 핫 리로드합니다. 위의 갱신하는 방법 절에서 설명한 대로입니다.
ECDSA/ECC 인증서에서도 작동하나요?
네. Traefik는 ECDSA 인증서를 RSA와 정확히 똑같이 제공합니다. certFile/keyFile 설정도 동일합니다. GetHTTPS는 더 작고 빠른 핸드셰이크를 위해 기본적으로 ECDSA P-256을 발급합니다.