지난 글에서 인증서 체인과 폐기 검증을 다뤘는데요, 거기서 빠진 게 하나 있습니다. "그래서 그 인증서, 누가 어떻게 발급해 주냐?" 예전엔 CA에 돈 내고, 양식 채워서 신청하고, 며칠 기다려서 받았어요. 갱신할 때는 또 그 과정을 반복했고요. 저도 한 번씩 만료일을 놓쳐서 새벽에 식은땀 흘리며 갱신한 적이 있습니다.
이걸 다 자동화해서 무료로 풀어버린 게 Let's Encrypt입니다. 그리고 그 뒤에는 ACME라는 잘 만든 프로토콜이 있어요. 오늘은 Let's Encrypt가 어떻게 도메인 소유를 검증하고, 인증서를 발급하고, 자동으로 갱신하는지 그 동작 원리를 풀어보겠습니다.
개념 / 원리
Let's Encrypt가 가져온 변화
Let's Encrypt는 ISRG(Internet Security Research Group)가 운영하는 무료 자동 발급 CA입니다. 세 가지가 달랐어요.
- 무료: 도메인 검증(DV) 인증서를 공짜로.
- 자동: 사람이 양식 채울 필요 없이 클라이언트가 알아서 발급.
- 단명(90일): 일부러 만료를 짧게 잡아서 자동 갱신을 강제. 키 노출 시 피해 범위도 줄어듦.
ACME 프로토콜
ACME(Automated Certificate Management Environment)는 "CA와 클라이언트가 인증서 발급·갱신·폐기를 자동으로 주고받는" 표준 프로토콜입니다 (RFC 8555).
핵심은 "이 도메인이 정말 너 거 맞아?"를 자동으로 증명하는 챌린지(Challenge) 메커니즘입니다. 세 종류가 있어요.
| 챌린지 | 동작 | 언제 쓰나 |
|---|---|---|
| HTTP-01 | http://도메인/.well-known/acme-challenge/<토큰> 경로에 특정 값 응답 |
가장 흔함. 80 포트가 열려 있을 때 |
| DNS-01 | _acme-challenge.도메인 TXT 레코드에 특정 값 등록 |
와일드카드 인증서, 80 포트가 막힌 환경 |
| TLS-ALPN-01 | 443 포트에서 특수 TLS 핸드셰이크로 토큰 응답 | 80을 못 쓰고 DNS API도 못 쓸 때 |
CA가 챌린지를 통과한 걸 확인하면, 인증서를 발급해 줍니다. 클라이언트는 그걸 받아 서버에 깔고 reload만 해 주면 끝.
발급 흐름 한 장으로
[클라이언트] [Let's Encrypt CA]
│ 1. 계정 등록 (한 번) │
├────────────────────────────────────────►│
│ 2. 인증서 주문 (도메인 명시) │
├────────────────────────────────────────►│
│ 3. 챌린지 받기 (HTTP-01/DNS-01 토큰) │
│◄────────────────────────────────────────┤
│ 4. 챌린지 응답 준비 (파일/DNS 셋업) │
│ 5. 검증 요청 │
├────────────────────────────────────────►│
│ 6. CA가 직접 도메인에 접속해 확인 │
│ 7. 통과 → CSR 제출 │
├────────────────────────────────────────►│
│ 8. 인증서 발급 │
│◄────────────────────────────────────────┤
실전 예제
1) certbot으로 가장 흔한 발급 (HTTP-01)
certbot이 사실상 표준 클라이언트입니다. nginx 환경에서 한 줄.
sudo certbot --nginx -d example.com -d www.example.com \
--agree-tos -m admin@example.com --non-interactive
- certbot이 nginx 설정을 잠깐 바꿔서 80 포트로 챌린지 응답을 띄움.
- Let's Encrypt가 그 경로에 접속해 검증.
- 발급 성공 시
/etc/letsencrypt/live/example.com/에 인증서가 떨어지고, nginx 설정에 SSL 디렉티브가 자동 추가됨.
2) 와일드카드는 DNS-01
*.example.com 같은 와일드카드 인증서는 HTTP-01로 못 받습니다. DNS-01만 가능해요.
sudo certbot certonly --manual --preferred-challenges dns \
-d "*.example.com" -d example.com \
--agree-tos -m admin@example.com
certbot이 "이 값을 _acme-challenge.example.com TXT 레코드에 넣고 Enter 누르세요" 하고 멈춥니다. DNS 콘솔에서 TXT 레코드 추가 후 진행. Cloudflare나 Route53 같은 DNS API가 있으면 --dns-cloudflare, --dns-route53 플러그인으로 완전 자동화도 됩니다.
3) 자동 갱신
certbot은 설치 시 보통 systemd timer나 cron을 같이 깔아둡니다. 직접 만든다면:
# /etc/cron.d/certbot
0 3 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"
certbot renew는 만료 30일 이내인 것만 실제 갱신을 시도합니다. 매일 돌려도 안전해요. --post-hook으로 reload를 꼭 걸어둬야 갱신된 인증서가 실제로 적용됩니다.
4) 갱신이 잘 되는지 테스트
sudo certbot renew --dry-run
실제 발급 없이 전체 흐름을 시뮬레이션합니다. 운영 전에 꼭 한 번 돌려보세요.
자주 하는 실수
- Rate limit 초과: Let's Encrypt에는 도메인당 주간 발급 한도가 있습니다. 자동화 스크립트가 폭주하면 며칠 정지당해요. 테스트는 반드시 staging 환경(
--staging플래그)에서. staging은 한도가 훨씬 큽니다. - 갱신은 됐는데 nginx reload를 안 함: 파일은 새 인증서인데 nginx는 옛 인증서를 메모리에 들고 있어서 만료 후 사이트가 다운됩니다.
--post-hook은 필수. - HTTP-01인데 80 포트가 막혀 있음: 방화벽이 443만 열어두고 80을 막는 경우가 있는데, 그러면 HTTP-01 검증이 실패합니다. 80을 열거나 DNS-01로 가야 해요.
- 와일드카드를 HTTP-01로 시도: 안 됩니다. 와일드카드는 무조건 DNS-01.
- 인증서 90일 만료를 "여유 있다"고 생각: 90일이 길어 보이지만, 자동화 없이 사람이 챙기면 100% 까먹습니다. 자동 갱신 + 모니터링은 세트.
- 갱신 파이프라인을 안 모니터링:
certbot renew가 조용히 실패하고 있어도 만료 직전까지 모를 수 있습니다. 만료 14일 전 알람과 함께renew --dry-run을 주기적으로 돌려 두세요.
저는 예전에 DNS-01로 와일드카드를 발급받았는데, DNS 전파가 느린 회사 DNS 서버 때문에 검증이 자꾸 타임아웃 났습니다. --dns-propagation-seconds 옵션을 늘리거나, 차라리 빠른 외부 DNS(예: Cloudflare) 위임으로 가는 게 답이었어요.
마무리
정리하면 세 줄입니다.
- Let's Encrypt는 무료·자동·90일 단명 인증서를 ACME 프로토콜로 발급한다.
- 챌린지는 HTTP-01(가장 흔함), DNS-01(와일드카드 필수), TLS-ALPN-01 세 가지.
- 운영에서는
certbot renew+--post-hook reload+ 갱신 모니터링 세트만 잘 굴리면 인증서 문제는 끝난다.
'보안' 카테고리의 다른 글
| CSRF와 SameSite 쿠키 - 토큰 없이도 막을 수 있을까 (0) | 2026.05.25 |
|---|---|
| [보안] HSTS와 보안 헤더 - HTTPS를 정말 강제하는 방법 (0) | 2026.05.22 |
| [보안] 인증서 검증의 전 과정 - 체인 추적부터 OCSP·CRL 폐기 확인까지 (0) | 2026.05.20 |
| [보안] TLS 키 교환의 비밀 - 대칭키와 비대칭키는 어떻게 협력할까 (0) | 2026.05.19 |
| [보안] HTTPS와 TLS 핸드셰이크 - 브라우저 자물쇠 안에서 벌어지는 일 (0) | 2026.05.18 |