devops

Let's Encrypt 인증서 자동 갱신 실패 대처법: DevOps 엔지니어를 위한 완벽 가이드

이 가이드는 Let's Encrypt 인증서 자동 갱신이 실패했을 때 유용한 해결 방법을 제공합니다. DevOps 엔지니어들이 직면하는 일반적인 문제를 다룹니다.

2025년 10월 08일
Let's Encrypt 인증서 자동 갱신 DevOps SSL 보안 문제 해결 가이드
6분 읽기

자동 갱신이 실패하는 이유부터 이해하자

Let's Encrypt(ACME) 인증서 자동 갱신은 “세팅만 해두면 알아서 돌아간다”는 편견과 달리, 실운영 환경에서는 여러 요소가 얽혀 쉽게 실패합니다. HTTP 트래픽 흐름, DNS 레코드, 방화벽, 리버스 프록시, 클라우드 로드 밸런서, 컨테이너 오케스트레이션(Kubernetes), 파일 권한과 디스크 용량, 시스템 타이머 등 어느 하나만 어긋나도 인증(Challenge) 검증이 깨집니다.

핵심 포인트:

  • ACME Challenge 유형: HTTP-01(80/tcp), DNS-01(DNS TXT), TLS-ALPN-01(443/tcp)
  • 기본 갱신 시점: 만료 30일 전부터 자동 시도
  • 대표 클라이언트: certbot, acme.sh, lego, win-acme 등
  • 실패 원인 범주: 네트워크/DNS, 웹서버/리버스 프록시, 클라이언트 설정/버전, 권한/디스크, 스케줄링, 레이트 리밋, 클라우드·K8s 통합

아래 가이드는 DevOps 엔지니어가 10분 내에 원인 범위를 좁히고, 근본 해결책을 적용할 수 있도록 구성되어 있습니다.


10분 만에 진단하는 빠른 체크리스트

  1. 만료 임박 확인
sudo certbot certificates
# 또는
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates -issuer -subject
  1. Dry-run으로 재현
sudo certbot renew --dry-run -v
# acme.sh 이용 시
acme.sh --renew -d example.com --dry-run --debug 2
  • 출력된 에러 메시지를 유형별로 분류: Connection refused/Timeout, 404/403, Unauthorized, DNS 문제, Rate limit 등
  1. 포트 및 접근성 점검
curl -I http://example.com/.well-known/acme-challenge/test
curl -I https://example.com
nc -vz example.com 80
nc -vz example.com 443
  1. DNS 레코드/CAA/DNSSEC 확인
dig +short A example.com
dig +short AAAA example.com
dig +short CAA example.com
dig TXT _acme-challenge.example.com
  1. 서비스/타이머/로그 확인
systemctl status certbot.timer
journalctl -u certbot -n 200 --no-pager
tail -n 200 /var/log/letsencrypt/letsencrypt.log
df -h
date && timedatectl
  1. 리버스 프록시/웹서버 설정 확인
  • Redirect 예외 설정 존재 여부
  • .well-known/acme-challenge 경로가 올바르게 서빙되는지
  • 컨테이너/로드 밸런서 헬스체크와 충돌 여부

실패 유형별 원인과 해결책

1) HTTP-01에서 “Connection refused/Timeout”

증상:

  • certbot 로그에 “Could not connect to” 또는 “Timeout during connect”
  • 외부에서 80 포트 접근 불가

원인:

  • 방화벽/보안그룹이 80 포트를 차단
  • NAT/포트포워딩 미설정
  • IPv6 AAAA 레코드는 있는데 80/443 서비스가 IPv6에 바인딩되지 않음
  • CDN/프록시/로드 밸런서 레벨에서 포트 차단

해결:

  • 반드시 80/tcp를 외부에서 접속 가능하게 하세요. HTTP-01은 80 포트 접근이 핵심입니다.
  • IPv6 비활성 혹은 불완전 구성 시 임시로 AAAA 레코드를 제거하거나, 서버가 v6로도 정상 listen하도록 설정합니다.
  • 방화벽 점검:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo iptables -S | grep -E '80|443'
  • 클라우드 보안그룹/네트워크 ACL 확인(AWS SG/NACL, GCP FW, Azure NSG 등)
  • 로드 밸런서(LB) 계층에 HTTP 리스너가 있는지 확인. LB에서 백엔드로 80 포트 전달 필요

대안:

  • 80 포트 개방이 불가한 조직 정책이라면 TLS-ALPN-01(443) 또는 DNS-01로 전환을 검토

2) HTTP-01에서 404/403/Unauthorized

증상:

원인:

  • webroot 경로 불일치
  • 리버스 프록시에서 특정 경로 rewrite/redirect로 인해 파일 조회 불가
  • 인증/ACL/기본 인증으로 경로 접근 제한
  • SaaS WAF에서 경로 차단

해결:

  • certbot 웹루트 플러그인 사용 시 실제 문서 루트와 일치해야 합니다:
sudo certbot certonly --webroot -w /var/www/html -d example.com
  • Nginx 예시: HTTPS 강제 리다이렉트를 적용하더라도 챌린지 경로는 예외 처리
server {
  listen 80;
  server_name example.com;
  location /.well-known/acme-challenge/ {
    root /var/www/html;
  }
  location / {
    return 301 https://$host$request_uri;
  }
}
  • Apache 예시:
Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/
<Directory "/var/www/html/.well-known/acme-challenge/">
    Options None
    AllowOverride None
    Require all granted
</Directory>
  • 인증 미들웨어/웹방화벽에서 해당 경로를 허용 예외로 추가

검증:

echo test | sudo tee /var/www/html/.well-known/acme-challenge/test
curl -I http://example.com/.well-known/acme-challenge/test

3) DNS 관련 오류(NXDOMAIN, 잘못된 IP, CAA, DNSSEC)

증상:

  • “No valid IP addresses found”, “DNS problem: NXDOMAIN”, “CAA forbids issuance”, “SERVFAIL”

원인:

  • A/AAAA 레코드가 잘못됨(스테이징/이전 서버 IP로 가리킴)
  • DNS 전파 지연 혹은 캐시된 레코드 충돌
  • CAA 레코드가 있고 Let’s Encrypt(“letsencrypt.org”) 발급 허용이 없음
  • DNSSEC 잘못 설정으로 검증 실패
  • 와일드카드(“*.example.com”)를 HTTP-01로 시도(지원 안 함)

해결:

  • A/AAAA 정확성 재확인, TTL을 일시적으로 낮추고 전파 확인
dig +short A example.com @8.8.8.8
dig +short AAAA example.com @1.1.1.1
  • CAA 허용 추가:
example.com.  CAA 0 issue "letsencrypt.org"
example.com.  CAA 0 iodef "mailto:[email protected]"
  • DNSSEC 오류는 DS 레코드/서명체인 재검증, 필요 시 일시 비활성화 후 수정
  • 와일드카드는 DNS-01로만 가능. DNS API 연동 자동화를 사용하세요

4) 리버스 프록시/로드 밸런서/CDN 환경 이슈

증상:

  • 클라이언트는 백엔드에서 챌린지 파일을 생성하지만, 외부 요청은 LB/CDN에서 차단되거나 다른 경로로 라우팅
  • Cloudflare 프록시(주황색 구름) 활성화 시 404/403

해결:

  • Cloudflare 프록시 사용 시: HTTP-01이 간섭받을 수 있으므로
    • DNS only(회색 구름)로 전환 후 발급/갱신, 또는
    • DNS-01(Cloudflare API)로 자동화:
acme.sh --issue -d example.com -d www.example.com --dns dns_cf
export CF_Token=... # 최소 권한의 API 토큰 사용
  • AWS ALB/ELB: LB에 80 리스너 추가, 특정 경로 /.well-known/acme-challenge 를 백엔드에 전달. 백엔드가 올바른 도큐먼트 루트를 서빙하도록 설정. 또는 Route53 + DNS-01
  • GCP HTTP(S) LB: HTTP-01은 글로벌 LB 레벨에서 특정 경로를 백엔드로 연결 필요. 복잡하면 DNS-01 권장
  • 프록시 체인(Nginx→App→Sidecar)에서는 챌린지 경로만 정적 제공하도록 최상단 프록시에서 처리

5) 권한/디스크/파일시스템 문제

증상:

  • “Permission denied: /etc/letsencrypt/…”
  • “Read-only file system”
  • “No space left on device”
  • SELinux/AppArmor로 인한 접근 차단

해결:

  • 디스크 용량 확보:
df -h
sudo rm -rf /var/log/letsencrypt/*.gz  # 신중히 정리
sudo journalctl --vacuum-time=7d
  • 권한 점검:
sudo chown -R root:root /etc/letsencrypt
sudo chmod -R 700 /etc/letsencrypt/live
sudo chmod -R 700 /etc/letsencrypt/archive
  • SELinux:
sudo ausearch -m avc -ts recent
sudo setsebool -P httpd_can_network_connect 1  # 필요 시에만
  • 컨테이너/읽기 전용 루트FS 환경에서는 인증서 저장 경로를 쓰기 가능한 볼륨으로 마운트

보안 노트:

  • 프라이빗 키 노출 방지. 백업 시 권한 유지, 저장소 암호화 적용
  • 키를 외부에 복사할 때는 최소 권한 채널 사용(SSH, KMS, Vault)

6) 스케줄러 문제: cron/systemd timer 미동작

증상:

  • 수동 갱신은 되는데 자동 갱신이 안 됨

확인 및 해결:

  • systemd timer 기반 설치(snap/apt certbot):
systemctl list-timers | grep certbot
systemctl status certbot.timer
journalctl -u certbot -n 100
  • 없는 경우 cron 설정 추가:
sudo crontab -e
# 매일 두 번 시도, 무작위 지연 추천
0 */12 * * * sleep $((RANDOM%1200)) && certbot renew -q
  • 타임싱크 문제 해결:
timedatectl
sudo systemctl enable --now systemd-timesyncd

7) 클라이언트/플러그인 문제

증상:

  • Nginx/Apache 플러그인 오류, 구버전 클라이언트 호환성 문제, ACME 엔드포인트 변경 미적용

해결:

  • certbot 최신화:
    • Snap 권장: sudo snap install --classic certbot
    • 기존 apt 버전 제거 후 snap로 이관 시 주의: 인증서/구성 백업
  • 플러그인 명시:
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot --apache -d example.com
sudo certbot certonly --standalone --preferred-challenges http
  • acme.sh/lego 등 사용 시도 중복 방지. 동일 도메인에 여러 클라이언트를 동시에 돌리지 말 것
  • 스테이징으로 먼저 검증:
sudo certbot renew --dry-run
sudo certbot --server https://acme-staging-v02.api.letsencrypt.org/directory ...

8) 리로드/데플로이 훅 문제: 갱신은 됐지만 서버는 옛 인증서 사용

증상:

  • certbot 로그에는 “Congratulations”가 있으나, 브라우저는 여전히 만료 인증서 표시

원인:

  • 웹서버/프록시가 인증서 파일을 핫리로드하지 않음

해결:

  • deploy-hook 활용:
sudo certbot renew --deploy-hook "systemctl reload nginx"
# 또는
sudo certbot renew --deploy-hook "apachectl graceful"
  • Nginx 구성 테스트 후 리로드:
nginx -t && systemctl reload nginx
  • HAProxy:
systemctl reload haproxy
  • 컨테이너: 사이드카/Init 컨테이너로 certs를 공유하면 SIGHUP 전달 혹은 rollout 재시작 로직 필요

9) 레이트 리밋(Too Many Requests) 대응

증상:

  • “too many certificates” 또는 “too many failed authorizations”

원인:

  • 반복 실패/재시도 과다
  • FQDN 분할 발급으로 리밋 초과

해결:

  • 스테이징 환경에서 먼저 해결 후 프로덕션 전환
  • SAN 통합 발급으로 인증서 수를 줄이기
  • 재시도는 backoff 적용
  • 상세 리밋: https://letsencrypt.org/docs/rate-limits

10) Wildcard와 DNS-01 자동화

포인트:

  • 와일드카드(*.example.com)는 DNS-01만 지원
  • DNS 제공자 API 토큰 기반 자동화 권장
  • 최소 권한 원칙 적용(해당 Zone TXT 레코드만)

예시(acme.sh + Cloudflare):

export CF_Token=cf_api_token_with_dns_edit
export CF_Account_ID=...
acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf
acme.sh --install-cert -d example.com \
  --key-file       /etc/letsencrypt/live/example.com/privkey.pem \
  --fullchain-file /etc/letsencrypt/live/example.com/fullchain.pem \
  --reloadcmd     "systemctl reload nginx"

Kubernetes에서의 자동 갱신 실패: cert-manager 중심 진단

증상:

  • Ingress는 존재하지만 인증서가 Pending/Invalid
  • cert-manager 관련 리소스에서 챌린지 실패

체크리스트:

kubectl get issuer,clusterissuer,certificate,order,challenge -A
kubectl describe certificate <name> -n <ns>
kubectl describe challenge <name> -n <ns>
kubectl logs -n cert-manager deploy/cert-manager -f

자주 발생하는 원인과 해결:

  • HTTP-01 solver가 Ingress 경로를 제대로 노출하지 못함
    • IngressClass, annotations, pathType 확인
    • Nginx/Traefik LB가 80 포트로 외부 노출 되어야 함
  • Cloud(L7 LB)에서 /.well-known 경로 라우팅 누락
    • 별도 Ingress로 챌린지 경로를 우선 매칭
  • DNS-01 사용 시 DNS 권한/프로파게이션 문제
    • ClusterIssuer에 DNS provider secret/token 정확성 확인
  • 성공 후에도 Ingress가 오래된 시크릿을 참조
    • cert-manager가 관리하는 secretName과 Ingress TLS 섹션 일치 확인

권장:

  • 프로덕션 이전 스테이징 Issuer로 사전 검증
  • Prometheus metrics(cert-manager)와 Alertmanager로 만료 임박 경보 구성

로드 밸런서/다중 서버 환경에서의 베스트 프랙티스

  • 단일 ACME 클라이언트 책임 원칙: 다중 노드가 동일 도메인을 동시에 발급하지 않도록 락/리더 선출(예: keepalived VIP에서만 certbot 실행)
  • 공유 스토리지(NFS/Gluster)로 /etc/letsencrypt 공유 시 권한/락 파일 경쟁 주의
  • LB 종단에서 TLS 종료 시:
    • LB가 자체적으로 ACME를 지원하면 그 기능 활용(예: Traefik, Caddy, 일부 클라우드 LB 어플라이언스)
    • 그렇지 않다면 DNS-01로 중앙 발급 → LB에 배포 자동화(Ansible, GitOps, CI/CD)
  • IPv6 이슈를 늘 병행 점검: AAAA가 설정되어 있으면 챌린지도 v6로 검증 시도됨

문제 상황별 실제 로그와 해석

  1. Connection 에러:
Timeout during connect (likely firewall problem)
  • 외부 접근 불가. 네트워크/방화벽 먼저
  1. Unauthorized/Invalid response:
The key authorization file from the server did not match this challenge
Expected: XYZ
Received: 404 Not Found
  • webroot/경로 잘못. 리버스 프록시 리라이트 확인
  1. CAA:
CAA record for example.com prevents issuance
  • CAA 레코드에 letsencrypt.org 허용 추가
  1. 권한/디스크:
[Errno 13] Permission denied: '/etc/letsencrypt/...'
  • 파일 시스템 권한/SELinux/읽기전용
  1. Rate limit:
too many failed authorizations recently
  • 스테이징에서 먼저 해결하고 쿨다운 후 시도

재발 방지: 운영 레벨 예방 전략

  • 모니터링
    • 인증서 만료 30/14/7/3/1일 전에 경보
    • 예: blackbox_exporter + Prometheus
probe_ssl_earliest_cert_expiry - time() < 30*24*3600
  • 사전 리허설
    • 분기별로 스테이징 엔드포인트로 드라이런
    • 주요 변경(도메인 이동, LB 교체, CDN 도입) 시 즉시 검증
  • 구성 표준화
    • /.well-known/acme-challenge 경로 예외 템플릿화
    • 공통 Ansible Role/Terraform module로 재사용
  • 비상 대응 룬북(runbook) 준비
    • 연락 포인트, DNS/API 토큰 경로, 수동 갱신 절차, 롤백/우회 전략 문서화
  • 키/인증서 백업과 회수
    • 주기적 백업(암호화), 퇴사·프로젝트 종료 시 토큰/키 회수 절차

실전 해결 예시 시나리오

시나리오 A) Cloudflare 프록시 사용 중 갱신 실패

  • 현상: 404/403, “Invalid response”
  • 조치:
    1. DNS only로 전환
    2. certbot renew --dry-run 성공 확인
    3. 프로덕션 발급 완료 후 프록시 재활성 또는 DNS-01 자동화로 전환

시나리오 B) IPv6 AAAA만 잘못 구성

  • 현상: 지역에 따라 간헐적 실패, 일부 네트워크에서만 접속 불가
  • 조치:
    1. dig AAAA example.com 확인
    2. 서버가 v6에서 80/443 listen하는지 점검
    3. 임시로 AAAA 제거하거나 v6 설정 보완

시나리오 C) Nginx 리다이렉트로 챌린지 차단

  • 현상: 301/302 후 앱으로 빠져 404
  • 조치: Nginx에 명시적 location 예외 추가(앞서 예시 참조)

시나리오 D) certbot 갱신 후 Nginx 미리로드

  • 현상: 파일은 새로움, 서비스는 구버전 유지
  • 조치: deploy-hook로 자동 reload 연결

스냅샷: 명령어 모음

  • 인증서 상태
sudo certbot certificates
  • 드라이런
sudo certbot renew --dry-run -v
  • 특정 도메인만 재발급
sudo certbot certonly --webroot -w /var/www/html -d example.com -d www.example.com
  • 스탠드얼론 모드(80 포트를 직접 바인딩, 서비스 중단 주의)
sudo systemctl stop nginx
sudo certbot certonly --standalone --preferred-challenges http -d example.com
sudo systemctl start nginx
  • 오리진 접근성 확인
curl -I http://example.com/.well-known/acme-challenge/test
  • 서버 리로드
sudo systemctl reload nginx
sudo apachectl graceful
  • DNS/C AA
dig +short CAA example.com

보안과 안정성을 동시에: 실무 팁

  • 최소 권한 토큰: DNS-01 자동화 시 Zone 편집 권한만 부여
  • 키 순환 정책: 장기 운영 시 키 재생성 주기 설정(예: 연 1~2회)
  • 비밀 관리: Vault/Secrets Manager에 API 키 저장, 감사 로그 활성화
  • HSTS/H2/QUIC 등 보안 기능은 유지하되, 챌린지 경로에만 예외 적용
  • OCSP Stapling, 중간 체인(fullchain) 정확히 적용
  • 백업/DR: /etc/letsencrypt + 웹서버 TLS 설정 스냅샷 보관

표준 운영 Runbook 템플릿

  1. 알람 수신(만료 14일 전)
  • 서비스 영향도 평가, 변경 금지 기간 확인
  1. 드라이런
sudo certbot renew --dry-run -v
  1. 오류 유형 식별
  • 네트워크/DNS/경로/권한/레이트 리밋 범주화
  1. 해결책 적용
  • 방화벽/DNS/리버스 프록시 수정
  • 클라이언트 업데이트
  • deploy-hook 추가
  1. 실제 갱신
sudo certbot renew -v
  1. 서비스 리로드 및 검증
nginx -t && systemctl reload nginx
curl -I https://example.com
echo | openssl s_client -connect example.com:443 2>/dev/null | openssl x509 -noout -dates
  1. 사후 조치
  • 모니터링/알람 튜닝
  • 변경사항 문서화, IaC 반영

결론: 문제를 “한 번에” 해결하는 사고 방식

자동 갱신 실패는 단일 원인이라기보다, 레이어 간 연결 고리가 하나씩 끊어지며 발생합니다. DevOps 엔지니어에게 가장 효율적인 접근은 다음 세 가지입니다.

  • 관찰 가능성 강화: 로그, 지표, 알람으로 조기에 징후 포착
  • 표준화된 패턴: 챌린지 경로 예외, DNS-01 자동화, deploy-hook 리로드 등 재사용 가능한 운영 패턴
  • 단계적 진단: Dry-run → 네트워크/DNS → 웹서버/프록시 → 클라이언트/권한 → 레이트 리밋 순으로 범위 축소

이 가이드의 체크리스트와 예시 설정을 바탕으로, 갱신 실패를 빠르게 복구하고 재발을 예방할 수 있습니다. 중요한 것은 “문제를 재현해 명확히 이해하고, 자동화로 되돌려 놓는 것”입니다. 그러면 다음 만료 주기에서도 시스템은 조용히 잘 돌아갈 것입니다.

개발 파트너가 필요하신가요?

DevTeam은 MVP·웹·앱·AI 개발을 설계부터 배포·운영까지 한 팀이 책임집니다.

이 글 공유하기
Twitter LinkedIn
최종 수정: 2025년 10월 08일

devops 관련 글

더 많은 스타트업 노하우와 비즈니스 인사이트를 확인해보세요

환경 변수 누락 및 권한 문제 해결: 배포 오류 및 롤백 절차 가...

환경 변수와 권한 문제로 인한 배포 오작동을 방지하기 위한 실용적인 가이드

전문가 도움이 필요하신가요?

스타트업과 비즈니스 성장을 위한 전문 컨설팅을 받아보세요.
확장 가능하고 비즈니스 성과로 이어지는 솔루션을 구축할 수 있도록 도와드립니다.