Post

Let's Encrypt + Nginx 운영 가이드: 발급, 갱신, 그리고 만난 함정 4가지

Let's Encrypt SSL 인증서를 nginx와 함께 운영하면서 마주친 server_name 매칭, fullchain, webroot 전환, HSTS 캐시 문제 정리

Let's Encrypt + Nginx 운영 가이드: 발급, 갱신, 그리고 만난 함정 4가지

Let’s Encrypt 무료 SSL 인증서는 발급 자체는 쉽지만, 운영 중 마주치는 함정들이 있다. 발급 방식 비교부터 함정 4가지까지 정리.

인증서 발급 방식 3가지

방식특징와일드카드
Standalonecertbot이 임시 웹서버를 80/443에 띄움. 가장 간단하지만 기존 웹서버를 잠시 중단해야 함
Webroot사이트 디렉토리에 인증 파일을 두고 검증. 웹서버 중단 불필요
DNSDNS TXT 레코드로 검증. 도메인 관리 권한 필요
1
2
3
4
5
6
7
8
9
# Standalone
sudo certbot certonly --standalone -d example.com -d www.example.com

# Webroot
sudo certbot certonly --webroot --webroot-path=/var/www/letsencrypt -d example.com

# DNS (수동)
sudo certbot certonly --manual -d "*.example.com" -d example.com \
  --preferred-challenges dns-01

Certbot 설치

Rocky Linux / RHEL 계열

1
2
sudo dnf install -y epel-release
sudo dnf install -y certbot python3-certbot-nginx

Debian / Ubuntu

1
2
sudo apt update
sudo apt install -y certbot python3-certbot-nginx

Nginx 자동 구성으로 발급

certbot --nginx는 nginx 설정을 자동으로 수정해 SSL 블록을 추가한다.

1
sudo certbot --nginx -d example.com -d www.example.com

발급 후 인증서 파일은 /etc/letsencrypt/live/example.com/:

1
2
3
4
cert.pem        ← 끝단 인증서 (단독)
fullchain.pem   ← 끝단 + 중간 인증서 (체인 완성)
privkey.pem     ← 개인키
chain.pem       ← 중간 인증서만

운영 중 만난 함정 4가지

1. server_name _; 일 때 certbot이 매칭 실패

기본 catch-all로 nginx를 운영하던 서버에서 certbot --nginx를 돌리면 “No matching server block” 같은 에러가 난다.

1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ certbot이 자동으로 못 찾음
server {
    listen 80;
    server_name _;
    ...
}

# ✅ 도메인명을 명시
server {
    listen 80;
    server_name example.com www.example.com;
    ...
}

certbot --nginx-d 인자의 도메인과 server block의 server_name을 매칭해서 SSL 블록을 끼워 넣는다. catch-all로는 매칭 못 한다.

2. cert.pem이 아니라 fullchain.pem

발급 후 nginx 설정에 인증서를 적용할 때 무조건 fullchain.pem.

1
2
3
4
5
6
# ❌ self-signed처럼 보임
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;

# ✅ 정상
ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

cert.pem은 끝단 인증서만 들어 있어서 브라우저가 중간 인증서(Let’s Encrypt R3 등)까지 신뢰 체인을 연결하지 못한다. fullchain.pem은 끝단 + 중간을 한 파일에 묶어둔 거.

3. 자동 갱신은 nginx 플러그인보다 webroot가 안정적

certbot --nginx로 발급한 경우 갱신할 때도 nginx 플러그인이 동작한다. 갱신마다 nginx 설정을 읽고 다시 손대는데, 복잡한 설정에선 무한 대기나 reload 실패가 발생한다.

1
2
sudo certbot renew --dry-run
# → 응답 없이 멈춤

해결: webroot로 전환.

1
sudo mkdir -p /var/www/letsencrypt

nginx 80 블록에 챌린지 경로 추가:

1
2
3
4
5
6
7
8
9
10
11
12
server {
    listen 80;
    server_name example.com;

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

    location / {
        return 301 https://$host$request_uri;
    }
}

renewal conf 수정 (/etc/letsencrypt/renewal/example.com.conf):

1
2
3
4
5
6
7
[renewalparams]
authenticator = webroot
key_type = ecdsa
renew_hook = systemctl reload nginx

[[webroot_map]]
example.com = /var/www/letsencrypt
1
sudo certbot renew --dry-run    # 성공해야 함

webroot 방식은 nginx 설정을 건드리지 않고 challenge 파일만 두니까 영향이 격리된다.

4. Chrome만 “not secure”, Safari는 정상

인증서를 올바르게 적용한 뒤에도 Chrome에서만 “not secure”로 표시되는 경우가 있다. 발급 전에 한 번 잘못된 SSL로 접속한 적이 있으면 HSTS 캐시가 무효 인증서 정책을 기억해두기 때문이다.

해결 (Chrome):

1
2
chrome://net-internals/#hsts
  → Delete domain security policies → 도메인 입력 → Delete

Safari는 HSTS 캐시 정책이 달라서 동일한 상황에서도 정상으로 표시될 수 있다. 두 브라우저 동작이 다르면 캐시를 먼저 의심한다.


자동 갱신 스케줄

Rocky 9는 certbot 설치 시 certbot.timer가 자동 등록되지 않으니 수동으로 설정한다.

Crontab 방식 (간단)

1
2
3
crontab -e
# 매일 새벽 3시 갱신 시도, 갱신되면 nginx reload
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"

systemd timer 방식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cat <<'EOF' | sudo tee /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet
EOF

cat <<'EOF' | sudo tee /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Run certbot twice daily

[Timer]
OnCalendar=*-*-* 03,15:00:00
RandomizedDelaySec=1h
Persistent=true

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now certbot-renew.timer

핵심 정리

  • 발급 방식 선택: 단순 = Standalone, 운영 중 = Webroot, 와일드카드 = DNS
  • nginx 설정에 무조건 fullchain.pem, server_name 명시
  • 자동 갱신은 webroot로 전환해서 nginx 플러그인 의존성 제거
  • 브라우저 인증서 표시가 다르면 HSTS 캐시부터 의심
This post is licensed under CC BY 4.0 by the author.