Hetzner 서버에 도메인 연결하고 Certbot으로 HTTPS SSL 인증서 발급까지 Docker Compose 환경에서 실제 삽질 기반으로 정리했어요.
이전글 : [서버, 인프라] - 서버에 내 도메인 달기 — 도메인 구매부터 DNS 연결까지 (6편)
5편에서 Jenkins CI/CD 파이프라인까지 완성했잖아요.
근데 아직 http://서버IP 로 접속하는 거라 좀 아쉽거든요. 도메인 연결하고 HTTPS까지 붙여야 진짜 서비스처럼 보이죠.
이번 편에서 도메인 연결이랑 SSL 인증서 발급까지 다 끝낼게요. 근데 Docker Compose 환경에서 Certbot 쓰는 게 생각보다 설정할 게 좀 있거든요. 그 부분도 다 정리해뒀어요.
전체 순서 먼저 잡고 갈게요
도메인 DNS 설정 (A 레코드 → 서버 IP)
→ Certbot 컨테이너 추가
→ SSL 인증서 발급
→ Nginx HTTPS 설정
→ HTTP → HTTPS 리다이렉트
→ Hetzner 방화벽 443 포트 오픈
1단계 — 도메인 DNS 설정
도메인 등록 업체(가비아, 호스팅케이알 등) 콘솔에서 A 레코드 추가하면 돼요.
- 호스트: @ (루트 도메인)
- 타입: A
- 값: 서버 IP
- TTL: 3600 (기본값)
서브도메인도 쓸 거면 추가로 등록해요. 저는 Jenkins용 서브도메인도 따로 잡았어요.
- 호스트: jenkins
- 타입: A
- 값: 서버 IP (동일)
DNS 전파는 보통 5~10분 걸려요. 아래 명령어로 확인해보면 돼요.
curl -I http://도메인주소
200 OK 나오면 도메인이 서버로 잘 연결된 거예요.
2단계 — Certbot 컨테이너 추가
여기서 잠깐. Docker Compose로 Nginx를 띄우고 있으면 Certbot을 서버에 직접 설치하면 안 돼요.
Certbot이 80포트를 직접 쓰려고 하는데 Nginx 컨테이너가 이미 80포트를 점유하고 있거든요. 충돌 나요.
그래서 Certbot도 컨테이너로 올리는 방식으로 가요.
인증서 저장 디렉토리 먼저 만들게요.
mkdir -p /opt/app/certbot/conf
mkdir -p /opt/app/certbot/www
docker-compose.yml에 Certbot 추가하고 Nginx에 443 포트랑 볼륨 마운트도 추가해요.
services:
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
depends_on:
- front-app
- api-app
certbot:
image: certbot/certbot
container_name: certbot
volumes:
- ./certbot/conf:/etc/letsencrypt
- ./certbot/www:/var/www/certbot
front-app:
image: front-app
container_name: front-app
restart: unless-stopped
ports:
- "10000:10000"
api-app:
image: api-app
container_name: api-app
restart: unless-stopped
ports:
- "10081:10081"
3단계 — Nginx에 Certbot 인증 경로 추가
인증서 발급 전에 Nginx 설정에 .well-known/acme-challenge/ 경로를 추가해줘야 해요. Certbot이 도메인 소유권 확인할 때 이 경로를 쓰거든요.
server {
listen 80;
server_name 도메인주소;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://front-app:10000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/ {
proxy_pass http://api-app:10081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
서브도메인(Jenkins 등)도 쓴다면 별도 server 블록 추가해요.
server {
listen 80;
server_name jenkins.도메인주소;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
proxy_pass http://서버IP:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
설정 적용하고 재시작해요.
docker compose -f /opt/app/docker-compose.yml down
docker compose -f /opt/app/docker-compose.yml up -d

4단계 — SSL 인증서 발급
여러 도메인을 한 번에 발급할 수 있어요. -d 옵션으로 추가하면 돼요.
docker compose -f /opt/app/docker-compose.yml run --rm certbot certonly --webroot \
-w /var/www/certbot \
-d 도메인주소 \
-d jenkins.도메인주소 \
--email 본인이메일@gmail.com \
--agree-tos \
--no-eff-email
Successfully received certificate 나오면 발급 완료예요.
5단계 — Nginx HTTPS 설정
인증서 발급됐으면 Nginx에 443 설정 추가하고 HTTP → HTTPS 리다이렉트 잡아줄게요.
server {
listen 80;
server_name 도메인주소;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name 도메인주소;
ssl_certificate /etc/letsencrypt/live/도메인주소/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/도메인주소/privkey.pem;
location / {
proxy_pass http://front-app:10000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /api/ {
proxy_pass http://api-app:10081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name jenkins.도메인주소;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
server_name jenkins.도메인주소;
ssl_certificate /etc/letsencrypt/live/도메인주소/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/도메인주소/privkey.pem;
location / {
proxy_pass http://서버IP:8090;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Nginx 재시작해요.
docker compose -f /opt/app/docker-compose.yml restart nginx
6단계 — Hetzner 방화벽 443 포트 오픈
여기서 잠깐. 이거 빠뜨리면 curl: (28) Failed to connect to 도메인주소 port 443 이 나와요.
Nginx 설정 다 잘 됐는데 접속이 안 되면 방화벽 문제예요. 저도 이것 때문에 잠깐 헤맸거든요.
Hetzner 콘솔 → Firewalls → 해당 방화벽 → Rules → Add Rule
- Direction: Inbound
- Protocol: TCP
- Port: 443
- Source IP: 0.0.0.0/0
저장하면 바로 적용돼요.

7단계 — 인증서 자동 갱신 설정
Let's Encrypt 인증서는 90일마다 만료돼요. 자동 갱신 설정해두지 않으면 90일 후에 HTTPS가 갑자기 안 돼요.
크론탭으로 자동 갱신 등록할게요.
crontab -e
아래 내용 추가해요.
0 3 * * * docker compose -f /opt/app/docker-compose.yml run --rm certbot renew && docker compose -f /opt/app/docker-compose.yml restart nginx
매일 새벽 3시에 인증서 갱신 체크하고 만료 30일 전이면 자동으로 갱신해줘요.
삽질 포인트 정리
1. Docker Compose 환경에서 Certbot 직접 설치 금지
Nginx 컨테이너가 80포트 점유 중이라 충돌 나요. Certbot도 컨테이너로 올려야 해요.
2. .well-known/acme-challenge/ 경로 필수
인증서 발급 전에 Nginx 설정에 이 경로 없으면 도메인 소유권 확인 실패해요.
3. Hetzner 방화벽 443 포트
Nginx 설정 다 맞아도 방화벽에서 443 안 열면 접속 안 돼요. 꼭 확인해야 해요.
4. 인증서 자동 갱신
90일 만료니까 크론탭 설정 꼭 해두세요. 안 하면 나중에 갑자기 HTTPS 죽어요.
최종 확인
curl -I https://도메인주소
HTTP/2 200 나오면 HTTPS 완전히 적용된 거예요.
결국 핵심은 세 가지예요. Certbot도 컨테이너로 올리고, Nginx에 acme-challenge 경로 추가하고, Hetzner 방화벽 443 포트 열어주는 것. 이 세 가지만 빠뜨리지 않으면 돼요.
다음 편에서는 OAuth2 소셜 로그인 (카카오, 네이버, Google, Facebook, GitHub) 리다이렉트 URI 업데이트 방법 다뤄볼게요.