테마 전환

Let's Encrypt SSL 인증서 설정: 자동 갱신과 다중 도메인 관리

Easton editorial illustration: practice lab desk

모니터링 알림 메일이 팀 전체를 깨웠습니다. 웹사이트에 접속할 수 없다는 사용자 불만 전화가 고객센터에 쏟아졌습니다. 브라우저를 열어 보니 빨간 경고 페이지에 “이 웹사이트의 보안 인증서가 만료되었습니다”라고 표시됩니다. 큰일이 났습니다.

자리에서 일어나 서버에 접속해 보니 SSL 인증서가 전날 밤 만료되어 있었습니다. 수동 갱신을 마친 시간이 새벽 5시였고, 두 시간 자고 다시 출근해야 했습니다.

그 일을 겪은 뒤 인증서 만료를 완전히 막을 방법이 없을지 고민했습니다. 그러다 Let’s Encrypt를 접하고 무료 SSL 인증서의 자동 갱신 체계가 이미 충분히 성숙했다는 사실을 알게 됐습니다. 다만 당시의 저를 포함한 많은 개발자는 여전히 수동 관리 방식에 머물러 있었습니다.

해결하려는 문제는 분명합니다. SSL 인증서가 다시는 만료되지 않게 하고 다중 도메인 관리도 더 이상 뒤엉키지 않게 만드는 것입니다. 개인 블로그든 회사 서비스든 바로 적용할 수 있도록 구성했습니다.


Let’s Encrypt란 무엇인가? 원리부터 이해하기

솔직히 Let’s Encrypt를 사용하면서도 작동 원리를 모르는 경우가 많습니다. 원리를 이해하면 문제가 생겼을 때 원인을 빠르게 찾을 수 있습니다.

인증 기관(CA)의 역할

Let’s Encrypt는 인증 기관, 즉 CA입니다. 지향점은 명확합니다. 무료, 자동화, 개방성입니다. 2016년 서비스를 시작한 뒤 몇 년 만에 HTTPS 보급을 크게 앞당겼습니다. 현재 활성 인증서는 2억 개가 넘고 전 세계 브라우저의 신뢰율은 100%입니다.

2억+
활성 인증서 수

기존 CA(DigiCert, GeoTrust 등)는 비용이 높고 절차가 복잡하며 수동 심사도 필요합니다. Let’s Encrypt는 다릅니다. 신청하면 자동으로 검증한 뒤 곧바로 발급합니다. 인증서 유효 기간은 90일로 짧아 보이지만 자동 갱신과 결합하면 오히려 더 안전합니다. 인증서 수명이 짧을수록 노출 위험도 작아지기 때문입니다.

ACME 프로토콜: 자동화의 핵심

Let’s Encrypt는 RFC 8555 표준인 ACME 프로토콜(Automated Certificate Management Environment)을 사용합니다. 이 프로토콜은 도메인 소유권을 자동으로 검증하고 인증서를 발급하는 방법을 정의합니다.

검증 방식은 세 가지입니다.

  1. HTTP-01 검증: 가장 널리 사용됩니다. CA가 도메인의 특정 경로(/.well-known/acme-challenge/)에 접근해 검증 토큰이 있는지 확인합니다. 이를 통해 해당 도메인을 제어하고 있음을 증명합니다.
  2. DNS-01 검증: 와일드카드 인증서에 반드시 필요합니다. CA가 DNS TXT 레코드에 검증 토큰이 있는지 확인합니다. Web 서버는 필요 없지만 DNS API 권한이 필요합니다.
  3. TLS-ALPN-01 검증: 사용 빈도는 낮으며 특수한 환경에 적합합니다.

HTTP-01은 가장 간단하지만 80번 포트에 접근할 수 있어야 합니다. DNS-01은 유연하며 내부망 서비스나 와일드카드 도메인에 적합합니다.

Certbot: 공식 권장 클라이언트

Certbot은 Let’s Encrypt가 공식적으로 권장하는 클라이언트 도구입니다. 인증서 발급, Web 서버 설정, 자동 갱신 구성까지 전체 과정을 처리할 수 있습니다.

Certbot의 핵심 기능은 다음과 같습니다.

  • 여러 검증 방식 지원
  • 플러그인을 통한 Nginx/Apache 설정 자동 변경
  • systemd timer 또는 cron job 자동 설정
  • 갱신 테스트 명령(dry-run) 제공

이 원리를 알아 두면 이후 설정 과정에서 문제가 생겨도 어디부터 확인해야 할지 알 수 있습니다.


단일 도메인 SSL 인증서 설정: 처음부터 따라 하기

먼저 도메인 하나와 서버 한 대인 가장 간단한 환경부터 시작합니다. 기본 흐름을 이해하면 다중 도메인도 자연스럽게 확장할 수 있습니다.

Certbot 설치

설치 방법은 운영체제마다 다릅니다. Ubuntu/Debian에서는 Snap을 사용하는 방법이 가장 간단하며 공식 권장 방식이라 최신 버전도 빠르게 받을 수 있습니다.

Ubuntu/Debian(Snap 방식):

# Snap 설치(아직 설치하지 않은 경우)
sudo apt update
sudo apt install snapd

# Certbot 설치
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

CentOS/RHEL:

sudo yum install certbot
# 또는
sudo dnf install certbot

설치가 끝나면 다음 명령으로 확인합니다.

certbot --version
# 다음과 비슷한 결과가 나와야 합니다: certbot 2.11.0

인증서 발급: 세 가지 방법

Certbot은 인증서를 발급하는 세 가지 방법을 제공합니다. 환경에 맞춰 선택하면 됩니다.

방법 1: Nginx 플러그인 자동 설정(초보자 권장)

가장 간단한 방법입니다. Certbot이 Nginx 설정을 자동으로 변경하고 인증서 발급, HTTPS 설정, 리디렉션 구성까지 명령 하나로 처리합니다.

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

실행 과정에서 몇 가지 항목을 묻습니다.

  • 이메일 주소(긴급 알림용)
  • 서비스 약관 동의
  • 이메일 공유 여부(No 선택)
  • HTTP를 HTTPS로 리디렉션할지 여부(더 안전한 Yes 선택)

완료되면 Nginx 설정은 이미 수정되어 있습니다. 인증서 경로도 다음과 같이 표시됩니다.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.com/privkey.pem

방법 2: Apache 플러그인 자동 설정

Nginx 방식과 같으며 플러그인만 Apache로 바뀝니다.

sudo certbot --apache -d example.com -d www.example.com

방법 3: 인증서만 발급(수동 설정)

Certbot이 설정 파일을 변경하지 않게 하거나 다른 Web 서버(Caddy, Node.js)를 사용한다면 certonly 모드를 사용할 수 있습니다.

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

-w는 Web 루트 디렉터리를 지정합니다. Certbot은 이 위치에 검증 파일을 배치합니다. 인증서를 발급받은 뒤 Web 서버는 직접 설정합니다.

인증서 적용 여부 확인

인증서 발급을 마쳤다면 먼저 정상 작동하는지 확인합니다.

인증서 파일 확인

sudo ls -la /etc/letsencrypt/live/example.com/

다음 네 파일이 보여야 합니다.

  • cert.pem: 인증서 본문
  • chain.pem: 중간 인증서 체인
  • fullchain.pem: 전체 인증서 체인(Nginx에서 사용)
  • privkey.pem: 개인 키(Nginx에서 사용)

SSL Labs 테스트

https://www.ssllabs.com/ssltest/ 에 접속해 도메인을 입력합니다. 테스트 결과는 A+부터 F까지의 등급으로 표시되며 최소 A 등급을 목표로 합니다.

일반적인 문제는 다음과 같습니다.

  • B 또는 C: TLS 버전이 낮거나 암호화 스위트가 취약합니다. 뒤에서 최적화 방법을 설명합니다.
  • F: 인증서 체인이 완전하지 않습니다. Nginx 설정에서 cert.pem이 아닌 fullchain.pem을 사용하는지 확인합니다.

브라우저 접속

https://example.com을 직접 열면 주소 표시줄에 자물쇠 아이콘이 보여야 합니다. 자물쇠 아이콘을 클릭하면 인증서 세부 정보와 “Let’s Encrypt”라는 발급자를 확인할 수 있습니다.


자동 갱신 설정: 만료 걱정 없애기

Let’s Encrypt 인증서의 유효 기간은 90일뿐입니다. 짧아 보이지만 자동 갱신과 결합하면 인증서가 자주 교체되고 노출 위험이 낮아지는 장점이 됩니다.

Certbot 자동 갱신 메커니즘

Certbot을 설치하면 갱신 메커니즘도 자동으로 설정됩니다. 방식은 운영체제에 따라 다릅니다.

Systemd Timer(최신 Linux):

Certbot 설치 후 certbot.timercertbot.service가 생성됩니다. Timer는 하루 두 번 실행되어 만료가 가까운 인증서(30일 이내)를 확인합니다. 갱신 시점이 되면 Service를 호출해 갱신합니다.

Timer 상태를 확인합니다.

sudo systemctl list-timers | grep certbot

출력 예시는 다음과 같습니다.

NEXT                         LEFT          LAST                         PASSED       UNIT           ACTIVATES
Thu 2026-04-02 12:00:00 UTC  1h left       Thu 2026-04-02 00:00:00 UTC  11h ago      certbot.timer  certbot.service

다음 실행 시간과 이전 실행 시간을 확인할 수 있습니다.

Cron Job(기존 방식):

systemd가 없는 구형 CentOS 같은 시스템에서는 Certbot이 cron job을 설정합니다. 다음 명령으로 확인합니다.

sudo crontab -l
# 또는
cat /etc/cron.d/certbot

일반적인 설정은 다음과 같습니다.

0 0,12 * * * root certbot renew --quiet

매일 0시와 12시에 갱신 여부를 확인합니다.

자동 갱신 작동 여부 검증

Timer나 Cron이 보인다는 사실만으로는 충분하지 않습니다. 실제로 갱신 절차가 실행되는지 확인해야 합니다.

Dry Run 테스트

Certbot은 실제 갱신 없이 과정을 시뮬레이션하는 테스트 명령을 제공합니다.

sudo certbot renew --dry-run

출력 예시는 다음과 같습니다.

Processing /etc/letsencrypt/renewal/example.com.conf
Cert not due for renewal, but simulating renewal for dry run
...
The dry run was successful.

“successful”이 표시되면 자동 갱신 설정이 정상입니다.

갱신 설정 파일 확인

각 인증서에는 해당 갱신 설정 파일이 있습니다.

cat /etc/letsencrypt/renewal/example.com.conf

이 파일에는 인증서 발급 당시의 매개변수와 검증 방식이 기록되어 있으며 갱신할 때 이 설정을 읽습니다.

갱신 후 Web 서버 자동 재로딩 설정

인증서가 갱신되어도 Web 서버는 계속 이전 인증서를 사용합니다. 새 인증서를 적용하려면 설정을 재로딩해야 합니다.

Deploy Hook(권장)

갱신 성공 후 명령을 자동으로 실행합니다.

sudo certbot renew --deploy-hook "systemctl reload nginx"

또는 갱신 설정 파일에 다음 내용을 추가합니다.

# /etc/letsencrypt/renewal/example.com.conf 편집
# 마지막에 추가:
deploy_hook = systemctl reload nginx

Post Hook(매번 실행)

갱신 성공 여부와 관계없이 실행됩니다.

sudo certbot renew --post-hook "systemctl reload nginx"

차이는 Deploy Hook은 갱신에 성공했을 때만 실행되고 Post Hook은 매번 실행된다는 점입니다. Deploy Hook이 더 합리적입니다.

갱신에 실패하면 어떻게 하나요?

자동 갱신도 항상 성공하는 것은 아닙니다. 가끔 실패할 수 있으며 일반적인 원인과 해결 방법은 다음과 같습니다.

DNS 해석 문제

도메인의 DNS를 변경했지만 검증 시 CA가 여전히 이전 IP를 조회하는 경우입니다. DNS 전파(TTL 시간)를 기다리거나 수동으로 갱신합니다.

sudo certbot renew --force-renewal

방화벽 또는 포트 문제

HTTP-01 검증에는 80번 포트 접근이 필요합니다. 방화벽을 확인합니다.

sudo ufw status
sudo iptables -L -n

80번 포트를 일시적으로 개방합니다.

sudo ufw allow 80/tcp

권한 문제

인증서 파일 권한이 잘못된 경우입니다. 다음 명령으로 확인합니다.

sudo ls -la /etc/letsencrypt/live/
sudo ls -la /etc/letsencrypt/archive/

Web 서버 사용자(예: www-data)가 인증서를 읽을 수 있는지 확인합니다.

갱신 로그 확인

실패하면 상세 로그를 확인합니다.

sudo tail -f /var/log/letsencrypt/letsencrypt.log

로그에는 실패 원인이 기록됩니다. 해당 내용에 따라 문제를 해결합니다.


다중 도메인 SSL 인증서 관리: 통합과 분리 전략

도메인이 여러 개라면 인증서 관리 전략이 중요합니다. 제대로 관리하지 않으면 갱신 시점이 제각각이고 인증서 파일이 흩어지며 설정 오류도 쉽게 발생합니다.

단일 인증서의 다중 도메인(SAN 방식)

가장 권장하는 방식은 하나의 인증서에 여러 도메인을 포함하는 것입니다. Certbot 발급 명령에서 도메인을 모두 지정합니다.

sudo certbot --nginx \
  -d example.com \
  -d www.example.com \
  -d api.example.com \
  -d admin.example.com

장점:

  • 통합 갱신: 한 번 갱신하면 모든 도메인이 업데이트됩니다.
  • 파일 집중: 인증서 디렉터리가 하나뿐입니다.
  • 간단한 설정: Web 서버에서 인증서 경로 하나만 참조하면 됩니다.

SAN(Subject Alternative Names) 확장은 인증서에 포함되는 필드로, 인증서가 지원하는 모든 도메인을 나열합니다. 브라우저는 접속한 도메인이 SAN 목록에 있는지 확인합니다.

인증서에 포함된 도메인은 다음 명령으로 확인합니다.

sudo certbot certificates

출력 예시는 다음과 같습니다.

Found the following certs:
  Certificate Name: example.com
    Domains: example.com www.example.com api.example.com admin.example.com
    Expiry Date: 2026-07-01 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/example.com/fullchain.pem

와일드카드 인증서(Wildcard)

와일드카드 인증서는 *.example.com 같은 패턴으로 모든 하위 도메인을 일치시킵니다. 인증서 하나로 blog.example.com, api.example.com, admin.example.com 등을 포함할 수 있습니다.

DNS-01 검증을 반드시 사용해야 합니다. HTTP-01은 와일드카드 도메인을 지원하지 않습니다. Certbot이 TXT 레코드를 추가할 수 있도록 DNS API 권한이 필요합니다.

DNS 플러그인 설정

플러그인은 DNS 서비스 제공업체마다 다릅니다. 가장 널리 사용하는 Cloudflare를 예로 들겠습니다.

# Cloudflare 플러그인 설치
sudo snap install certbot-dns-cloudflare

# 자격 증명 파일 생성
sudo nano /root/.secrets/certbot/cloudflare.ini

자격 증명 파일 내용은 다음과 같습니다.

dns_cloudflare_api_token = your_cloudflare_api_token

API Token은 Cloudflare 콘솔에서 생성하며 권한은 “DNS:Edit”를 선택합니다.

와일드카드 인증서 발급

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  -d "*.example.com" \
  -d example.com

*.example.comexample.com을 함께 발급해야 합니다. 와일드카드는 루트 도메인과 일치하지 않습니다.

와일드카드 인증서의 제한

1단계 하위 도메인에만 일치합니다. *.example.comsub.example.com과 일치하지만 sub.sub.example.com과는 일치하지 않습니다.

2단계 하위 도메인이 있다면 별도로 발급해야 합니다.

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  -d "*.example.com" \
  -d "*.api.example.com" \
  -d example.com

이렇게 하면 api.example.comv1.api.example.com을 모두 포함할 수 있습니다.

여러 인증서 관리 전략

언제 단일 인증서에 여러 도메인을 넣고 언제 여러 인증서를 사용해야 할까요?

서비스별 그룹화(권장)

Web 서비스 그룹: 메인 사이트, 블로그, 문서

sudo certbot --nginx -d example.com -d www.example.com -d blog.example.com -d docs.example.com

API 서비스 그룹: API 엔드포인트, 관리 콘솔

sudo certbot --nginx -d api.example.com -d admin.example.com -d v1.api.example.com

내부 서비스 그룹: 모니터링, 로그, 내부 도구

sudo certbot certonly --dns-cloudflare -d "*.internal.example.com"

장점:

  • 갱신 영향 범위 제어: API 서비스 갱신이 Web 서비스에 영향을 주지 않습니다.
  • 권한 분리: 내부 서비스 인증서 권한을 따로 관리할 수 있습니다.
  • 장애 격리: 한 인증서의 문제가 다른 서비스에 영향을 주지 않습니다.

도메인 계층별 그룹화

와일드카드와 단일 도메인 인증서를 조합합니다.

# 와일드카드로 모든 하위 도메인 포함
sudo certbot certonly --dns-cloudflare -d "*.example.com" -d example.com

# 특별한 설정이 필요한 하위 도메인은 별도 인증서 사용
sudo certbot --nginx -d secure.example.com

인증서 파일 경로 자세히 보기

인증서 디렉터리 구조를 이해하면 여러 인증서를 관리할 때 혼란을 줄일 수 있습니다.

실제 인증서 디렉터리: /etc/letsencrypt/live/[인증서 이름]/

심볼릭 링크는 최신 인증서를 가리킵니다. 갱신할 때마다 링크가 새 인증서 파일로 업데이트됩니다.

sudo ls -la /etc/letsencrypt/live/example.com/
lrwxrwxrwx 1 root root  42 Apr  2 12:00 cert.pem -> ../../archive/example.com/cert2.pem
lrwxrwxrwx 1 root root  43 Apr  2 12:00 chain.pem -> ../../archive/example.com/chain2.pem
lrwxrwxrwx 1 root root  44 Apr  2 12:00 fullchain.pem -> ../../archive/example.com/fullchain2.pem
lrwxrwxrwx 1 root root  40 Apr  2 12:00 privkey.pem -> ../../archive/example.com/privkey2.pem

인증서 보관 디렉터리: /etc/letsencrypt/archive/[인증서 이름]/

매번 갱신된 인증서 이력이 저장됩니다. 파일명에는 cert1.pem, cert2.pem처럼 번호가 붙습니다.

갱신 설정 디렉터리: /etc/letsencrypt/renewal/[인증서 이름].conf

인증서 발급 당시의 매개변수를 기록하며 갱신 시 이 파일을 읽습니다.

# 특정 인증서의 갱신 설정 확인
cat /etc/letsencrypt/renewal/example.com.conf

설정에는 다음 정보가 들어 있습니다.

  • 검증 방식(webroot, nginx, dns-cloudflare)
  • 원래 도메인 목록
  • Deploy Hook 등

고급 설정과 최적화: 보안과 성능의 균형

기본 설정을 마쳤다면 추가로 최적화할 수 있습니다. 목표는 보안과 성능을 함께 확보하는 것입니다.

HTTP/2와 OCSP Stapling

HTTP/2는 성능을 높이고 OCSP Stapling은 검증 지연을 줄입니다.

Nginx에서 HTTP/2 설정

server {
    listen 443 ssl http2;  # http2 추가
    server_name example.com;

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

    # ... 기타 설정
}

listen 지시문 뒤에 http2를 추가합니다. Nginx를 재로딩합니다.

sudo nginx -t
sudo systemctl reload nginx

OCSP Stapling 설정

OCSP(Online Certificate Status Protocol)는 인증서 상태를 검증합니다. Stapling을 사용하면 서버가 검증 결과를 미리 받아 두므로 클라이언트가 CA에 별도로 요청하지 않아도 됩니다.

server {
    # ... SSL 설정

    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

설정을 마치면 SSL Labs 테스트에 “OCSP Stapling: Yes”가 표시됩니다.

보안 강화: 구형 TLS 비활성화

TLS 1.0과 1.1은 더 이상 안전하지 않습니다. 주요 브라우저도 2020년 이후 지원을 중단했습니다.

server {
    # TLS 1.2와 1.3만 유지
    ssl_protocols TLSv1.2 TLSv1.3;

    # 권장 암호화 스위트(보안과 호환성의 균형)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

    ssl_prefer_server_ciphers on;

    # SSL Session 캐시(성능 향상)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
}

설정 후 SSL Labs 테스트에서 A 또는 A+ 등급을 받을 수 있습니다.

HSTS 헤더 설정

HSTS(HTTP Strict Transport Security)는 브라우저가 HTTPS로만 접속하도록 강제합니다. 설정하면 브라우저가 이 도메인은 반드시 HTTPS를 사용해야 한다고 기억하므로 사용자가 http://를 입력해도 자동으로 전환합니다.

server {
    # ... SSL 설정

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
}

매개변수의 의미는 다음과 같습니다.

  • max-age=31536000: 유효 기간 1년(초)
  • includeSubDomains: 모든 하위 도메인 포함
  • preload: 브라우저 사전 로딩 목록 등록 허용

주의: HSTS를 설정한 뒤 일시적으로 HTTP가 필요해도(예: 테스트) 브라우저가 접속을 거부할 수 있습니다. 신중하게 사용해야 합니다.

모니터링과 알림: 문제를 먼저 발견하기

자동 갱신도 만능은 아닙니다. 갱신에 실패할 가능성에 대비해 인증서 만료 일주일 전에 알림을 받으면 직접 처리할 시간이 생깁니다.

63%
인증서 장애 중 만료로 인한 비율

간단한 모니터링 스크립트

인증서의 남은 일수를 확인하는 스크립트를 만듭니다.

#!/bin/bash
# /usr/local/bin/check-ssl-expiry.sh

DOMAIN="example.com"
EXPIRY_DAYS=$(openssl s_client -connect $DOMAIN:443 -servername $DOMAIN 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)

EXPIRY_DATE=$(date -d "$EXPIRY_DAYS" +%s)
CURRENT_DATE=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_DATE - $CURRENT_DATE) / 86400 ))

if [ $DAYS_LEFT -lt 7 ]; then
    echo "WARNING: SSL certificate for $DOMAIN expires in $DAYS_LEFT days"
    # 이메일 알림(메일 서비스 설정 필요)
    mail -s "SSL Certificate Expiry Warning" [email protected] <<< "SSL certificate for $DOMAIN expires in $DAYS_LEFT days"
fi

cron에 추가해 매일 확인합니다.

0 6 * * * /usr/local/bin/check-ssl-expiry.sh

Certbot 자동 알림

Certbot은 갱신에 실패하면 인증서 발급 시 등록한 이메일 주소로 알림을 보냅니다. 주소가 정확하고 메일을 받을 수 있는지 확인합니다.


자주 발생하는 문제와 해결 방법

설정 과정에서는 여러 문제가 발생할 수 있습니다. 여기서는 자주 마주치는 문제와 해결 방법을 정리합니다.

인증서 발급 실패

원인 1: 도메인 DNS가 서버를 가리키지 않음

인증서를 발급할 때 CA는 도메인 소유권을 검증합니다. HTTP-01 검증은 도메인에 접근해야 하므로 DNS가 아직 설정되지 않았다면 실패합니다.

DNS 확인:

dig example.com +short
# 또는
nslookup example.com

반환된 IP가 서버 IP와 같은지 확인합니다.

해결 방법: DNS 전파를 기다립니다. TTL 시간(보통 몇 분에서 몇 시간)이 지난 뒤 다시 시도합니다.

원인 2: 80번 포트 점유 또는 방화벽 차단

HTTP-01 검증은 80번 포트로 접근합니다. 다른 프로세스가 포트를 사용하거나 방화벽이 차단하면 검증에 실패합니다.

포트 점유 확인:

sudo netstat -tulpn | grep :80
sudo lsof -i :80

Nginx/Apache가 아닌 프로세스가 사용 중이라면 해당 프로세스를 중지합니다.

sudo systemctl stop점유서비스이름

방화벽 확인:

sudo ufw status
# 또는
sudo iptables -L -n

80번 포트를 개방합니다.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

원인 3: Webroot 디렉터리 권한 문제

certonly --webroot 방식에서는 Certbot이 Web 루트 디렉터리에 검증 파일을 생성합니다. 권한이 부족하면 실패합니다.

디렉터리 권한 확인:

ls -la /var/www/html/.well-known/

Certbot 사용자(일반적으로 root)가 쓸 수 있는지 확인합니다.

디렉터리 직접 생성:

sudo mkdir -p /var/www/html/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/html/.well-known

갱신 실패 처리

자동 갱신도 가끔 실패합니다. 당황하지 말고 먼저 로그를 확인한 뒤 원인에 맞게 해결합니다.

Certbot 로그 확인

sudo tail -100 /var/log/letsencrypt/letsencrypt.log

로그에 실패 원인이 표시됩니다. 일반적인 오류는 다음과 같습니다.

  • Connection refused: 포트 문제
  • DNS problem: NXDOMAIN: DNS 해석 문제
  • Rate limit exceeded: 발급 횟수 제한 초과

수동 강제 갱신

자동 갱신에 실패했다면 수동으로 갱신합니다.

sudo certbot renew --force-renewal

--force-renewal은 만료 시점과 관계없이 갱신을 강제합니다.

갱신 설정 확인

갱신 설정 파일이 손상되어도 실패할 수 있습니다. 다음 명령으로 확인합니다.

cat /etc/letsencrypt/renewal/example.com.conf

설정이 올바른지 확인합니다. 파일이 손상됐다면 인증서를 삭제한 뒤 다시 발급합니다.

sudo certbot delete --cert-name example.com
sudo certbot --nginx -d example.com -d www.example.com

여러 인증서 충돌

인증서가 여러 개면 Web 서버가 잘못된 인증서를 참조할 수 있습니다.

잘못된 Nginx 인증서 경로

Nginx 설정을 확인합니다.

sudo nginx -T | grep ssl_certificate

각 server block이 올바른 인증서 경로를 사용하는지 확인합니다.

일반적인 오류:

  • fullchain.pem 대신 cert.pem 사용(인증서 체인 불완전)
  • 잘못된 인증서 디렉터리 참조(인증서 이름과 도메인이 다름)

인증서 이름과 도메인이 다름

Certbot은 첫 번째 도메인을 인증서 이름으로 사용합니다. 예를 들면 다음과 같습니다.

sudo certbot --nginx -d api.example.com -d example.com

인증서 이름은 api.example.com이고 경로는 /etc/letsencrypt/live/api.example.com/입니다.

Nginx 설정에 /etc/letsencrypt/live/example.com/을 쓰면 인증서를 찾을 수 없습니다.

인증서 이름 확인:

sudo certbot certificates

인증서 이름과 경로가 일치하는지 확인합니다.

와일드카드 인증서의 제한

와일드카드 인증서는 편리하지만 제약이 있습니다.

1단계 하위 도메인에만 일치

*.example.comsub.example.com과 일치하지만 sub.sub.example.com과는 일치하지 않습니다.

2단계 하위 도메인이 있다면 추가 인증서가 필요합니다.

# 와일드카드 인증서(1단계)
sudo certbot certonly --dns-cloudflare -d "*.example.com" -d example.com

# 2단계 하위 도메인 인증서
sudo certbot certonly --dns-cloudflare -d "*.api.example.com"

루트 도메인과 일치하지 않음

*.example.com은 루트 도메인 example.com과 일치하지 않습니다. -d example.com을 별도로 추가해야 합니다.

잘못된 예:

# 잘못된 설정: 루트 도메인에 접속할 수 없음
sudo certbot certonly --dns-cloudflare -d "*.example.com"

올바른 예:

# 올바른 설정: 루트 도메인도 함께 포함
sudo certbot certonly --dns-cloudflare -d "*.example.com" -d example.com

정리: 수동에서 자동으로 전환하기

SSL 인증서 설정 과정은 결국 수동 관리에서 자동화로 전환하는 과정입니다. 기존 CA의 수동 절차는 Let’s Encrypt의 자동화 체계에서 간단하고 안정적인 흐름으로 바뀝니다.

핵심 내용을 다시 정리하면 다음과 같습니다.

  1. 원리 이해: ACME 프로토콜은 자동 검증과 발급 절차를 정의합니다. HTTP-01과 DNS-01은 대표적인 두 검증 방식이며 각각 적합한 환경이 다릅니다.
  2. 자동 갱신: Certbot을 설치하면 systemd timer 또는 cron job이 자동으로 설정됩니다. 만료 30일 전부터 갱신할 수 있으며 하루 두 번 확인합니다.
  3. 다중 도메인 관리: SAN 방식은 하나의 인증서로 여러 도메인을 통합 관리하는 데 적합하고 와일드카드 인증서는 하위 도메인이 많을 때 유용합니다. 서비스별 그룹화를 권장합니다.
  4. 보안 최적화: HTTP/2, OCSP Stapling, 구형 TLS 비활성화, HSTS 설정을 적용하면 SSL Labs A+ 등급을 목표로 할 수 있습니다.
  5. 모니터링과 알림: 자동 갱신도 항상 성공하지는 않습니다. 인증서의 남은 유효 기간을 능동적으로 모니터링해 문제를 미리 발견해야 합니다.

추가 최적화 방향:

여러 서버와 많은 도메인을 관리한다면 다음 도구와 방식을 고려할 수 있습니다.

  • 자동화 플랫폼: Cert Manager(Kubernetes), Traefik(자동 SSL) 등
  • 인증서 모니터링 서비스: SSL Monitor, Uptime Robot 등
  • CI/CD 연동: 배포할 때 인증서 상태를 자동 확인

인증서 관리를 자동화하면 더 이상 신경 쓸 일이 크게 줄어듭니다. 웹사이트는 안전해지고 사용자의 신뢰도 높아지며 검색 엔진에도 유리합니다. 새벽 3시의 알림 메일은 이제 과거의 일이 됩니다.

Let's Encrypt SSL 인증서 자동 갱신 설정

Certbot 설치부터 자동 갱신 설정까지 전체 SSL 인증서 구성 과정을 따라 웹사이트 HTTPS 인증서가 만료되지 않도록 합니다.

⏱️ Estimated time: 30 min

  1. 1

    Step 1: Certbot 설치

    운영체제에 맞는 설치 방법을 선택합니다.

    • Ubuntu/Debian(권장): sudo snap install --classic certbot
    • CentOS/RHEL: sudo yum install certbot
    • 설치 확인: certbot --version
  2. 2

    Step 2: SSL 인증서 발급

    환경에 맞는 발급 방식을 선택합니다.

    • Nginx 자동 설정: sudo certbot --nginx -d example.com -d www.example.com
    • Apache 자동 설정: sudo certbot --apache -d example.com -d www.example.com
    • 인증서만 발급: sudo certbot certonly --webroot -w /var/www/html -d example.com
  3. 3

    Step 3: 자동 갱신 검증

    Certbot이 자동 갱신 메커니즘을 설정했는지 확인합니다.

    • Systemd Timer: sudo systemctl list-timers | grep certbot
    • Dry Run 테스트: sudo certbot renew --dry-run
    • 출력에 "successful"이 있는지 확인
  4. 4

    Step 4: 갱신 후 자동 재로딩 설정

    갱신 설정 파일에 Deploy Hook을 추가합니다.

    • 파일 편집: sudo nano /etc/letsencrypt/renewal/example.com.conf
    • 설정 추가: deploy_hook = systemctl reload nginx
    • 또는 명령 실행: sudo certbot renew --deploy-hook "systemctl reload nginx"
  5. 5

    Step 5: 보안 강화 설정

    Nginx 설정에 보안 최적화를 추가합니다.

    • 구형 TLS 비활성화: ssl_protocols TLSv1.2 TLSv1.3;
    • HTTP/2 활성화: listen 443 ssl http2;
    • OCSP Stapling: ssl_stapling on;
    • HSTS 헤더: add_header Strict-Transport-Security "max-age=31536000" always;
  6. 6

    Step 6: 모니터링 알림 설정

    인증서 만료 모니터링 스크립트를 만듭니다.

    • 스크립트 생성: sudo nano /usr/local/bin/check-ssl-expiry.sh
    • Cron 추가: 0 6 * * * /usr/local/bin/check-ssl-expiry.sh
    • Certbot 이메일 알림을 받을 수 있는지 확인

FAQ

Let's Encrypt 인증서의 유효 기간은 왜 90일뿐인가요?
90일의 짧은 유효 기간을 자동 갱신과 함께 사용하면 오히려 더 안전합니다. 인증서가 자주 교체되어 노출 위험이 줄어듭니다. 개인 키가 유출되더라도 90일 뒤 자동으로 무효화되므로 장기적인 위험을 낮출 수 있습니다.
HTTP-01과 DNS-01 검증 방식은 어떻게 다른가요?
HTTP-01은 CA가 도메인의 특정 경로에 접근해 검증하는 가장 간단한 방식이며 80번 포트가 열려 있어야 합니다. 단일 도메인 인증서에 적합합니다.

DNS-01은 CA가 DNS TXT 레코드를 확인하는 방식으로 DNS API 권한이 필요합니다. 와일드카드 인증서는 반드시 이 방식을 사용해야 하며 내부망 서비스나 많은 하위 도메인에 적합합니다.
와일드카드 인증서 *.example.com은 어떤 도메인과 일치하나요?
sub.example.com, api.example.com, blog.example.com 같은 1단계 하위 도메인에만 일치합니다.

일치하지 않는 경우:
• 2단계 하위 도메인: sub.sub.example.com(별도로 *.sub.example.com을 발급해야 함)
• 루트 도메인: example.com(별도로 -d example.com을 추가해야 함)
Certbot 자동 갱신은 언제 실행되나요?
인증서 만료 30일 전부터 자동 갱신이 실행됩니다. Systemd Timer 또는 Cron Job이 하루 두 번(일반적으로 0시와 12시) 확인하고 만료가 가까운 인증서를 갱신합니다. 갱신에 성공하면 Deploy Hook으로 Web 서버를 자동 재로딩합니다.
SSL Labs 테스트에서 B 또는 C 등급이 나오면 어떻게 해결하나요?
일반적인 원인과 해결 방법은 다음과 같습니다.

• 낮은 TLS 버전: Nginx에 ssl_protocols TLSv1.2 TLSv1.3; 설정
• 취약한 암호화 스위트: 6절의 권장 암호화 스위트 설정
• 불완전한 인증서 체인: cert.pem 대신 fullchain.pem 사용
• OCSP Stapling 누락: ssl_stapling on; 설정 추가

설정 후 Nginx를 재로딩하고 다시 테스트하면 A 또는 A+ 등급을 받을 수 있습니다.
여러 도메인에 단일 인증서와 여러 인증서 중 무엇을 사용해야 하나요?
서비스별 그룹화 전략을 권장합니다.

• 단일 인증서의 다중 도메인(SAN 방식): 관련 도메인을 함께 갱신할 수 있어 설정이 간단합니다. 예: Web 서비스 그룹인 example.com, www.example.com, blog.example.com
• 여러 인증서로 그룹화: 서비스가 다를 때 장애와 권한을 분리할 수 있습니다. 예: Web, API, 내부 서비스를 별도로 관리
• 와일드카드 인증서: 많은 하위 도메인을 하나로 묶어 인증서 수를 줄입니다. 예: *.example.com으로 모든 1단계 하위 도메인 포함
인증서 갱신에 실패하면 어떻게 해야 하나요?
먼저 로그를 확인해 원인을 찾습니다: sudo tail -100 /var/log/letsencrypt/letsencrypt.log

일반적인 실패 원인:
• DNS 해석 문제: DNS 전파를 기다리거나 --force-renewal로 수동 갱신
• 포트 점유: 80번 포트를 확인하고 점유 서비스를 일시 중지
• 방화벽 차단: 80/443번 포트 개방
• 권한 문제: 인증서 파일 권한 확인

문제를 해결한 뒤 수동으로 갱신합니다: sudo certbot renew --force-renewal

5분 읽기 · 게시일: 2026년 4월 2일 · 수정일: 2026년 9월 4일

댓글

GitHub로 로그인하여 댓글을 남기세요

Easton BlogEaston Blog