EC2에서 Let's Encrypt와 Nginx로 HTTPS 설정하는 방법 (Certbot 완전 가이드)
브라우저 주소창에 '주의 요함' 또는 'Not Secure' 경고가 뜨는 순간, 사용자 이탈은 시작된다. EC2 위에서 Nginx로 서비스를 운영 중인데 아직 HTTP만 열려 있다면, Let's Encrypt의 무료 SSL 인증서와 Certbot을 이용해 30분 안에 HTTPS를 완성할 수 있다. 이 글은 실제 운영 환경에서 겪는 함정 — 보안 그룹 누락, 도메인 검증 실패, 자동 갱신 설정 오류 — 까지 포함해 처음부터 끝까지 다룬다.
TL;DR — EC2 HTTPS 설정 핵심 요약
| 단계 | 작업 | 주요 확인 포인트 |
|---|---|---|
| 1 | 보안 그룹에서 443 포트 개방 | 인바운드 규칙 HTTPS(443) 추가 |
| 2 | 도메인 A 레코드를 EC2 퍼블릭 IP로 연결 | DNS 전파 확인 필수 |
| 3 | Nginx 설치 및 서버 블록 구성 | server_name에 실제 도메인 입력 |
| 4 | Certbot 설치 및 인증서 발급 | --nginx 플러그인 사용 |
| 5 | 자동 갱신 크론 또는 systemd 타이머 확인 | certbot renew --dry-run 통과 여부 |
HTTPS 인증서 발급이 작동하는 방식
Let's Encrypt는 ACME(Automated Certificate Management Environment) 프로토콜로 도메인 소유권을 검증한다. Certbot의 --nginx 플러그인은 HTTP-01 챌린지를 사용하는데, 이 과정에서 Let's Encrypt 서버가 외부에서 http://<your-domain>/.well-known/acme-challenge/ 경로에 접근할 수 있어야 한다. 즉, EC2 보안 그룹의 80번 포트가 열려 있지 않으면 챌린지 자체가 실패한다 — 이게 가장 흔한 첫 번째 함정이다.
- 클라이언트(Certbot)가 Let's Encrypt CA에 인증서 발급을 요청한다.
- Let's Encrypt CA가 HTTP-01 챌린지 토큰을 발급한다.
- Certbot이 Nginx를 통해 챌린지 파일을
/.well-known/acme-challenge/경로에 임시 노출한다. - Let's Encrypt CA가 외부에서 해당 URL을 HTTP(80)로 직접 조회해 토큰을 검증한다.
- 검증 성공 시 인증서(cert.pem, privkey.pem 등)를 발급하고, Certbot이 Nginx 설정을 자동으로 수정한다.
- 이후 클라이언트는 443 포트로 TLS 핸드셰이크를 수행한다.
사전 조건 확인 — 시작 전 체크리스트
인증서 발급 전에 아래 세 가지가 반드시 충족되어야 한다. 하나라도 빠지면 Certbot이 중간에 멈춘다.
- 도메인 소유: EC2 퍼블릭 IP 또는 Elastic IP를 가리키는 A 레코드가 DNS에 등록되어 있어야 한다. Route 53이든 외부 레지스트라든 상관없다.
- 보안 그룹 인바운드 규칙: TCP 80(HTTP)과 TCP 443(HTTPS) 모두
0.0.0.0/0또는 필요한 CIDR에서 허용되어야 한다. - EC2 인스턴스 OS: 이 가이드는 Amazon Linux 2023 및 Ubuntu 22.04/24.04 기준이다. OS에 따라 패키지 관리자 명령이 다르다.
DNS 전파 확인
# 도메인이 EC2 IP를 가리키는지 확인
dig +short your-domain.com A
# 또는
nslookup your-domain.com
반환된 IP가 EC2 퍼블릭 IP와 일치해야 한다. 일치하지 않으면 인증서 발급을 시도하지 말 것 — 챌린지가 반드시 실패한다.
보안 그룹 인바운드 규칙 확인 (AWS CLI)
aws ec2 describe-security-groups \
--group-ids sg-0123456789abcdef0 \
--query 'SecurityGroups[*].IpPermissions[?FromPort==`80` || FromPort==`443`]' \
--region us-east-1
80과 443 모두 결과에 나타나야 한다. 없다면 아래 명령으로 추가한다.
# HTTP(80) 허용
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp \
--port 80 \
--cidr 0.0.0.0/0 \
--region us-east-1
# HTTPS(443) 허용
aws ec2 authorize-security-group-ingress \
--group-id sg-0123456789abcdef0 \
--protocol tcp \
--port 443 \
--cidr 0.0.0.0/0 \
--region us-east-1
Step 1 — Nginx 설치 및 기본 서버 블록 구성
Certbot의 --nginx 플러그인은 기존 Nginx 설정을 파싱해서 HTTPS 블록을 자동으로 추가한다. 그러려면 Certbot이 실행되기 전에 server_name에 실제 도메인이 정확히 입력된 서버 블록이 존재해야 한다. 이 단계를 건너뛰고 Certbot을 먼저 실행하면 플러그인이 어느 블록을 수정해야 할지 찾지 못한다.
Amazon Linux 2023
sudo dnf install -y nginx
sudo systemctl enable --now nginx
Ubuntu 22.04 / 24.04
sudo apt update
sudo apt install -y nginx
sudo systemctl enable --now nginx
서버 블록 생성 (Ubuntu 기준 경로)
🔽 Nginx 서버 블록 설정 파일 보기 (클릭해서 펼치기)
sudo vim /etc/nginx/sites-available/your-domain.com
server {
listen 80;
listen [::]:80;
server_name your-domain.com www.your-domain.com;
root /var/www/your-domain.com/html;
index index.html index.htm;
location / {
try_files $uri $uri/ =404;
}
}
# 심볼릭 링크 생성 (Ubuntu)
sudo ln -s /etc/nginx/sites-available/your-domain.com /etc/nginx/sites-enabled/
# 설정 문법 검사
sudo nginx -t
# Nginx 재시작
sudo systemctl reload nginx
Amazon Linux 2023에서는 /etc/nginx/conf.d/your-domain.conf에 직접 파일을 생성하면 된다. sites-available/sites-enabled 구조는 Ubuntu 계열 기본 설정이다.
Step 2 — Certbot 설치
Certbot 공식 권장 설치 방법은 snap 패키지다. OS 패키지 저장소의 Certbot은 버전이 오래된 경우가 있어 플러그인 호환성 문제가 생길 수 있다.
Amazon Linux 2023 — snap 미지원, pip 사용
sudo dnf install -y python3 augeas-libs
sudo python3 -m venv /opt/certbot/
sudo /opt/certbot/bin/pip install --upgrade pip
sudo /opt/certbot/bin/pip install certbot certbot-nginx
sudo ln -s /opt/certbot/bin/certbot /usr/bin/certbot
Ubuntu 22.04 / 24.04 — snap 사용
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
Step 3 — SSL 인증서 발급 (핵심 단계)
이 명령 하나가 도메인 검증, 인증서 발급, Nginx 설정 수정, HTTP→HTTPS 리다이렉트 추가를 모두 처리한다.
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
실행하면 이메일 주소 입력과 이용약관 동의를 묻는다. 이후 Certbot이 HTTP-01 챌린지를 수행하고, 성공하면 아래와 유사한 메시지가 출력된다.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your-domain.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your-domain.com/privkey.pem
This certificate expires on 2025-10-XX.
Certbot이 자동으로 수정한 Nginx 설정을 확인해보면 443 블록과 HTTP→HTTPS 301 리다이렉트가 추가되어 있다.
sudo nginx -t && sudo systemctl reload nginx
Step 4 — 자동 갱신 설정 확인
Let's Encrypt 인증서 유효기간은 90일이다. Certbot은 설치 시 자동으로 갱신 작업을 등록하지만, 실제로 작동하는지 반드시 직접 확인해야 한다. 갱신에 실패해도 즉각적인 알림이 없고, 90일 후 인증서가 만료되어서야 문제를 발견하는 경우가 흔하다.
dry-run으로 갱신 시뮬레이션
sudo certbot renew --dry-run
Congratulations, all simulated renewals succeeded 메시지가 나와야 정상이다.
systemd 타이머 확인 (Ubuntu snap 설치 기준)
sudo systemctl status snap.certbot.renew.timer
크론 확인 (Amazon Linux 2023 pip 설치 기준)
sudo crontab -l
자동으로 등록되지 않았다면 수동으로 추가한다.
echo '0 0,12 * * * root /opt/certbot/bin/python -c "import random; import time; time.sleep(random.random() * 3600)" && sudo certbot renew -q' | sudo tee -a /etc/crontab
실제 운영에서 마주친 실패 패턴 — 오진에서 실제 원인까지
처음에는 Certbot 오류 메시지를 보고 Nginx 설정 문제라고 판단했다. nginx -t는 통과하고, 서버 블록도 정상이었다. 그런데 계속 챌린지 실패가 떴다.
증상: certbot --nginx 실행 시 아래 오류 반복
Challenge failed for domain your-domain.com
http-01 challenge for your-domain.com
Cleaning up challenges
Some challenges have failed.
오진: Nginx server_name 설정이 잘못됐거나 Certbot 버전 문제라고 추정했다.
실제 원인: EC2 보안 그룹에서 TCP 80이 막혀 있었다. Let's Encrypt CA가 외부에서 챌린지 URL을 조회할 수 없었던 것이다. Nginx는 인스턴스 내부에서 정상 동작하고 있었기 때문에 로컬 테스트로는 문제를 발견할 수 없었다.
확인 방법: EC2 외부에서 curl로 직접 챌린지 경로 접근을 테스트한다.
# 외부 머신 또는 Cloud Shell에서 실행
curl -v http://your-domain.com/.well-known/acme-challenge/test
연결 자체가 타임아웃되면 보안 그룹 문제다. 404가 반환되면 Nginx는 정상이고 챌린지 파일 경로 문제다.
보안 그룹은 인스턴스 외부의 방화벽이다. Nginx가 내부에서 잘 돌아간다고 해서 외부 트래픽이 도달한다는 보장이 없다. Let's Encrypt는 항상 인터넷에서 직접 접근한다.
최종 Nginx 설정 확인 — Certbot 수정 후 결과
Certbot이 자동으로 수정한 설정이 의도한 대로 되어 있는지 검토하는 것이 좋다. 특히 리버스 프록시(Node.js, Gunicorn 등)를 사용하는 경우 proxy_pass 블록이 443 서버 블록 안에 있어야 한다.
🔽 Certbot 적용 후 Nginx 설정 예시 보기 (클릭해서 펼치기)
server {
listen 80;
server_name your-domain.com www.your-domain.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name your-domain.com www.your-domain.com;
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/your-domain.com/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
HTTPS 동작 최종 검증
설정이 완료되면 아래 세 가지로 실제 동작을 확인한다.
# 1. HTTPS 응답 확인
curl -I https://your-domain.com
# 2. HTTP → HTTPS 리다이렉트 확인
curl -I http://your-domain.com
# Location: https://your-domain.com/ 가 반환되어야 함
# 3. 인증서 만료일 확인
sudo certbot certificates
301 Redirect"] NGX80 -->|"HTTPS :443"| NGX443["Nginx :443
TLS 종료"] NGX443 -->|"인증서 검증"| CERT["Let's Encrypt
인증서"] NGX443 -->|"정적 파일 또는 프록시"| APP["애플리케이션
백엔드"] CRON["Certbot 자동 갱신
(systemd/cron)"] -->|"90일마다"| CERT
- 클라이언트가 HTTP(80)로 접속하면 Nginx가 301 리다이렉트로 HTTPS(443)로 보낸다.
- HTTPS 요청에서 TLS 핸드셰이크가 발생하고, Let's Encrypt 인증서로 신뢰가 검증된다.
- Nginx가 요청을 처리하거나 백엔드 애플리케이션으로 프록시한다.
- 90일마다 Certbot이 자동으로 인증서를 갱신하고 Nginx를 재로드한다.
EC2 HTTPS 설정 마무리 및 다음 단계
이제 브라우저 주소창에 자물쇠 아이콘이 표시되고, HTTP 접속은 자동으로 HTTPS로 리다이렉트된다. 여기서 멈추지 말고 아래 항목을 추가로 검토하면 보안과 성능이 한 단계 더 올라간다.
- Elastic IP 사용: EC2를 재시작하면 퍼블릭 IP가 바뀐다. Elastic IP를 할당하지 않으면 DNS A 레코드를 다시 수정해야 하고, 인증서 갱신도 실패할 수 있다.
- HSTS 헤더 추가: Nginx 443 블록에
add_header Strict-Transport-Security "max-age=31536000" always;를 추가하면 브라우저가 HTTP 접속 자체를 차단한다. - ALB + ACM 전환 검토: 트래픽이 늘어나면 Application Load Balancer와 AWS Certificate Manager(ACM)를 조합하는 것이 운영 부담이 적다. ACM 인증서는 자동 갱신되고 EC2 인스턴스에 직접 설치할 필요가 없다.
공식 Certbot 문서: https://certbot.eff.org/
핵심 용어 정리
| 용어 | 설명 |
|---|---|
| Let's Encrypt | 무료 SSL/TLS 인증서를 발급하는 비영리 CA(Certificate Authority) |
| Certbot | Let's Encrypt 인증서 발급 및 갱신을 자동화하는 EFF 공식 클라이언트 도구 |
| HTTP-01 챌린지 | 도메인 소유권을 검증하는 ACME 프로토콜 방식. 80번 포트로 특정 URL에 접근 가능해야 함 |
| ACME | Automated Certificate Management Environment. 인증서 발급 자동화 프로토콜 (RFC 8555) |
| Elastic IP | AWS에서 제공하는 고정 퍼블릭 IPv4 주소. EC2 재시작 후에도 IP가 유지됨 |
댓글
댓글 쓰기