안동민 개발노트

본문 시작

인증서 실무

ACME로 인증서를 발급·자동 갱신하고 자체 서명 인증서·SNI·mTLS를 용도와 신뢰 범위에 맞게 구성합니다.

TLS와 인증서의 원리를 이해했으니, 이제 실무에서 인증서를 어떻게 발급하고 관리하는지 살펴보겠습니다.


Let's Encrypt와 자동 갱신

Let's Encrypt는 무료로 TLS 인증서를 발급하는 비영리 CA입니다.

2015년 공개 서비스 시작 이후 HTTPS 보급을 넓힌 프로젝트입니다.

핵심은 무료라는 점보다 ACME(Automatic Certificate Management Environment) 프로토콜을 통해 도메인 검증, 발급, 갱신, 폐기를 자동화한다는 점입니다.

짧은 유효 기간은 수동 관리에는 부담이지만, 자동 갱신과 ACME Renewal Information(ARI)을 전제로 하면 키 유출이나 잘못된 발급의 영향 기간을 줄이는 데 도움이 됩니다.

  • 무료: 비용 부담 없이 누구나 HTTPS 적용.
  • 자동화: ACME 프로토콜로 발급/갱신 자동화.
  • 유효 기간은 프로필별로 확인: 2026-09-20 기준 classic 90일, tlsserver 45일, shortlived 약 6일.
  • DV 인증서: 도메인 제어권 확인 (기업 신원 확인 없음).
  • 와일드카드: DNS-01로 *.example.com 발급 가능.
certbot_commands.sh
# Certbot으로 Let's Encrypt 인증서 발급 (Nginx)
sudo certbot --nginx -d example.com -d www.example.com

# 갱신 절차 테스트 (staging 사용, 운영 인증서는 교체하지 않음)
sudo certbot renew --dry-run

# 자동 갱신 예약 확인 (배포판에 따라 systemd timer가 이미 활성화됨)
systemctl list-timers | grep certbot

# 기존 timer가 없고 cron을 쓸 때만, 기존 항목을 보존해 편집
sudo crontab -e
# 편집기에 다음 한 줄을 추가하고 저장:
# 17 3 * * * certbot renew --quiet --deploy-hook 'systemctl reload nginx'

# 발급된 인증서 확인
sudo certbot certificates
# Certificate Name: example.com
#   Domains: example.com www.example.com
#   Expiry Date: 2024-04-15 (VALID: 89 days)

# 수동 DNS-01 발급: auth hook 없이는 자동 갱신되지 않음
sudo certbot certonly --manual --preferred-challenges dns \
  -d "*.example.com" -d "example.com"

기간은 Let’s Encrypt의 공개 일정에 따라 변경됩니다. classic은 2027-02-10부터 64일, 2028-02-16부터 45일로 전환될 예정이므로 갱신 간격을 고정된 60일로 가정하지 않습니다. 위 출력의 2024년 만료일은 형식 예시이며 현재 발급 관측이 아닙니다.

인증서 발급과 실제 서비스 적용

인증서 발급과 실제 서비스 적용

인증서 운영의 네 단계도메인 제어 검증과 발급 뒤에도 서비스 배포 및 실제 제공 인증서를 확인해야 한다.ACME 검증도메인 제어 확인인증서 발급키와 파일 보관서비스 배포설정 검사 · reload외부에서 확인실제 제공 인증서
인증서 운영의 네 단계검증, 발급, 배포, 외부 확인 순서이다.ACME 검증도메인 제어 확인인증서 발급키와 파일 보관서비스 배포설정 검사 · reload외부에서 확인실제 제공 인증서

갱신 명령의 성공과 모든 서버의 적용 완료는 다릅니다. 실제 엔드포인트의 만료일·체인·호스트 이름을 확인하고, 실패 알림은 인증서가 만료되기 전에 전달되게 합니다.

AWS ACM, Cloudflare, Vercel 등의 관리형 구성에서는 인증서 발급과 갱신을 서비스에 맡길 수 있습니다. 자동 갱신의 조건은 도메인 검증 방식과 연결된 서비스에 따라 확인해야 합니다.

다만 “자동”이라고 해서 무조건 방치해도 된다는 뜻은 아닙니다.

수동 DNS-01은 인증용 hook이나 자동화 플러그인 없이 예약 실행만으로 갱신되지 않습니다. DNS 검증 레코드와 위임한 CNAME의 대상도 계속 관리해야 합니다.


자체 서명 인증서

개발 환경에서 HTTPS를 테스트하려면 인증서가 필요한데, 매번 Let's Encrypt를 사용하기는 번거롭습니다.

이때 자체 서명 인증서(Self-Signed Certificate)를 쓰거나 로컬 CA가 발급한 인증서를 사용할 수 있습니다. 아래 OpenSSL 예제는 자체 서명이고, mkcert의 서버 인증서는 설치한 로컬 CA가 서명합니다. -nodes는 개인키 파일을 암호화하지 않는 옵션이므로 파일 접근 통제도 필요합니다.

자체 서명 vs CA 서명 비교
CA 서명 인증서
  Root CA → 중간 CA → 서버 인증서
                        ↑ CA가 서명
  → 신뢰 경로·이름·기간·용도 등이 모두 유효해야 검증 성공

자체 서명 인증서
  서버 인증서
    ↑ 자기 자신이 서명
  → 브라우저 신뢰 저장소에 없음 → 검증 실패 → 경고! ✗
self_signed_cert.sh
# OpenSSL로 자체 서명 인증서 생성 (SAN 포함)
openssl req -x509 -newkey rsa:2048 -nodes \
  -keyout server.key -out server.crt -days 365 \
  -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1"

# 생성된 인증서 내용 확인
openssl x509 -in server.crt -noout -text | head -20

# mkcert 사용 (로컬 CA 설치로 경고 없이 개발)
# 1. 로컬 CA 설치 (최초 1회)
mkcert -install

# 2. localhost용 인증서 생성
mkcert localhost 127.0.0.1 ::1
# → localhost+2.pem (인증서)
# → localhost+2-key.pem (개인키)

# 3. Node.js에서 사용
# const https = require('https');
# const options = {
#   cert: fs.readFileSync('localhost+2.pem'),
#   key: fs.readFileSync('localhost+2-key.pem')
# };

자체 서명 서버 인증서를 공개 서비스에 그대로 쓰면 브라우저와 클라이언트가 신뢰할 수 없어 경고가 발생합니다.

프로덕션에서는 공개 Web PKI 인증서나 조직 내부에서 관리하는 사설 CA를 클라이언트 신뢰 저장소에 배포하는 방식이 필요합니다.


SNI

SNI(Server Name Indication)는 TLS 핸드셰이크에서 클라이언트가 접속하려는 호스트명을 서버에 알려주는 확장입니다.

ECH 없이 SNI를 보내면 접속하려는 호스트명이 평문 ClientHello에 들어갑니다. SNI는 서버의 인증서 선택을 돕는 값이며 호스트 이름 검증을 대신하지 않습니다.

인증서 선택을 위해 서버가 TLS 핸드셰이크 초기에 이름을 알아야 하기 때문입니다.

SNI 프라이버시 진화
SNI: ECH 없이 보내면 호스트명이 평문으로 노출
ESNI: ECH 이전의 실험적 접근
ECH: Inner/Outer 구조, ClientHelloInner 암호화

ECH(Encrypted Client Hello)는 RFC 9849로 표준화된 TLS 확장입니다. ClientHelloInner를 암호화해 그 안의 SNI와 협상 정보를 보호하지만, 외부 ClientHello와 IP 주소·트래픽 크기까지 모두 숨기지는 않습니다.

클라이언트는 보통 DNS HTTPS/SVCB 레코드에 담긴 ECHConfig를 통해 서버의 공개 설정을 얻습니다.

다만 DNS 질의 자체가 평문이면 그 단계에서 도메인이 노출될 수 있으므로, 실제 프라이버시는 암호화된 DNS와 배포 환경까지 함께 봐야 합니다.


mTLS (상호 인증)

일반적인 웹 TLS에서는 주로 서버만 인증서를 제시합니다.

mTLS(Mutual TLS)는 서버뿐 아니라 클라이언트도 인증서를 제시해 양쪽 신원을 TLS 계층에서 검증하는 방식입니다.

mTLS는 “접속 주체가 누구인지”를 강하게 확인하는 데 유용하지만, 그 주체가 어떤 작업을 할 수 있는지는 별도의 인가 정책으로 판단해야 합니다.

인증서 발급, 폐기, 회전, 키 보호, 서비스 ID 매핑까지 운영 체계가 함께 필요합니다.

  • 마이크로서비스: 서비스 간 상호 인증; 예시 — Istio, Linkerd 서비스 메시.
  • 금융 API: 핀테크 서비스 신원 증명; 예시 — Open Banking API.
  • IoT: 기기 인증; 예시 — 스마트홈, 의료 기기.
  • Zero Trust: 네트워크 접근 제어; 예시 — BeyondCorp, Cloudflare Access.
  • 내부 시스템: 관리 도구 접근 제한; 예시 — DB 관리 콘솔.

다음 명령은 CA와 클라이언트 키·서명 파일을 만드는 절차 예시입니다. 마지막 curl은 서버가 신뢰할 CA, 해당 호스트 이름의 서버 인증서, 클라이언트 인증 요구를 이미 구성했다는 전제입니다. 이 명령만으로 서버 설정이나 용도별 인증서 확장 정책이 완성되지는 않습니다.

mtls_setup.sh
# 1. CA 개인키와 인증서 생성
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout ca.key -out ca.crt -days 3650 \
  -subj "/CN=My Internal CA"

# 2. 클라이언트 개인키와 CSR 생성
openssl req -newkey rsa:2048 -nodes \
  -keyout client.key -out client.csr \
  -subj "/CN=service-a"

# 3. CA로 클라이언트 인증서 서명
openssl x509 -req -in client.csr \
  -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt -days 365

# 4. mTLS로 서버 접속 테스트
curl --cert client.crt --key client.key \
  --cacert ca.crt https://internal-api.company.com/health
인증서 관리 자동화 도구 비교
cert-manager
  대상: Kubernetes
  특징: 자동 발급/갱신, Let’s Encrypt 연동

Vault PKI
  대상: 전체 인프라
  특징: HashiCorp, 동적 인증서 발급

SPIFFE/SPIRE
  대상: 마이크로서비스
  특징: 서비스 ID 기반, mTLS 자동화

AWS ACM
  대상: AWS 서비스
  특징: ALB/CloudFront 등 지원 서비스 연동
  비용: 비내보내기 공개 인증서 발급은 무료

AWS 리소스 사용료와 내보내기·ACME 공개 인증서, Private CA의 비용은 별도입니다. 최신 조건은 ACM 가격 정책에서 확인합니다.

인증서와 개인키의 일치, SAN, 체인과 유효기간을 함께 확인합니다. 키가 노출되면 새 키와 인증서로 교체하고 기존 인증서의 폐기 절차도 수행합니다.

다음 절에서는 네트워크에서 발생하는 보안 위협과 방어 전략을 살펴보겠습니다.