Skip to content
Published on

[DevOps] Let's Encrypt와 Certbot 완전 가이드: 무료 TLS 인증서

Authors

1. TLS/SSL 인증서 기본 개념

1.1 왜 TLS 인증서가 필요한가

TLS(Transport Layer Security) 인증서는 웹 통신의 세 가지 핵심 보안 요소를 제공한다.

  • 암호화(Encryption): 클라이언트와 서버 간 데이터를 암호화하여 도청 방지
  • 인증(Authentication): 서버가 진짜 해당 도메인의 소유자임을 증명
  • 무결성(Integrity): 데이터가 전송 중 변조되지 않았음을 보장

1.2 인증서 체인 구조

┌───────────────────────┐
│   Root CA Certificate │  ← 브라우저/OS에 내장된 신뢰 앵커
│  (Self-Signed)        │
└───────────┬───────────┘
            │ 서명
┌───────────▼───────────┐
│ Intermediate CA Cert  │  ← 중간 CA 인증서
│ (Signed by Root CA)   │
└───────────┬───────────┘
            │ 서명
┌───────────▼───────────┐
│  Server Certificate   │  ← 도메인 인증서 (Leaf)
│ (Signed by Inter. CA) │
└───────────────────────┘

1.3 공개키/개인키 쌍

┌──────────┐     CSR 생성     ┌──────────────┐
│ 개인키     │ ──────────────> │     CSR      │
│(Private  │                  │(인증서 서명    │
│  Key)    │                  │  요청)        │
└──────────┘                  └──────┬───────┘
     │                               │ CA에 제출
     │                        ┌──────▼───────┐
     │                        │     CA       │
     │                        │  (서명 발급)   │
     │                        └──────┬───────┘
     │                               │
     │                        ┌──────▼───────┐
     └───────── 함께 사용 ─────>│   인증서      │
                              │ (Certificate) │
                              └──────────────┘
  • 개인키(Private Key): 서버만 보유, 절대 외부 유출 금지
  • CSR(Certificate Signing Request): 공개키 + 도메인 정보, CA에 제출
  • 인증서(Certificate): CA가 서명한 공개키 + 도메인 정보

2. Let's Encrypt란

2.1 개요

Let's Encrypt는 ISRG(Internet Security Research Group) 가 운영하는 무료, 자동화된, 개방형 인증 기관(CA)이다.

핵심 특징:

  • 무료: 도메인 검증(DV) 인증서를 무료로 발급
  • 자동화: ACME 프로토콜로 인증서 발급/갱신 완전 자동화
  • 개방: 오픈소스 프로토콜과 도구 사용
  • 유효기간: 90일 (짧은 주기로 보안 강화)
  • 신뢰: 모든 주요 브라우저와 OS에서 신뢰

2.2 인증서 유형 비교

유형검증 수준발급 시간비용Let's Encrypt
DV (Domain Validation)도메인 소유 확인수 분무료~저가지원
OV (Organization Validation)조직 확인수 일유료미지원
EV (Extended Validation)확장 검증수 주고가미지원

3. ACME 프로토콜 동작 원리

ACME(Automatic Certificate Management Environment)는 인증서 발급을 자동화하는 프로토콜이다.

3.1 전체 흐름

┌────────┐                          ┌──────────────┐
│ Certbot│                          │ Let's Encrypt│
│(Client)│                          │   (CA/ACME)  │
└───┬────┘                          └──────┬───────┘
    │  1. Account Registration              │
    │──────────────────────────────────────>│
    │  2. Account Created                   │
    │<──────────────────────────────────────│
    │                                       │
    │  3. Order (domain list)               │
    │──────────────────────────────────────>│
    │  4. Authorizations + Challenges       │
    │<──────────────────────────────────────│
    │                                       │
    │  5. Respond to Challenge              │
    │  (HTTP-01 / DNS-01 / TLS-ALPN-01)    │
    │──────────────────────────────────────>│
    │                                       │
    │  6. Challenge Validated               │
    │<──────────────────────────────────────│
    │                                       │
    │  7. Finalize (send CSR)               │
    │──────────────────────────────────────>│
    │  8. Certificate issued                │
    │<──────────────────────────────────────│

3.2 HTTP-01 Challenge

가장 일반적인 챌린지 방식이다. 포트 80을 통해 특정 파일에 접근할 수 있는지 검증한다.

Let's Encrypt → http://yourdomain.com/.well-known/acme-challenge/TOKEN_VALUE

검증 과정:
1. Certbot이 토큰 파일을 웹 서버에 배치
2. Let's Encrypt가 HTTP로 해당 파일에 접근
3. 파일 내용이 기대값과 일치하면 도메인 소유 확인

장점:

  • 가장 간단하고 일반적
  • 추가 DNS 설정 불필요
  • 대부분의 웹 서버에서 쉽게 설정

제한:

  • 포트 80이 외부에서 접근 가능해야 함
  • 와일드카드 인증서 발급 불가
  • 로드밸런서 뒤에 있을 때 설정이 복잡할 수 있음

3.3 DNS-01 Challenge

DNS TXT 레코드를 생성하여 도메인 소유를 증명한다. 와일드카드 인증서 발급에 필수다.

Let's Encrypt → DNS 조회: _acme-challenge.yourdomain.com TXT

검증 과정:
1. Certbot이 토큰을 계산
2. _acme-challenge.yourdomain.com TXT 레코드에 토큰 값 설정
3. Let's Encrypt가 DNS를 조회하여 값 확인
4. 일치하면 도메인 소유 확인

장점:

  • 와일드카드 인증서 발급 가능
  • 웹 서버가 없어도 가능
  • 포트 80이 열려있지 않아도 됨
  • 여러 서버에서 하나의 인증서 사용 가능

제한:

  • DNS Provider API가 필요 (자동화 시)
  • DNS 전파 지연 가능성
  • 수동 설정 시 번거로움

3.4 TLS-ALPN-01 Challenge

포트 443에서 TLS 연결 시 ALPN(Application-Layer Protocol Negotiation) 확장을 통해 검증한다.

검증 과정:
1. Let's Encrypt가 포트 443으로 TLS 연결 시도
2. ALPN으로 "acme-tls/1" 프로토콜 협상
3. 서버가 자체 서명 인증서로 응답 (특정 확장 포함)
4. 인증서의 acmeIdentifier 확장값으로 검증

장점:

  • 포트 80이 불필요
  • 포트 443만으로 검증 가능

제한:

  • 대부분의 웹 서버에서 기본 지원하지 않음
  • 별도 소프트웨어 필요 (예: Caddy)

3.5 챌린지 비교 표

기능HTTP-01DNS-01TLS-ALPN-01
포트80없음 (DNS)443
와일드카드XOX
웹 서버 필요OXO
자동화 난이도낮음중간 (DNS API 필요)높음
DNS 설정 필요XOX
주요 용도일반 웹 서버와일드카드, 내부 서버특수 환경

4. Certbot 설치

4.1 Ubuntu / Debian

# snap으로 설치 (권장)
sudo snap install core
sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

4.2 CentOS / RHEL / Rocky Linux

# EPEL 저장소 활성화
sudo dnf install epel-release
sudo dnf install certbot

# Nginx 플러그인
sudo dnf install python3-certbot-nginx

4.3 macOS

brew install certbot

4.4 Docker

docker run -it --rm \
  -v /etc/letsencrypt:/etc/letsencrypt \
  -v /var/lib/letsencrypt:/var/lib/letsencrypt \
  certbot/certbot certonly --help

5. 인증서 발급 방법

5.1 Standalone 모드

Certbot이 자체 웹 서버를 띄워서 HTTP-01 챌린지를 처리한다. 기존 웹 서버를 중지해야 한다.

# 포트 80을 사용하므로 기존 웹 서버 중지 필요
sudo systemctl stop nginx

sudo certbot certonly --standalone \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com \
  --non-interactive

# 웹 서버 재시작
sudo systemctl start nginx

5.2 Webroot 모드

기존 웹 서버를 중지하지 않고 인증서를 발급받는다. 웹 서버가 특정 디렉토리를 서빙하도록 설정해야 한다.

Nginx 설정 추가:

server {
    listen 80;
    server_name example.com www.example.com;

    # ACME 챌린지용 디렉토리
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # 나머지 요청은 HTTPS로 리다이렉트
    location / {
        return 301 https://$host$request_uri;
    }
}
# 디렉토리 생성
sudo mkdir -p /var/www/certbot

# Webroot 모드로 인증서 발급
sudo certbot certonly --webroot \
  -w /var/www/certbot \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

5.3 Nginx 플러그인

Certbot이 Nginx 설정을 자동으로 수정해 준다.

sudo certbot --nginx \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

Certbot이 자동으로 수행하는 작업:

  • HTTP-01 챌린지 처리
  • Nginx 설정에 SSL 관련 지시자 추가
  • HTTP를 HTTPS로 리다이렉트하는 설정 추가

5.4 Apache 플러그인

sudo certbot --apache \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

5.5 DNS 플러그인 (와일드카드 인증서)

와일드카드 인증서는 DNS-01 챌린지가 필수다.

Cloudflare DNS 플러그인:

# 플러그인 설치
sudo snap install certbot-dns-cloudflare

# API 토큰 파일 생성
sudo mkdir -p /etc/letsencrypt
cat > /etc/letsencrypt/cloudflare.ini << 'CFEOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
CFEOF
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

# 와일드카드 인증서 발급
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
  -d "example.com" \
  -d "*.example.com" \
  --agree-tos \
  --email admin@example.com

AWS Route 53 DNS 플러그인:

sudo snap install certbot-dns-route53

# AWS 자격 증명 설정 (IAM Role 또는 환경변수)
sudo certbot certonly \
  --dns-route53 \
  -d "example.com" \
  -d "*.example.com" \
  --agree-tos \
  --email admin@example.com

수동 DNS 챌린지:

sudo certbot certonly --manual \
  --preferred-challenges dns \
  -d "example.com" \
  -d "*.example.com" \
  --agree-tos \
  --email admin@example.com

# Certbot이 TXT 레코드 값을 안내하면
# DNS 관리 페이지에서 수동으로 TXT 레코드 추가:
# _acme-challenge.example.com  TXT  "제시된_토큰_값"

6. 발급된 파일 구조

# 인증서 파일 경로
ls -la /etc/letsencrypt/live/example.com/

# 파일 구조
/etc/letsencrypt/live/example.com/
  cert.pem       # 서버 인증서 (도메인 인증서만)
  chain.pem      # 중간 CA 인증서 체인
  fullchain.pem  # cert.pem + chain.pem (서버에서 사용)
  privkey.pem    # 개인키

# 실제 파일은 archive 디렉토리에 있고 live는 심볼릭 링크
/etc/letsencrypt/archive/example.com/
  cert1.pem
  chain1.pem
  fullchain1.pem
  privkey1.pem
파일내용용도
cert.pem서버 인증서단독 사용 (잘 사용하지 않음)
chain.pem중간 CA 인증서OCSP Stapling용
fullchain.pem서버 인증서 + 중간 CANginx ssl_certificate에 사용
privkey.pem개인키Nginx ssl_certificate_key에 사용

Nginx에 적용:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
}

7. 자동 갱신 설정

7.1 갱신 테스트

# 실제 갱신 없이 테스트만 수행
sudo certbot renew --dry-run

7.2 systemd Timer (권장)

Certbot snap 설치 시 자동으로 systemd timer가 생성된다.

# Timer 상태 확인
sudo systemctl status snap.certbot.renew.timer

# Timer 목록
sudo systemctl list-timers | grep certbot

직접 생성하는 경우:

# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Certbot renewal timer

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=3600
Persistent=true

[Install]
WantedBy=timers.target
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot renewal service

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "systemctl reload nginx"
sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

7.3 Crontab

# crontab -e
# 매일 새벽 2시, 오후 2시에 갱신 시도
0 2,14 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

7.4 갱신 훅(Hook) 설정

# 갱신 전 훅
sudo mkdir -p /etc/letsencrypt/renewal-hooks/pre
cat > /etc/letsencrypt/renewal-hooks/pre/stop-service.sh << 'HOOKEOF'
#!/bin/bash
# Standalone 모드용: 웹 서버 중지
systemctl stop nginx
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/pre/stop-service.sh

# 갱신 후 훅
sudo mkdir -p /etc/letsencrypt/renewal-hooks/post
cat > /etc/letsencrypt/renewal-hooks/post/start-service.sh << 'HOOKEOF'
#!/bin/bash
# 웹 서버 재시작
systemctl start nginx
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/post/start-service.sh

# 배포 훅 (성공적으로 갱신된 경우에만 실행)
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'HOOKEOF'
#!/bin/bash
systemctl reload nginx
echo "Certificate renewed and nginx reloaded at $(date)" >> /var/log/certbot-deploy.log
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

8. Rate Limits 주의사항

Let's Encrypt는 남용 방지를 위해 다음 Rate Limits를 적용한다.

제한설명
Certificates per Registered Domain50 / 주같은 도메인에 대해 주당 50개
Duplicate Certificate5 / 주동일한 도메인 세트에 대해 5개
Failed Validations5 / 시간 / 계정 / 호스트명검증 실패 제한
New Orders300 / 3시간새 주문 제한
Accounts per IP10 / 3시간IP당 계정 생성 제한

Staging 환경 활용:

# 테스트 시에는 Staging 서버 사용 (Rate Limit 훨씬 관대)
sudo certbot certonly --standalone \
  --staging \
  -d example.com \
  --agree-tos \
  --email admin@example.com

9. 대안 도구

9.1 acme.sh

순수 쉘 스크립트로 작성된 ACME 클라이언트다.

# 설치
curl https://get.acme.sh | sh

# 인증서 발급
acme.sh --issue -d example.com -w /var/www/html

# 와일드카드 (DNS API 사용)
acme.sh --issue \
  -d example.com \
  -d "*.example.com" \
  --dns dns_cf \
  --dnssleep 120

# Nginx에 설치
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com.key \
  --fullchain-file /etc/nginx/ssl/example.com.fullchain.pem \
  --reloadcmd     "systemctl reload nginx"

9.2 Caddy (자동 HTTPS)

Caddy는 웹 서버 자체에서 HTTPS를 자동으로 처리한다.

# Caddyfile
example.com {
    root * /var/www/html
    file_server
}
# 이것만으로 Let's Encrypt 인증서 자동 발급 및 갱신!

9.3 lego

Go로 작성된 ACME 클라이언트다.

# 설치
go install github.com/go-acme/lego/v4/cmd/lego@latest

# HTTP 챌린지
lego --email admin@example.com \
  --domains example.com \
  --http \
  run

# DNS 챌린지 (Cloudflare)
CLOUDFLARE_DNS_API_TOKEN=xxx \
lego --email admin@example.com \
  --domains "*.example.com" \
  --dns cloudflare \
  run

9.4 cert-manager (Kubernetes)

Kubernetes 환경에서 인증서를 자동 관리한다.

# ClusterIssuer 정의
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod-key
    solvers:
      - http01:
          ingress:
            class: nginx
# Ingress에서 인증서 요청
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-tls-secret
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app
                port:
                  number: 80
# Certificate 리소스 직접 생성
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: app-certificate
  namespace: default
spec:
  secretName: app-tls-secret
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - app.example.com
    - api.example.com
  renewBefore: 720h # 30일 전에 갱신

10. 트러블슈팅

10.1 일반적인 오류와 해결법

Challenge 실패:

# HTTP-01 챌린지 파일 접근 확인
curl -v http://yourdomain.com/.well-known/acme-challenge/test

# 방화벽 확인 (포트 80)
sudo ufw status
sudo iptables -L -n | grep 80

# DNS 확인
dig +short yourdomain.com
nslookup yourdomain.com

인증서 갱신 실패:

# 갱신 상태 확인
sudo certbot certificates

# 상세 로그 확인
sudo certbot renew --dry-run -v

# 로그 파일 확인
sudo cat /var/log/letsencrypt/letsencrypt.log

인증서 정보 확인:

# 인증서 내용 확인
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -text -noout

# 만료일 확인
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -enddate -noout

# 원격 서버 인증서 확인
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
  openssl x509 -text -noout

# 인증서 체인 검증
openssl verify -CAfile /etc/letsencrypt/live/example.com/chain.pem \
  /etc/letsencrypt/live/example.com/cert.pem

10.2 인증서 폐기(Revoke)

# 인증서 폐기
sudo certbot revoke --cert-path /etc/letsencrypt/live/example.com/cert.pem

# 인증서 삭제 (파일 정리)
sudo certbot delete --cert-name example.com

11. 보안 모범 사례

  1. 개인키 보호: privkey.pem 파일의 권한을 600으로 설정, root만 읽기 가능
  2. 자동 갱신 모니터링: 갱신 실패 시 알림 설정 (이메일, Slack 등)
  3. Staging 먼저: 새로운 설정은 항상 Staging 환경에서 테스트
  4. HSTS 적용: 인증서 발급 후 HSTS 헤더 설정
  5. 인증서 투명성 모니터링: crt.sh 등에서 도메인 인증서 발급 내역 모니터링
  6. 백업: /etc/letsencrypt 디렉토리 정기 백업
# 개인키 권한 확인
ls -la /etc/letsencrypt/live/example.com/privkey.pem
# -rw------- 1 root root ... privkey.pem

# 인증서 투명성 로그 조회
# https://crt.sh/?q=example.com

12. 결론

Let's Encrypt와 Certbot은 무료 TLS 인증서의 표준이 되었다. 핵심 정리:

  • HTTP-01 챌린지는 가장 간단하지만 와일드카드는 DNS-01 필수
  • Certbot의 Webroot 또는 Nginx 플러그인이 가장 실용적
  • 자동 갱신은 systemd timer 또는 crontab으로 설정
  • Kubernetes 환경에서는 cert-manager가 표준
  • Rate Limits를 이해하고 테스트 시 Staging 서버를 활용
  • 인증서 발급 후 Nginx TLS 보안 설정을 반드시 최적화해야 한다