포스트

Let's Encrypt + Nginx — 발급보다 갱신 가능한 인증서 수명주기 만들기

Certbot의 nginx·webroot·standalone·DNS 인증 방식을 비교하고 인증서 발급→Nginx 적용→자동 갱신→dry-run→TLS 검증까지 운영 수명주기로 정리한다.

Let's Encrypt + Nginx — 발급보다 갱신 가능한 인증서 수명주기 만들기

Let’s Encrypt 운영에서 중요한 것은 인증서를 한 번 발급받는 것이 아니라 같은 방식으로 자동 갱신되고, Nginx가 새 인증서를 실제로 사용하며, 만료 전에 실패를 발견할 수 있는 수명주기를 만드는 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Domain / DNS 준비
      ↓
ACME Challenge 방식 선택
      ↓
Certificate 발급
      ↓
Nginx에 full chain + private key 적용
      ↓
자동 Renewal
      ↓
성공 후 Reload
      ↓
Dry-run / 실제 TLS 검증

이 흐름이 잡혀 있으면 certbot --nginx--webroot냐는 목적에 따른 구현 선택이 된다.

1. 먼저 ACME Challenge 방식을 고른다

Certbot의 대표 인증 방식은 다음처럼 구분할 수 있다.

방식검증 구조Web Server 중단Wildcard
--nginxCertbot이 Nginx 설정을 이용해 HTTP Challenge 처리보통 불필요아니오
--webroot기존 Web Server가 .well-known/acme-challenge 파일 제공불필요아니오
--standaloneCertbot이 임시 HTTP Server를 띄움80 Port 충돌 시 필요아니오
DNS Plugin / DNS-01DNS TXT Record로 Domain 소유권 검증불필요가능

Wildcard가 필요하면 DNS-01이 필요하다.

1
2
*.example.com
→ DNS-01

일반 Domain에서 이미 Nginx를 계속 운영하고 있고 Challenge Location을 직접 통제하고 싶다면 Webroot가 이해하기 쉬운 방식이다.

2. Nginx Plugin과 Webroot의 차이

Nginx Plugin

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

Certbot이 Nginx 설정을 읽고 인증과 설치를 도울 수 있다. 자동 설치를 원하는 초기 구성에는 편리하다.

이때 server_name이 실제 Domain과 맞아야 Certbot이 적절한 Server Block을 찾기 쉽다.

1
2
3
4
server {
    listen 80;
    server_name example.com www.example.com;
}

server_name _; 같은 Catch-all만 있는 구조라면 자동 매칭을 기대하기보다 Domain용 Server Block을 명시적으로 두는 편이 운영 구조도 명확하다.

Webroot

Webroot는 Certbot이 Challenge File만 특정 Directory에 쓰고, 기존 Nginx가 이를 HTTP로 제공한다.

1
2
3
4
5
6
7
sudo mkdir -p /var/www/letsencrypt

sudo certbot certonly \
  --webroot \
  -w /var/www/letsencrypt \
  -d example.com \
  -d www.example.com

Nginx:

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

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

    location / {
        return 301 https://$host$request_uri;
    }
}
1
2
3
4
5
6
7
Let's Encrypt
   ↓ HTTP-01 Request
Nginx :80
   ↓
/.well-known/acme-challenge/...
   ↓
Webroot File

Webroot의 장점은 Certificate 인증 경로와 Application Proxy 경로를 분리해 눈으로 확인하기 쉽다는 것이다. Nginx Plugin보다 무조건 안정적이라는 절대 규칙은 아니며, 현재 운영 방식에 맞는 Authenticator를 선택한다.

3. 발급된 파일의 역할을 구분한다

Certbot이 관리하는 live/<certificate-name>/에는 대표적으로 다음 Symlink가 있다.

1
2
3
4
5
6
7
8
9
10
11
cert.pem
→ Server Certificate

chain.pem
→ Intermediate Certificate Chain

fullchain.pem
→ cert.pem + chain.pem

privkey.pem
→ Private Key

Nginx의 일반적인 TLS 설정은:

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

처럼 fullchain.pemprivkey.pem을 사용한다.

Client가 Server Certificate에서 Trusted Root까지 Chain을 구성하려면 Server가 필요한 Intermediate Certificate를 함께 제공해야 하기 때문이다.

4. HTTPS Server Block을 적용하기 전에 Config Test

1
2
3
4
5
6
7
8
9
10
11
server {
    listen 443 ssl;
    server_name example.com www.example.com;

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

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

적용 전:

1
sudo nginx -t

성공한 뒤 Reload한다.

1
sudo systemctl reload nginx

Certificate 파일이 존재하는 것과 Nginx가 그 Certificate를 실제로 Load한 것은 다른 단계다.

5. Renewal은 처음 발급 때 사용한 설정을 재사용한다

Certbot의 renew는 갱신 대상 Certificate가 만료에 가까워지면 기존 Renewal Configuration의 Plugin과 옵션을 이용해 갱신을 시도한다.

따라서 운영에서 중요한 것은:

1
2
3
4
처음 발급 성공
        ↓
같은 Renewal Configuration으로
미래에도 자동 성공 가능한가?

다.

테스트:

1
sudo certbot renew --dry-run

--dry-run은 실제 Certificate를 바꾸지 않고 미래 갱신 경로를 시험하는 핵심 검증 절차다.

6. 새 인증서가 발급됐으면 Nginx가 다시 읽어야 한다

Renewal에 성공해 File Symlink가 새 Version을 가리켜도 이미 실행 중인 Nginx Process가 자동으로 새 Certificate를 사용하는 것은 별도 문제다.

성공한 Renewal 뒤에만 실행할 작업은 Deploy Hook으로 둘 수 있다. Certbot은 성공적으로 발급/갱신된 뒤 실행하는 --deploy-hook을 제공한다.

예:

1
2
sudo certbot renew \
  --deploy-hook "systemctl reload nginx"

운영에서는 명령줄에 매번 쓰기보다 Renewal Hook Directory나 관리되는 Certbot 설정으로 지속성을 확보한다.

1
2
3
4
5
Certificate Renewal 성공
        ↓
Nginx Reload
        ↓
새 Worker가 새 Certificate Load

7. “브라우저만 이상하다”면 HSTS와 Certificate Error를 분리한다

HSTS는 Browser에게 해당 Host를 앞으로 HTTP가 아니라 HTTPS로만 접근하라고 기억시키는 정책이다.

1
2
Strict-Transport-Security
→ HTTP 접속을 HTTPS로 강제

HSTS가 “예전에 봤던 잘못된 Certificate를 기억한다”는 뜻은 아니다.

브라우저마다 결과가 다를 때는 다음을 분리해 본다.

1
2
3
4
5
6
1. 실제 접속 Hostname이 같은가?
2. Server가 현재 어떤 Certificate Chain을 제공하는가?
3. Certificate SAN에 Hostname이 포함되는가?
4. Expiry / Trust Chain이 정상인가?
5. Browser/OS Trust Store나 Proxy/TLS Inspection 차이가 있는가?
6. HSTS 때문에 HTTP→HTTPS 강제가 일어나는가?

Server가 실제로 제공하는 Certificate는 Browser UI만 보지 않고 CLI에서도 확인할 수 있다.

1
2
3
4
openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  -showcerts

HSTS 상태 삭제는 HSTS 동작 자체를 진단할 때 사용할 수 있지만, 잘못된 Certificate Chain의 해결책으로 사용하지 않는다.

8. 운영 검증은 세 층으로 한다

Certbot이 무엇을 관리하나

1
sudo certbot certificates

미래 Renewal이 성공하나

1
sudo certbot renew --dry-run

실제 외부 TLS Endpoint가 새 Certificate를 내놓나

1
2
3
4
openssl s_client \
  -connect example.com:443 \
  -servername example.com </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates
1
2
3
4
5
6
7
Certbot State
   ↓
Renewal Path
   ↓
Nginx Runtime
   ↓
External TLS Handshake

각 단계가 모두 정상이어야 운영이 끝난다.

9. 자동 실행 방식은 설치 방법에 따라 확인한다

Certbot 자동 갱신을 어떤 Scheduler가 실행하는지는 OS Package, Snap 등 설치 방식에 따라 달라질 수 있다. 특정 배포판이면 반드시 cron을 직접 만들거나 반드시 특정 timer가 존재한다고 일반화하지 않는다.

먼저 현재 환경을 확인한다.

1
2
systemctl list-timers | grep -i certbot
systemctl status certbot.timer

Cron이나 다른 Scheduler로 설치했다면 해당 구성을 확인한다.

핵심은 Scheduler 종류가 아니라:

1
2
3
4
5
정기적으로 certbot renew가 실행되는가
        +
renew --dry-run이 성공하는가
        +
성공 후 Nginx Reload가 보장되는가

다.

정리

Let’s Encrypt 운영의 핵심은 발급 명령 하나가 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
Challenge 방식 결정
        ↓
Certificate 발급
        ↓
Nginx fullchain 적용
        ↓
Renewal Configuration 보존
        ↓
renew --dry-run
        ↓
성공 시 Nginx Reload
        ↓
외부 TLS Endpoint 검증

오늘 발급되는지보다 60일 뒤에도 사람 손 없이 갱신되는지를 확인해야 Certificate 운영이 완성된다.

참고

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.