Split View: [DevOps] Let's Encrypt와 Certbot 완전 가이드: 무료 TLS 인증서
[DevOps] Let's Encrypt와 Certbot 완전 가이드: 무료 TLS 인증서
- 1. TLS/SSL 인증서 기본 개념
- 2. Let's Encrypt란
- 3. ACME 프로토콜 동작 원리
- 4. Certbot 설치
- 5. 인증서 발급 방법
- 6. 발급된 파일 구조
- 7. 자동 갱신 설정
- 8. Rate Limits 주의사항
- 9. 대안 도구
- 10. 트러블슈팅
- 11. 보안 모범 사례
- 12. 결론
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-01 | DNS-01 | TLS-ALPN-01 |
|---|---|---|---|
| 포트 | 80 | 없음 (DNS) | 443 |
| 와일드카드 | X | O | X |
| 웹 서버 필요 | O | X | O |
| 자동화 난이도 | 낮음 | 중간 (DNS API 필요) | 높음 |
| DNS 설정 필요 | X | O | X |
| 주요 용도 | 일반 웹 서버 | 와일드카드, 내부 서버 | 특수 환경 |
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 | 서버 인증서 + 중간 CA | Nginx 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 Domain | 50 / 주 | 같은 도메인에 대해 주당 50개 |
| Duplicate Certificate | 5 / 주 | 동일한 도메인 세트에 대해 5개 |
| Failed Validations | 5 / 시간 / 계정 / 호스트명 | 검증 실패 제한 |
| New Orders | 300 / 3시간 | 새 주문 제한 |
| Accounts per IP | 10 / 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. 보안 모범 사례
- 개인키 보호: privkey.pem 파일의 권한을 600으로 설정, root만 읽기 가능
- 자동 갱신 모니터링: 갱신 실패 시 알림 설정 (이메일, Slack 등)
- Staging 먼저: 새로운 설정은 항상 Staging 환경에서 테스트
- HSTS 적용: 인증서 발급 후 HSTS 헤더 설정
- 인증서 투명성 모니터링: crt.sh 등에서 도메인 인증서 발급 내역 모니터링
- 백업: /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
- 2. What Is Let's Encrypt
- 3. ACME Protocol Mechanics
- 4. Certbot Installation
- 5. Certificate Issuance Methods
- 6. Issued File Structure
- 7. Automatic Renewal Setup
- 8. Rate Limits
- 9. Alternative Tools
- 10. Troubleshooting
- 11. Security Best Practices
- 12. Conclusion
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
| Type | Validation Level | Issuance Time | Cost | Let's Encrypt |
|---|---|---|---|---|
| DV (Domain Validation) | Domain ownership | Minutes | Free to low | Supported |
| OV (Organization Validation) | Organization | Days | Paid | Not supported |
| EV (Extended Validation) | Extended | Weeks | Expensive | Not 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
| Feature | HTTP-01 | DNS-01 | TLS-ALPN-01 |
|---|---|---|---|
| Port | 80 | None (DNS) | 443 |
| Wildcard | No | Yes | No |
| Web Server Required | Yes | No | Yes |
| Automation Difficulty | Low | Medium (DNS API needed) | High |
| DNS Setup Required | No | Yes | No |
| Primary Use | General web servers | Wildcards, internal servers | Special 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
| File | Contents | Usage |
|---|---|---|
| cert.pem | Server certificate | Standalone use (rarely used) |
| chain.pem | Intermediate CA cert | For OCSP Stapling |
| fullchain.pem | Server cert + Intermediate CA | Nginx ssl_certificate |
| privkey.pem | Private key | Nginx 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
7.2 systemd Timer (Recommended)
# /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.
| Limit | Value | Description |
|---|---|---|
| Certificates per Registered Domain | 50/week | 50 per domain per week |
| Duplicate Certificate | 5/week | 5 for identical domain sets |
| Failed Validations | 5/hour/account/hostname | Validation failure limit |
| New Orders | 300/3 hours | New order limit |
| Accounts per IP | 10/3 hours | Account 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
- Protect private keys: Set privkey.pem permissions to 600, readable only by root
- Monitor automatic renewal: Set up alerts for renewal failures (email, Slack, etc.)
- Staging first: Always test new configurations in the staging environment
- Apply HSTS: Configure HSTS headers after certificate issuance
- Certificate Transparency monitoring: Monitor domain certificate issuance at crt.sh
- 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