SSL/TLS 인증서 만료가 왜 위험한가
인증서 한 장이 만료되면 단순히 브라우저 경고가 뜨는 수준을 넘어, 매출 손실·브랜드 신뢰 하락·SEO 평가 하락·API 통신 중단·모바일 앱 장애까지 광범위한 영향을 초래합니다. 특히 HSTS를 사용하는 사이트는 인증서가 만료되면 사용자가 보안 예외를 통해 접속할 수 없어 실질적인 서비스 두절로 이어집니다. 사내 mTLS 환경이나 모바일 앱의 인증서/키 핀닝을 적용한 경우에는 더 큰 리스크가 발생합니다.
이 글에서는 “만료를 예방하는 설계”와 “자동 갱신 실패 시 즉시 복구하는 실행 계획”을 함께 다룹니다. 예방 관점에서는 재고 관리, 자동화, 모니터링, 거버넌스를, 대응 관점에서는 체크리스트·트러블슈팅·수동 복구·사후 개선까지 실무에 바로 적용할 수 있는 내용을 제시합니다.
SSL/TLS 인증서 기본 이해
인증서 종류와 유효기간
- 발급 주체: 공인 CA(예: Let’s Encrypt, DigiCert) 또는 사설 PKI(AD CS, HashiCorp Vault 등)
- 검증 수준: DV(도메인 검증), OV(조직 검증), EV(확장 검증)
- 커버리지: 단일 도메인, 와일드카드, SAN(Subject Alternative Name)
- 유효기간:
- 공용 웹 인증서는 CA/B Forum 규격에 따라 최대 398일로 제한
- Let’s Encrypt 등은 기본 90일로 운영(자동화 전제)
- 내부 mTLS는 보통 짧은 수명(일~주 단위)으로 자동 회전
- 체인: 서버 인증서 → 중간(Intermediate) CA → 루트(Root) CA
- 체인 누락 또는 중간 인증서 만료도 장애를 유발합니다.
키와 알고리즘
- RSA 2048/3072 또는 ECDSA(P-256, P-384) 권장
- ECDSA는 성능과 보안 밸런스가 좋아 권장되며, 레거시 클라이언트를 위해 RSA/ECDSA 이중 제공을 고려할 수 있습니다.
- 키 보관은 KMS/HSM, 권한 최소화, 접근 로깅을 기본 원칙으로 합니다.
만료 예방 전략: 조직적·기술적 설계
1) 전체 인증서 인벤토리와 소유자 지정
- 어떤 도메인에 어떤 인증서가 어디에 배포되어 있는지 “한눈에” 파악 가능한 목록이 필수입니다.
- 온프레미스 웹서버, 로드밸런서, CDN, API 게이트웨이, 메시/서비스, 모바일 백엔드, 사내 시스템 등 전 범위를 포함
- 메타데이터를 반드시 포함
- 만료일, 발급 CA, SAN 목록, 키 알고리즘/크기, 배포 대상, 담당 팀/소유자, 라벨/태그(중요도, 환경, 규정 준수)
- 구현 방법
- CLM(Certificate Lifecycle Management) 솔루션 또는 자체 CMDB
- 클라우드 네이티브: AWS Certificate Manager(ACM), Azure Key Vault, GCP Certificate Manager와 태그 연동
- 정기 스캔(예: zgrab, sslscan, OpenSSL)과 API 동기화로 누락 방지
2) 표준화와 자동화
- 발급/갱신/배포를 표준 파이프라인으로 정의
- ACME 기반 자동화(예: certbot, acme.sh, lego)
- Kubernetes는 cert-manager, Istio/Linkerd의 mTLS 회전
- 사설 PKI는 HashiCorp Vault PKI, Smallstep CA, AD CS 자동등록 활용
- 배포는 무중단 롤링 방식으로
- NGINX/HAProxy/Apache 핫 리로드
- 로드밸런서/프록시 구성 변경 자동화(Infra as Code)
3) 갱신 윈도우와 분산
- 만료 30/15/7/3/1일 전 다단계 알림
- 인증서 유효기간이 짧을수록 자동화를 전제로 분산 갱신
- “월말 몰림” 방지: Jitter(무작위 지연)로 분산 스케줄링
- 중복성 확보
- 가능한 경우 주 CA와 보조 CA 이중화 설계
- 오버랩(Overlap) 기간을 두고 새 인증서를 미리 배포
4) 보안 거버넌스
- 최소 권한 원칙으로 DNS·클라우드·비밀정보(API 토큰) 접근 제어
- 토큰/키 주기적 회전, 감사로그/감사 증적 보관
- HSTS Preload(엄격모드)는 자동화와 모니터링이 성숙한 뒤 도입
모니터링과 알림: 장애를 “시간으로” 예방
다층 모니터링이 핵심입니다.
1) 인증서 만료 지표 수집
- 엔드포인트 실제 핸드셰이크를 통한 만료일 측정
- 체인 유효성, SAN 매칭, OCSP Stapling 상태까지 확인
- 내부 mTLS 포트(예: 9443), 메시 사이드카 포트도 포함
예: blackbox_exporter로 TLS 만료 모니터링
modules:
https_2xx:
prober: http
timeout: 10s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2"]
tls_config:
insecure_skip_verify: false
Alert 예시(Prometheus):
- alert: TLSCertificateExpiringSoon
expr: (probe_ssl_earliest_cert_expiry - time()) < 86400 * 14
for: 10m
labels:
severity: warning
annotations:
summary: "TLS 인증서 만료 임박 (14일 이내)"
description: "{{ $labels.instance }} 인증서 만료까지 {{ (probe_ssl_earliest_cert_expiry - time()) / 86400 }}일"
2) CA/플랫폼 상태 모니터링
- Let’s Encrypt 상태 페이지, DNS 제공자 상태, 클라우드 서비스 상태 구독
- 내부: cert-manager 컨트롤러, Vault PKI, AD CS 서비스 헬스
3) 알림 라우팅과 소유자 매핑
- 슬랙/이메일/Teams/온콜로 라우팅
- 인증서별 소유자/팀 정보로 직접 연결
- “무응답 시” 에스컬레이션 규칙(예: 4시간 → 팀장, 24시간 → SRE)
자동 갱신 설계 패턴
ACME 챌린지 방식 선택
- HTTP-01: 80포트에서 .well-known/acme-challenge 경로 응답
- 장점: 간단, 널리 지원
- 주의: 프록시/리다이렉트/WAF/캐시 영향
- DNS-01: _acme-challenge.<도메인> TXT 레코드
- 장점: 와일드카드, 내부/비공개 호스트에도 적용 가능
- 주의: DNS API 권한, 전파 지연, 토큰 보안
- TLS-ALPN-01: 443 포트 ALPN(acme-tls/1)로 검증
- 장점: 80포트 필요 없음
- 주의: 일부 CDN/프록시와 호환성 제한
리버스 프록시/로드밸런서 환경
- L7 프록시(NGINX/HAProxy)에서 챌린지 우회 처리
- /.well-known/acme-challenge 요청을 로컬 챌린지 서버로 라우팅
- CDN 사용 시
- HTTP-01은 CDN 캐시 우회를 보장하거나 DNS-01 전환
- Cloudflare Proxy(오렌지 클라우드) 사용 시 TLS-ALPN-01 불가 → DNS-01 권장
NGINX 예시(HTTP-01 우회):
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/acme-challenge;
}
Kubernetes: cert-manager
- ClusterIssuer/Issuer 자원 정의로 ACME 자동화
- Ingress에 주석으로 인증서 자동 연계
ClusterIssuer 예시(DNS-01 + Route53):
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns
spec:
acme:
email: [email protected]
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-account-key
solvers:
- dns01:
route53:
region: us-east-1
hostedZoneID: Z123456789
accessKeyID: YOUR_AWS_ACCESS_KEY_ID
secretAccessKeySecretRef:
name: route53-credentials
key: secret-access-key
Ingress 예시:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-dns"
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
Windows/IIS
- win-acme(wacs) 또는 Certify The Web 사용
- SNI 바인딩 자동 업데이트, 스케줄러 작업 확인
- 스토어 권한(LOCAL_MACHINE\My)과 포트 바인딩 권한 점검
사설 PKI와 짧은 수명
- HashiCorp Vault PKI
- Role 정의로 도메인/SAN/TTL 제한, 자동 회전
- 서비스 메시/워크로드 아이덴티티
- SPIFFE/SPIRE로 초단기(시간 단위) X.509 mTLS 부여
흔한 자동 갱신 실패 원인과 해결 가이드
공통 체크리스트
- 어떤 도메인/SAN이 장애인가?
- 실제로 만료된 것은 서버 인증서인가, 중간 인증서인가?
- 체인 구성은 올바른가? AIA에서 중간 CA가 제공되는가?
- 서버 시간/타임존/NTP 동기화 상태는 정상인가?
- 최근 변경(방화벽, WAF, DNS 권한, 토큰 회전)이 있었는가?
OpenSSL 빠른 확인:
# 원격 인증서와 체인 확인
openssl s_client -connect app.example.com:443 -servername app.example.com -showcerts </dev/null
# 만료일 확인
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null \
| openssl x509 -noout -enddate -issuer -subject
HTTP-01 실패
- 증상: “Invalid response from http://example.com/.well-known/acme-challenge/...”
- 원인/해결:
- 80포트 차단 → 방화벽/보안그룹 열기
- HTTPS로 강제 리다이렉트 → 챌린지 경로만 예외 처리
- CDN 캐싱/압축 → 해당 경로 캐시 무효화 또는 “캐시 패스 스루”
- 리버스 프록시 경로 설정 누락 → 위 NGINX location 추가
- ALB/Ingress에서 정적 파일 제공 안됨 → certbot webroot/standalone 모드 정확히 설정
DNS-01 실패
- 증상: “TXT record not found at _acme-challenge.example.com”
- 원인/해결:
- 잘못된 Zone에 레코드 생성 → 권한 있는 호스팅 존 확인
- 전파 지연 → TTL 단축, 충분한 대기
- CNAME 위임 누락 → 멀티서브도메인 위임 시 CNAME 정확히 설정
- DNS API 토큰 권한 부족 → zone.dns:edit 최소 권한 토큰 발급
- DNSSEC 오구성 → DS 레코드/서명 상태 점검
acme.sh + Cloudflare 예시:
export CF_Token="cf-api-token-with-zone-dns-edit"
acme.sh --issue -d app.example.com --dns dns_cf --keylength ec-256
acme.sh --install-cert -d app.example.com \
--fullchain-file /etc/nginx/certs/app.fullchain.pem \
--key-file /etc/nginx/certs/app.key \
--reloadcmd "nginx -s reload"
TLS-ALPN-01 주의
- 일부 CDN/프록시에서 ALPN 커스텀 핸드셰이크 불가
- 오렌지 클라우드(Cloudflare Proxy) 활성화 상태에서는 DNS-01 사용 권장
로드밸런서/프록시 배포 실패
- 인증서 갱신은 되었지만 배포/리로드가 실패
- NGINX/Apache/HAProxy 리로드 권한/명령어 경로 점검
- ALB/CloudFront에 인증서 교체 API 호출 실패 → IAM 권한/리전(us-east-1 for CloudFront) 확인
- 무중단 리로드 설정 누락 → 테스트 후 graceful reload 적용
시간/OCSP/체인 이슈
- 서버 시간 불일치 → NTP 동기화
- 중간 CA 만료/누락 → 올바른 fullchain.pem 구성
- OCSP Must-Staple 사용 중 스테이플 실패 → OCSP 캐시/네트워크/파이어월 확인 또는 일시적 해제 검토
이미 만료되었거나 임박할 때: 즉각 대응 Runbook
1) 영향 범위 파악과 커뮤니케이션
- 장애 도메인/서비스 목록화, 대체 경로(서브도메인, 백업 엔드포인트) 안내
- 상태 페이지/공지로 투명성 확보, 주요 고객 직접 커버리지
2) 수동 갱신(단기 복구)
- ACME 스테이징으로 먼저 연습 후 프로덕션 발급
- Let’s Encrypt는 레이트 리밋이 있으므로 공식 문서로 즉시 확인
Certbot 예시:
# 갱신 시뮬레이션
certbot renew --dry-run
# 특정 도메인 강제 재발급
certbot certonly --webroot -w /var/www/acme-challenge -d app.example.com --force-renewal
Windows(IIS) win-acme 예시:
wacs.exe --target iis --host app.example.com --installation iis --store centralssl
AWS 환경:
- ACM으로 발급받아 ALB/CloudFront에 연결 시 자동 갱신(ACM 발급분)은 수월
- 외부 발급분을 가져온 경우 만료 전 재수입(import) 자동화 필요
3) 대체 루트/보조 CA 활용
- 주 CA 장애/레이트 리밋 초과 시, 신뢰 가능한 보조 CA로 동일 SAN 인증서 발급
- 체인 변경에 따른 호환성 테스트(특히 임베디드/레거시 클라이언트)
4) 배포와 검증
- 무중단 리로드로 교체, 지역/PoP별 전파 확인
- 외부에서 다중 지역/네트워크로 실측(브라우저·모바일·CLI)
5) HSTS와 핀닝 고려
- HSTS preload 등록 상태면 만료 시 우회 불가
- 운영 성숙도 확보 전 preload 지양
- max-age를 운영 중 점진적으로 늘리는 전략
- 앱/SDK에서 인증서/키 핀닝 적용 중이면 사전 로테이션 시나리오 필수
- 새 키의 백업핀(BACKUP PIN) 사전 포함
운영 모범 사례 체크리스트
- 인벤토리
- 모든 도메인/SAN/체인/배포대상/소유자 목록화
- 자동화
- ACME 파이프라인, Infra as Code, 무중단 리로드
- systemd timer 또는 스케줄러로 주기 실행
systemd 예시:
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renew
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Twice daily Certbot Renew
[Timer]
OnCalendar=*-*-* 00,12:00:00
Persistent=true
[Install]
WantedBy=timers.target
- 모니터링/알림
- 30/15/7/3/1일 전 다단계 경보, 온콜 에스컬레이션
- 실제 핸드셰이크 기반 만료·체인·SAN 검증
- 보안/권한
- DNS/API 최소 권한 토큰, 저장 시 비밀관리(Secrets Manager, Vault)
- 비밀 주기 회전과 접근 감사
- 키/알고리즘
- ECDSA 우선, RSA 병행(필요 시)
- 키/인증서 오버랩 기간 내 미리 배포
- 레이트 리밋/스테이징
- 대량 변경 전 Let’s Encrypt 스테이징으로 검증
- 프로덕션 전환 시 배포 분할
- 문서화/훈련
- Runbook, 다이어그램, 재현 절차, 연 1회 이상 모의훈련(게임데이)
실제 시나리오와 해결
시나리오 1: DNS API 토큰 회전으로 DNS-01 실패
- 현상: cert-manager가 _acme-challenge TXT 레코드 생성을 시도했으나 권한 오류로 실패, 만료 임박 알림
- 조치:
- DNS 제공자에서 zone.dns:edit 최소 권한 토큰 재발급(해당 호스팅 존만)
- Kubernetes Secret 갱신, cert-manager 재시도
- 즉시성이 필요하면 수동으로 TXT 레코드 생성 후 발급
- 토큰 만료 주기 문서화, 회전 자동화 스케줄 설정
- 예방:
- 토큰 만료/회전 알림, 읽기/쓰기 분리, 덜 민감한 서브도메인으로 검증 위임(CNAME)
시나리오 2: CDN 캐싱으로 HTTP-01 응답 불일치
- 현상: /.well-known/acme-challenge 경로가 CDN 캐시에 잡혀 잘못된 토큰 제공
- 조치:
- CDN 규칙에서 해당 경로 캐시 무효화/미적용
- 오리진에서 정확한 파일 제공 확인
- 장기적으로 DNS-01 전환
- 예방:
- 챌린지 경로는 캐시 제외, 또는 백도어 오리진 경로 보장
시나리오 3: 중간 인증서 만료
- 현상: 일부 클라이언트에서만 오류 발생(체인 다운로드 차이)
- 조치:
- 최신 중간 체인을 포함한 fullchain으로 교체
- 서버/로드밸런서가 중간 체인을 제대로 제공하는지 확인
- 예방:
- 체인 만료도 모니터링, CA 체인 변경 공지 구독
현실적인 팁과 실무 예시
NGINX에서 RSA/ECDSA 이중 인증서 제공
ssl_certificate /etc/nginx/certs/app-ecdsa.fullchain.pem;
ssl_certificate_key /etc/nginx/certs/app-ecdsa.key;
ssl_certificate /etc/nginx/certs/app-rsa.fullchain.pem;
ssl_certificate_key /etc/nginx/certs/app-rsa.key;
ssl_ecdh_curve X25519:P-256;
ssl_prefer_server_ciphers on;
- 클라이언트가 ECDSA를 지원하면 ECDSA, 아니면 RSA로 협상됩니다.
만료 스크립트 간편 체크(크론)
#!/usr/bin/env bash
set -euo pipefail
DOMAIN="app.example.com"
THRESHOLD_DAYS=14
EXPIRY_EPOCH=$(echo | openssl s_client -connect "$DOMAIN:443" -servername "$DOMAIN" 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2 | xargs -I{} date -d "{}" +%s)
NOW=$(date +%s)
REMAIN=$(( (EXPIRY_EPOCH - NOW) / 86400 ))
if [ "$REMAIN" -lt "$THRESHOLD_DAYS" ]; then
echo "경고: $DOMAIN 인증서 만료까지 ${REMAIN}일 남음" >&2
exit 2
fi
cert-manager 트러블슈팅 포인트
- Challenge/Order 리소스 상태 확인: kubectl describe certificate, challenge
- acme-http solver 파드/서비스 접근 가능 여부(NetworkPolicy)
- Ingress 클래스/어노테이션 불일치
- 로그에서 “presented challenge not valid” → 오리진 경로/헤더/리다이렉트 조사
흔히 놓치는 요소
- 모바일/임베디드/레거시 클라이언트
- 특정 루트 CA 미신뢰, 체인 길이/서명 알고리즘 제한
- 체인 변경 시 호환성 회귀 테스트 필수
- HSTS Preload
- 프로세스 성숙 전 도입 지양, max-age 점진 조정
- 인증서 핀닝
- HPKP는 폐기되었고, 앱 단의 SPKI 핀닝은 백업핀 전략과 함께 신중 적용
- 멀티 리전/멀티 CDN
- 동일 시점 배포 실패 시 일부 지역만 장애 → 전파 완료까지 상태 관측 필요
- 대규모 갱신 스톰
- 만료일이 동일한 수백/수천 인증서가 몰리지 않도록 발급 시 분산
사후 개선과 감사 대응
- 포스트모템(RCA)
- 기술적 원인 + 프로세스/거버넌스 원인 모두 규명
- 액션아이템에 명확한 책임자/기한 부여
- 감사/컴플라이언스
- 발급/갱신/배포/접근 로그, 변경관리 이력 보관
- 인증서 인벤토리와 알림 설정 스크린샷/구성 버전 관리
마무리: 지금 당장 할 일
- 인증서 인벤토리 구축/정비
- 만료 30/15/7/3/1일 다단계 알림과 온콜 라우팅 설정
- 핵심 경로(프런트엔드, API, 내부 mTLS)의 자동 갱신 파이프라인 도입 또는 점검
- Let’s Encrypt 스테이징으로 테스트 후 프로덕션 전환
- DNS/API 토큰 최소 권한·회전·감사 체계 확립
- 무중단 배포와 다중 지역 모니터링으로 배포 품질 보장
- HSTS/핀닝 등 고위험 설정은 성숙도 확인 후 점진 도입
인증서 만료는 “사전 예방”이 최선이고, “자동화·가시성·거버넌스”가 핵심입니다. 여기에 “실행 가능한 Runbook”을 더하면, 자동 갱신 실패 상황에서도 빠르게 복구할 수 있습니다. 오늘 소개한 설계와 체크리스트를 기반으로, 여러분의 조직에 맞는 표준과 도구를 정착시키십시오. 보안과 가용성은 올바른 습관에서 시작합니다.