Skip to content

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

|

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


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 보안 설정을 반드시 최적화해야 한다

[DevOps] Let's Encrypt and Certbot Complete Guide: Free TLS Certificates


1. TLS/SSL Certificate Fundamentals

1.1 Why TLS Certificates Are Needed

TLS (Transport Layer Security) certificates provide three core security elements for web communication.

  • Encryption: Encrypts data between client and server to prevent eavesdropping
  • Authentication: Proves the server is the real owner of the domain
  • Integrity: Guarantees data was not tampered with during transmission

1.2 Certificate Chain Structure

┌───────────────────────┐
│   Root CA Certificate │  <- Trust anchor embedded in browsers/OS
│  (Self-Signed)        │
└───────────┬───────────┘
            │ Signs
┌───────────▼───────────┐
│ Intermediate CA Cert  │  <- Intermediate CA certificate
│ (Signed by Root CA)   │
└───────────┬───────────┘
            │ Signs
┌───────────▼───────────┐
│  Server Certificate   │  <- Domain certificate (Leaf)
│ (Signed by Inter. CA) │
└───────────────────────┘

1.3 Public/Private Key Pairs

  • Private Key: Held only by the server, must never be leaked
  • CSR (Certificate Signing Request): Public key + domain info, submitted to CA
  • Certificate: CA-signed public key + domain info

2. What Is Let's Encrypt

2.1 Overview

Let's Encrypt is a free, automated, open Certificate Authority (CA) operated by ISRG (Internet Security Research Group).

Key features:

  • Free: Issues Domain Validation (DV) certificates at no cost
  • Automated: Fully automates certificate issuance/renewal via ACME protocol
  • Open: Uses open-source protocols and tools
  • Validity: 90 days (short cycle enhances security)
  • Trusted: Trusted by all major browsers and operating systems

2.2 Certificate Type Comparison

TypeValidation LevelIssuance TimeCostLet's Encrypt
DV (Domain Validation)Domain ownershipMinutesFree to lowSupported
OV (Organization Validation)OrganizationDaysPaidNot supported
EV (Extended Validation)ExtendedWeeksExpensiveNot supported

3. ACME Protocol Mechanics

ACME (Automatic Certificate Management Environment) is a protocol that automates certificate issuance.

3.1 Overall Flow

┌────────┐                          ┌──────────────┐
│ 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

The most common challenge type. Validates that a specific file is accessible over port 80.

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

Process:
1. Certbot places a token file on the web server
2. Let's Encrypt accesses the file via HTTP
3. If content matches expected value, domain ownership is confirmed

Advantages:

  • Simplest and most common
  • No additional DNS configuration needed
  • Easy to set up on most web servers

Limitations:

  • Port 80 must be externally accessible
  • Cannot issue wildcard certificates
  • Configuration can be complex behind load balancers

3.3 DNS-01 Challenge

Proves domain ownership by creating a DNS TXT record. Required for wildcard certificates.

Let's Encrypt -> DNS lookup: _acme-challenge.yourdomain.com TXT

Process:
1. Certbot calculates a token
2. Set the token value in _acme-challenge.yourdomain.com TXT record
3. Let's Encrypt queries DNS to verify the value
4. If it matches, domain ownership is confirmed

Advantages:

  • Can issue wildcard certificates
  • Works without a web server
  • Port 80 does not need to be open
  • One certificate can be used across multiple servers

Limitations:

  • DNS Provider API needed (for automation)
  • Possible DNS propagation delay
  • Manual setup can be tedious

3.4 TLS-ALPN-01 Challenge

Validates through the ALPN (Application-Layer Protocol Negotiation) extension during TLS connection on port 443.

3.5 Challenge Comparison

FeatureHTTP-01DNS-01TLS-ALPN-01
Port80None (DNS)443
WildcardNoYesNo
Web Server RequiredYesNoYes
Automation DifficultyLowMedium (DNS API needed)High
DNS Setup RequiredNoYesNo
Primary UseGeneral web serversWildcards, internal serversSpecial environments

4. Certbot Installation

4.1 Ubuntu / Debian

# Install via snap (recommended)
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

sudo dnf install epel-release
sudo dnf install certbot
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. Certificate Issuance Methods

5.1 Standalone Mode

Certbot runs its own web server to handle the HTTP-01 challenge. The existing web server must be stopped.

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 Mode

Issues certificates without stopping the existing web server. The web server must serve a specific directory.

Nginx configuration:

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

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}
sudo mkdir -p /var/www/certbot

sudo certbot certonly --webroot \
  -w /var/www/certbot \
  -d example.com \
  -d www.example.com \
  --agree-tos \
  --email admin@example.com

5.3 Nginx Plugin

Certbot automatically modifies the Nginx configuration.

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

5.4 DNS Plugin (Wildcard Certificates)

Wildcard certificates require the DNS-01 challenge.

Cloudflare DNS Plugin:

sudo snap install certbot-dns-cloudflare

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 Plugin:

sudo snap install certbot-dns-route53

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

6. Issued File Structure

/etc/letsencrypt/live/example.com/
  cert.pem       # Server certificate only
  chain.pem      # Intermediate CA certificate chain
  fullchain.pem  # cert.pem + chain.pem (used by servers)
  privkey.pem    # Private key
FileContentsUsage
cert.pemServer certificateStandalone use (rarely used)
chain.pemIntermediate CA certFor OCSP Stapling
fullchain.pemServer cert + Intermediate CANginx ssl_certificate
privkey.pemPrivate keyNginx ssl_certificate_key

Apply to 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. Automatic Renewal Setup

7.1 Renewal Test

sudo certbot renew --dry-run
# /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
0 2,14 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

7.4 Renewal Hooks

# Deploy hook (runs only when renewal succeeds)
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 applies the following rate limits to prevent abuse.

LimitValueDescription
Certificates per Registered Domain50/week50 per domain per week
Duplicate Certificate5/week5 for identical domain sets
Failed Validations5/hour/account/hostnameValidation failure limit
New Orders300/3 hoursNew order limit
Accounts per IP10/3 hoursAccount creation per IP

Use the Staging Environment:

# Use staging server for testing (much more lenient rate limits)
sudo certbot certonly --standalone \
  --staging \
  -d example.com \
  --agree-tos \
  --email admin@example.com

9. Alternative Tools

9.1 acme.sh

A pure shell script ACME client.

curl https://get.acme.sh | sh

acme.sh --issue -d example.com -w /var/www/html

# Wildcard with DNS API
acme.sh --issue \
  -d example.com \
  -d "*.example.com" \
  --dns dns_cf \
  --dnssleep 120

# Install to 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 (Automatic HTTPS)

Caddy handles HTTPS automatically within the web server itself.

# Caddyfile
example.com {
    root * /var/www/html
    file_server
}
# This alone auto-issues and renews Let's Encrypt certificates!

9.3 cert-manager (Kubernetes)

Automatically manages certificates in Kubernetes environments.

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
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
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 # Renew 30 days before expiry

10. Troubleshooting

10.1 Common Errors and Solutions

Challenge failure:

# Verify HTTP-01 challenge file access
curl -v http://yourdomain.com/.well-known/acme-challenge/test

# Check firewall (port 80)
sudo ufw status
sudo iptables -L -n | grep 80

# Check DNS
dig +short yourdomain.com

Certificate renewal failure:

sudo certbot certificates
sudo certbot renew --dry-run -v
sudo cat /var/log/letsencrypt/letsencrypt.log

Certificate info check:

# View certificate contents
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -text -noout

# Check expiration date
openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -enddate -noout

# Check remote server certificate
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | \
  openssl x509 -text -noout

11. Security Best Practices

  1. Protect private keys: Set privkey.pem permissions to 600, readable only by root
  2. Monitor automatic renewal: Set up alerts for renewal failures (email, Slack, etc.)
  3. Staging first: Always test new configurations in the staging environment
  4. Apply HSTS: Configure HSTS headers after certificate issuance
  5. Certificate Transparency monitoring: Monitor domain certificate issuance at crt.sh
  6. Backup: Regularly back up the /etc/letsencrypt directory

12. Conclusion

Let's Encrypt and Certbot have become the standard for free TLS certificates. Key takeaways:

  • HTTP-01 challenge is simplest, but wildcards require DNS-01
  • Certbot Webroot or Nginx plugin is most practical
  • Automatic renewal via systemd timer or crontab
  • cert-manager is the standard in Kubernetes environments
  • Understand rate limits and use staging server for testing
  • After certificate issuance, always optimize Nginx TLS security settings