Skip to content

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

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

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. 보안 모범 사례

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

현재 단락 (1/396)

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

작성 글자: 0원문 글자: 11,882작성 단락: 0/396