Let's Encrypt 무료 SSL 인증서 설정 완벽 가이드 - HTTPS 적용 실전
Let's Encrypt Free SSL Certificate Complete Setup Guide
서론: HTTPS는 이제 선택이 아닌 필수
2026년 현재 HTTPS는 웹사이트의 필수 요소입니다. Google Chrome, Firefox, Safari 등 모든 주요 브라우저는 HTTP 사이트에 "안전하지 않음" 경고를 표시하며, Google은 HTTPS 사이트에 검색 순위 가산점을 부여합니다. 또한 HTTP/2, HTTP/3, Service Worker 같은 최신 웹 기술은 HTTPS에서만 동작합니다.
과거에는 SSL 인증서 발급에 연간 수십만 원의 비용이 들었지만, Let's Encrypt가 등장하면서 상황이 완전히 바뀌었습니다. 2015년 설립된 Let's Encrypt는 비영리 인증 기관(CA)으로, 누구나 완전 무료로 SSL 인증서를 발급받을 수 있게 했습니다. 2026년 현재 전 세계에서 가장 많이 사용되는 무료 SSL 인증서이며, 매일 수백만 개의 인증서가 발급되고 있습니다.
이 가이드에서는 Let's Encrypt를 활용하여 실제 서비스에 HTTPS를 적용하는 모든 과정을 다룹니다. Certbot 설치부터 Nginx/Apache 적용, 와일드카드 인증서, 자동 갱신, 그리고 자주 발생하는 문제와 해결 방법까지 실무에서 바로 활용할 수 있도록 상세히 설명합니다.
1. Let's Encrypt와 SSL 인증서 기초
1.1 Let's Encrypt의 특징
- 완전 무료: 발급, 갱신, 재발급 모두 무료
- 자동화: ACME 프로토콜로 발급/갱신 자동화 가능
- 유효 기간 90일: 상용 인증서(1~2년)보다 짧지만, 자동 갱신으로 해결
- 도메인 검증(DV)만 지원: 조직 검증(OV), 확장 검증(EV)은 미지원
- 와일드카드 지원:
*.example.com형식의 인증서 발급 가능 - 모든 주요 브라우저가 신뢰: Chrome, Firefox, Safari, Edge 모두 자동 신뢰
1.2 인증 방식 이해
Let's Encrypt는 도메인 소유 여부를 검증하는 챌린지(Challenge) 방식으로 인증서를 발급합니다:
| 챌린지 유형 | 검증 방식 | 용도 |
|---|---|---|
| HTTP-01 | 웹서버 특정 경로에 파일 배치하여 검증 | 가장 일반적, 단일 도메인 |
| DNS-01 | DNS TXT 레코드로 검증 | 와일드카드 인증서 필수, 자동화 가능 |
| TLS-ALPN-01 | TLS 핸드셰이크 중 검증 | 특수한 환경 (포트 80 차단 등) |
• 단일 도메인(example.com, www.example.com): HTTP-01 (가장 간단)
• 와일드카드(*.example.com): DNS-01 필수
• 내부망 서버, 포트 80 차단: DNS-01
1.3 발급 전 준비사항
- 도메인 소유권: 실제로 관리 가능한 도메인이 있어야 함
- DNS 설정: 도메인이 서버 IP로 올바르게 연결되어 있어야 함
- 포트 80/443 개방: HTTP-01 방식은 포트 80이 외부에서 접근 가능해야 함
- 루트 권한: 인증서 설치 및 웹서버 설정 변경을 위해 필요
2. Certbot 설치
Certbot은 EFF(Electronic Frontier Foundation)에서 개발한 Let's Encrypt 공식 클라이언트로, 인증서 발급과 관리를 자동화해줍니다.
2.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
# 또는 apt 방식 (간편)
sudo apt update
sudo apt install certbot -y
sudo apt install python3-certbot-nginx -y # Nginx 플러그인
sudo apt install python3-certbot-apache -y # Apache 플러그인 (필요시)
# 버전 확인
certbot --version
2.2 RHEL/Rocky Linux/CentOS 설치
# EPEL 저장소 활성화 (RHEL/CentOS 7)
sudo yum install epel-release -y
sudo yum install certbot python3-certbot-nginx -y
# RHEL 9 / Rocky Linux 9
sudo dnf install epel-release -y
sudo dnf install certbot python3-certbot-nginx -y
# snap 방식 (모든 버전 공통)
sudo dnf install snapd -y
sudo systemctl enable --now snapd.socket
sudo ln -s /var/lib/snapd/snap /snap
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
3. Nginx에 SSL 인증서 적용
3.1 자동 설정 (가장 쉬운 방법)
Certbot의 Nginx 플러그인을 사용하면 인증서 발급과 Nginx 설정 수정을 한 번에 처리할 수 있습니다:
# 사전 조건: Nginx에 해당 도메인의 server 블록이 이미 존재해야 함
# 예: server_name example.com www.example.com;
# 인증서 발급 + Nginx 자동 설정
sudo certbot --nginx -d example.com -d www.example.com
# 실행하면 아래 정보를 묻습니다:
# 1. 이메일 주소 입력 (만료 알림 수신용)
# 2. 이용약관 동의 (Y)
# 3. EFF 뉴스레터 수신 (N 가능)
# 4. HTTP → HTTPS 리다이렉트 설정 (2번 권장)
성공 시 아래와 비슷한 메시지가 출력됩니다:
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
This certificate expires on 2026-07-05.
Deploying certificate
Successfully deployed certificate for example.com
Successfully deployed certificate for www.example.com
Your existing server block has been modified to redirect HTTP traffic to HTTPS.
3.2 수동 설정 (인증서만 발급)
Nginx 설정을 직접 작성하고 싶다면, 인증서만 발급하고 설정은 수동으로 작성할 수 있습니다:
# webroot 방식 (Nginx가 돌아가는 상태에서)
sudo certbot certonly --webroot -w /var/www/example.com/html \
-d example.com -d www.example.com
# standalone 방식 (Nginx를 일시 중단하고 발급)
sudo systemctl stop nginx
sudo certbot certonly --standalone -d example.com -d www.example.com
sudo systemctl start nginx
# 수동 Nginx 설정 예시
# /etc/nginx/conf.d/example.com.conf
# HTTP → HTTPS 리다이렉트
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
# HTTPS 메인 설정
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Let's Encrypt 인증서 경로
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# SSL 최적 설정
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;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# HSTS (HTTPS 강제)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
root /var/www/example.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
# 설정 검증 후 적용
sudo nginx -t
sudo systemctl reload nginx
4. Apache에 SSL 인증서 적용
4.1 자동 설정
# Apache SSL 모듈 활성화 (Ubuntu/Debian)
sudo a2enmod ssl
sudo a2enmod headers
sudo systemctl reload apache2
# Certbot Apache 플러그인으로 자동 설정
sudo certbot --apache -d example.com -d www.example.com
4.2 수동 Apache 설정
# /etc/apache2/sites-available/example.com-ssl.conf
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/html
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# SSL 프로토콜 (TLS 1.2 이상)
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
# OCSP Stapling
SSLUseStapling on
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
# HSTS
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
ErrorLog ${APACHE_LOG_DIR}/example.com-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-ssl-access.log combined
</VirtualHost>
</IfModule>
# HTTP → HTTPS 리다이렉트
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
Redirect permanent / https://example.com/
</VirtualHost>
# 사이트 활성화
sudo a2ensite example.com-ssl.conf
# 설정 검증 후 적용
sudo apachectl configtest
sudo systemctl reload apache2
5. 와일드카드 인증서 발급 (DNS-01)
와일드카드 인증서(*.example.com)는 하나의 인증서로 모든 서브도메인(app.example.com, api.example.com 등)을 커버할 수 있어 매우 편리합니다. 단, DNS-01 챌린지만 지원합니다.
5.1 수동 DNS-01 발급
# 수동 DNS 챌린지
sudo certbot certonly --manual --preferred-challenges=dns \
-d example.com -d "*.example.com"
# Certbot이 아래 같은 메시지를 출력:
# Please deploy a DNS TXT record under the name:
# _acme-challenge.example.com
# with the following value:
# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# 위 TXT 레코드를 DNS 관리 패널에 수동으로 등록
# 등록 후 전파 확인 (Enter 누르기 전에)
dig -t txt _acme-challenge.example.com
# DNS 전파가 확인되면 Enter를 눌러 검증 진행
dig 명령으로 먼저 전파 여부를 확인한 후 Enter를 누르세요. 너무 일찍 누르면 검증 실패합니다.
5.2 DNS API 자동화 (Cloudflare 예시)
Cloudflare, Route53, Google Cloud DNS 등을 사용한다면 API로 자동화할 수 있습니다:
# Cloudflare 플러그인 설치
sudo snap install certbot-dns-cloudflare
# 또는
sudo apt install python3-certbot-dns-cloudflare -y
# Cloudflare API 토큰 저장 (/root/.secrets/cloudflare.ini)
sudo mkdir -p /root/.secrets
sudo cat > /root/.secrets/cloudflare.ini << 'EOF'
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
EOF
sudo chmod 600 /root/.secrets/cloudflare.ini
# 와일드카드 인증서 자동 발급
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d "*.example.com"
# Route53 플러그인 (AWS)
sudo snap install certbot-dns-route53
sudo certbot certonly \
--dns-route53 \
-d example.com -d "*.example.com"
# (IAM 자격증명이 환경변수나 ~/.aws/credentials에 있어야 함)
6. 인증서 자동 갱신
Let's Encrypt 인증서는 90일마다 갱신이 필요합니다. Certbot은 자동 갱신을 기본 지원합니다.
6.1 자동 갱신 확인
# 자동 갱신 테스트 (실제 갱신 없이 시뮬레이션)
sudo certbot renew --dry-run
# 강제 갱신 (만료 30일 이상 남은 경우에도)
sudo certbot renew --force-renewal
# 특정 도메인만 갱신
sudo certbot renew --cert-name example.com
# 자동 갱신 타이머 상태 (systemd)
sudo systemctl status certbot.timer
# cron 방식 (snap 버전 자동 설정)
systemctl list-timers | grep certbot
6.2 자동 갱신 구조
Certbot은 설치 시 자동 갱신 메커니즘을 설정합니다:
- systemd timer:
/etc/systemd/system/certbot.timer(snap/최신 설치) - cron:
/etc/cron.d/certbot(apt 설치) - 실행 주기: 하루 2번 (12시간마다)
- 갱신 조건: 만료 30일 이내인 인증서만 실제 갱신
# 수동으로 cron 등록 (필요시)
sudo crontab -e
# 매일 새벽 3시에 자동 갱신 체크
0 3 * * * /usr/bin/certbot renew --quiet --post-hook "systemctl reload nginx"
# 갱신 후 특정 명령 실행 (hook)
sudo certbot renew --post-hook "systemctl reload nginx"
sudo certbot renew --deploy-hook "systemctl reload nginx"
6.3 갱신 실패 알림 설정
# 이메일 알림 변경
sudo certbot update_account --email new@example.com
# 갱신 실패 시 스크립트 실행
# /etc/letsencrypt/renewal-hooks/deploy/notify.sh
#!/bin/bash
echo "Certificate renewed: $RENEWED_DOMAINS" | mail -s "SSL Renewal" admin@example.com
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/notify.sh
7. 인증서 관리 명령어
7.1 자주 쓰는 Certbot 명령어
# 발급된 인증서 목록 확인
sudo certbot certificates
# 특정 인증서 삭제
sudo certbot delete --cert-name example.com
# 인증서 재발급 (다른 도메인 추가)
sudo certbot certonly --expand -d example.com -d www.example.com -d blog.example.com
# ECDSA 키 사용 (RSA보다 빠르고 작음)
sudo certbot certonly --nginx --key-type ecdsa --elliptic-curve secp384r1 \
-d example.com -d www.example.com
# 특정 이메일로 등록
sudo certbot register --email admin@example.com --agree-tos
# 인증서 정보 상세 확인
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout
# 만료일 확인
echo | openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -dates
7.2 인증서 파일 구조
| 파일명 | 설명 |
|---|---|
cert.pem |
서버 인증서만 (단독) |
chain.pem |
중간 CA 인증서 체인 |
fullchain.pem |
서버 인증서 + 중간 체인 (Nginx/Apache용) |
privkey.pem |
개인 키 (절대 노출 금지!) |
privkey.pem은 절대로 Git 저장소, 공유 폴더, 외부 서비스 등에 업로드하면 안 됩니다. 유출 시 즉시 인증서를 재발급받고 기존 인증서는 폐기(revoke)해야 합니다.
8. HTTPS 설정 검증과 최적화
8.1 온라인 SSL 검사 도구
- SSL Labs (Qualys): https://www.ssllabs.com/ssltest/ - 가장 권위 있는 SSL 검사 도구. A+ 등급이 목표.
- Mozilla Observatory: https://observatory.mozilla.org/ - 종합 보안 점수
- testssl.sh: 로컬에서 실행 가능한 CLI 도구
- SSL Configuration Generator: https://ssl-config.mozilla.org/ - 최적 설정 자동 생성
# testssl.sh로 로컬 검사
git clone https://github.com/drwetter/testssl.sh.git
cd testssl.sh
./testssl.sh https://example.com
# 인증서 체인 확인
curl -vI https://example.com 2>&1 | grep -i "SSL\|certificate"
# 프로토콜/암호화 확인
nmap --script ssl-enum-ciphers -p 443 example.com
8.2 A+ 등급을 위한 추가 설정
# Strong DH Parameters 생성 (시간이 걸림)
sudo openssl dhparam -out /etc/nginx/dhparam.pem 2048
# Nginx 설정에 추가
ssl_dhparam /etc/nginx/dhparam.pem;
# 보안 헤더 (SSL Labs A+ 요구)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
8.3 HSTS Preload 등록
HSTS preload에 등록하면 사이트가 처음 접속하는 사용자에게도 브라우저가 무조건 HTTPS로 연결하도록 강제할 수 있습니다:
# 등록 조건 (모두 충족 필요)
1. 유효한 인증서 보유
2. HTTP → HTTPS 리다이렉트
3. 모든 서브도메인 HTTPS 제공
4. HSTS 헤더에 max-age=63072000 (2년) 이상
5. HSTS 헤더에 includeSubDomains 포함
6. HSTS 헤더에 preload 포함
# 등록 사이트: https://hstspreload.org/
9. 자주 발생하는 문제와 해결
9.1 "Connection refused" 오류
Problem: Failed authorization procedure.
example.com (http-01): urn:ietf:params:acme:error:connection :: The server could not connect to the client to verify the domain :: Fetching http://example.com/.well-known/acme-challenge/xxx: Connection refused
원인: 포트 80이 외부에서 접근 불가
해결:
- 방화벽에서 포트 80 허용:
sudo ufw allow 80/tcp - 클라우드 Security Group에서 HTTP 허용
- 도메인이 올바른 IP를 가리키는지 확인:
dig example.com
9.2 Rate Limit 초과
Problem: too many certificates already issued for exact set of domains
원인: Let's Encrypt의 발급 제한 도달
Let's Encrypt의 주요 Rate Limit:
- 같은 도메인 세트에 대해 주당 5회 중복 발급 제한
- 같은 등록 도메인당 주당 50개 인증서
- 실패한 검증 시도 시간당 5회
해결:
- 테스트 환경에서는
--staging옵션 사용 (제한 없음) - 시간을 기다린 후 재시도 (보통 1주일)
- 도메인을 추가하여 세트 변경
# 스테이징 환경에서 테스트
sudo certbot --nginx --staging -d example.com
# 스테이징 인증서는 실제 브라우저에서는 신뢰되지 않음
# 설정 완료 후 정식 발급:
sudo certbot --nginx --force-renewal -d example.com
9.3 DNS 검증 실패
Problem: DNS problem: NXDOMAIN looking up TXT for _acme-challenge.example.com
원인: DNS TXT 레코드가 전파되지 않음
해결:
# TXT 레코드 확인
dig -t txt _acme-challenge.example.com
# Google DNS로 확인
dig -t txt _acme-challenge.example.com @8.8.8.8
# Cloudflare DNS로 확인
dig -t txt _acme-challenge.example.com @1.1.1.1
# 모두 같은 값이 나오면 전파 완료
9.4 혼합 콘텐츠(Mixed Content) 경고
HTTPS 페이지에서 HTTP 리소스(이미지, 스크립트 등)를 로드하면 브라우저가 경고합니다.
해결:
- HTML에서
http://를//(프로토콜 상대)나https://로 변경 - CSP 헤더로 자동 업그레이드:
upgrade-insecure-requests
# Nginx에서 자동 업그레이드
add_header Content-Security-Policy "upgrade-insecure-requests" always;
9.5 인증서 갱신 실패
# 갱신 로그 확인
sudo journalctl -u certbot.timer
sudo cat /var/log/letsencrypt/letsencrypt.log
# 자주 있는 원인
# 1. 웹서버 설정 오류 → nginx -t / apachectl configtest
# 2. 방화벽 차단 → ufw status / firewall-cmd --list-all
# 3. 디스크 풀 → df -h
# 4. DNS 변경 → dig example.com
# 수동 강제 갱신
sudo certbot renew --force-renewal --cert-name example.com
10. 실전 운영 체크리스트
10.1 인증서 발급 후 확인사항
- ✅ 브라우저 접속 확인:
https://example.com이 경고 없이 열리는가 - ✅ 자물쇠 아이콘 확인: 주소창에 녹색 자물쇠가 표시되는가
- ✅ HTTP 리다이렉트 확인:
http://example.com이 HTTPS로 자동 이동하는가 - ✅ SSL Labs 등급 확인: 최소 A 등급, 가능하면 A+
- ✅ 자동 갱신 테스트:
certbot renew --dry-run이 성공하는가 - ✅ 인증서 만료일 모니터링: 만료 30일 전 알림 설정
- ✅ 백업:
/etc/letsencrypt/디렉토리 정기 백업
10.2 인증서 모니터링 스크립트
#!/bin/bash
# /usr/local/bin/check_ssl_expiry.sh - 인증서 만료 모니터링
DOMAINS=("example.com" "api.example.com" "blog.example.com")
WARNING_DAYS=30
ALERT_EMAIL="admin@example.com"
for domain in "${DOMAINS[@]}"; do
expiry_date=$(echo | openssl s_client -servername "$domain" \
-connect "$domain:443" 2>/dev/null | \
openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
now_epoch=$(date +%s)
days_left=$(( (expiry_epoch - now_epoch) / 86400 ))
echo "$domain: $days_left days remaining"
if [ $days_left -lt $WARNING_DAYS ]; then
echo "WARNING: $domain expires in $days_left days!" | \
mail -s "SSL Expiry Alert: $domain" "$ALERT_EMAIL"
fi
done
# 실행 권한 부여 및 cron 등록
sudo chmod +x /usr/local/bin/check_ssl_expiry.sh
# 매일 아침 9시에 실행
sudo crontab -e
# 0 9 * * * /usr/local/bin/check_ssl_expiry.sh
결론: Let's Encrypt로 모든 웹사이트에 HTTPS를
Let's Encrypt는 무료 SSL 인증서라는 개념을 넘어, 웹 전체의 보안 수준을 끌어올린 혁신입니다. 과거에는 비용 문제로 HTTPS를 포기했던 수많은 사이트들이 이제 모두 안전한 HTTPS로 서비스되고 있으며, 이는 사용자와 서비스 제공자 모두에게 이익이 되는 변화입니다.
이 가이드에서 다룬 핵심 포인트를 다시 정리하면:
- Certbot으로 자동화하라 - 수동 발급과 갱신은 실수의 여지가 많습니다. Certbot의 자동 갱신을 반드시 활용하세요.
- 와일드카드는 DNS-01로 - 여러 서브도메인을 운영한다면 와일드카드 인증서가 훨씬 편합니다.
- 모니터링을 설정하라 - 자동 갱신이 설정되어 있어도, 실패할 수 있으므로 만료 모니터링은 필수입니다.
- SSL Labs A+ 등급을 목표로 - 단순히 HTTPS만 적용하는 것이 아니라, 최적화된 SSL 설정까지 함께 적용하세요.
- HSTS는 신중하게 - 한번 활성화하면 되돌리기 어려우므로, 모든 설정이 완벽할 때 적용하세요.
- 개인 키는 철저히 보호하라 -
privkey.pem은 절대 외부에 노출되면 안 됩니다.
Let's Encrypt와 Certbot을 활용하면 몇 분 만에 프로덕션 수준의 HTTPS를 구축할 수 있습니다. 이 가이드의 예제를 여러분의 환경에 맞게 적용해 보시고, 안전한 웹 서비스를 운영하시기 바랍니다.