[Certbot] 무료 SSL인증서 발급 + 자동(강제)갱신 (Nginx 컨테이너 환경)

WEBNginxServer
by hwaneehwanee·Jun 15, 2026

들어가며

이번 글에서는 Docker 환경에서 Nginx 컨테이너와 Next.js 애플리케이션을 구성한 뒤, Certbot을 이용해 무료 SSL 인증서를 발급하고 자동 갱신하는 과정을 정리해보려고 한다.

실습 환경은 다음과 같다.

  • GCP Compute Engine (Ubuntu)
  • Docker & Docker Compose
  • Nginx Container
  • Next.js
  • Certbot (Let's Encrypt)

추가로, 이번 실습을 진행하기 위해서는 SSL 인증서를 발급받을 도메인이 필요하다.

준비사항

  • 아래 과정은 모두 서버에서 진행한다.

Docker & Docker Compose 설치

sudo apt update

curl -fsSL https://get.docker.com | sh

sudo systemctl enable docker
sudo systemctl start docker

// 현재 유저 권한 부여
sudo usermod -aG docker $USER

// 위 권한 부여후 서버를 재접속한다.

// 확인
docker --version
docker compose version

Node.js 설치

// 설치
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs

// 버전 확인
node -v

Next.js 프로젝트 생성

npx create-next-app@latest certbot-test-app

프로젝트 빌드 및 백그라운드 실행

  • 실무에서는 PM2로 프로세스를 관리하겠지만 해당 글에서는 직접 실행한다.
cd {프로젝트}

// 빌드
npm run build

// 백그라운드 실행 (로그없이)
nohup npm start > /dev/null 2>&1 &

Nginx 컨테이너환경 구성

디렉토리 구조

nginx/
├── config/
│   ├──certbot-test-app.conf
├── certbot/
│   ├── www
│   ├── config
├── docker-compose.yml

nginx 설정파일작성

  • {도메인}은 작업하려는 도메인을 입력한다.
# certbot-test-app.conf
upstream certbot-test-app {
    server host.docker.internal:3000;
}

server {
    listen 80;
    server_name {도메인};

    location / {
        proxy_pass http://certbot-test-app;
        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.yml 파일 작성

services:
  nginx:
    image: nginx:latest
    container_name: certbot-test-app-nginx
    restart: always

    ports:
      - "80:80"

    volumes:
      - ./config/certbot-test-app.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/www:/var/www/certbot
      - ./certbot/config:/etc/letsencrypt

    extra_hosts:
      - "host.docker.internal:host-gateway"

컨테이너 실행 및 확인

// docker-compose.yml 파일이 있는 위치에서 실행
docker compose up -d

// 확인
docker ps

image.png

도메인 DNS설정(DNS 레코드 등록)

  • 도메인을 구매한 사이트에서 DNS 설정을 진행한다. 서버IP는 외부IP로 등록한다.

외부에서 확인
이제 curl로 외부에서 도메인으로 접근하면, nginx 인덱스 페이지가 아닌 Nextjs 애플리케이션의 인덱스 페이지가 응답으로 반환되게 된다.

curl http://{도메인}

image.png


Certbot으로 SSL 인증서 최초 발급

앞서 Nginx 컨테이너와 Next.js를 구성하고 도메인까지 연결했다면 이제 HTTPS 적용을 위한 SSL 인증서를 발급할 차례다.

Docker 환경에서는 인증서 파일과 Challenge 파일을 Nginx 컨테이너와 공유하기 위해 Volume을 사용한다.


Certbot 설치

sudo apt update

sudo apt install certbot -y

설치 확인

certbot --version

ACME Challenge 경로 설정

Let's Encrypt는 도메인 소유 여부를 확인하기 위해 아래 URL로 접근한다.

http://{도메인}/.well-known/acme-challenge/{파일명}

따라서 Nginx가 해당 경로를 Next.js가 아닌 실제 파일 경로로 처리하도록 설정해야 한다.


Nginx 설정 수정

certbot-test-app.conf 설정 파일에 Challenge 경로를 추가한다.

upstream certbot-test-app {
    server host.docker.internal:3000;
}

server {
    listen 80;
    server_name {도메인};

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

    location / {
        proxy_pass http://certbot-test-app;
        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 down
docker compose up -d

Challenge 경로 확인

먼저 Challenge 파일이 저장될 디렉터리를 생성한다.

mkdir -p certbot/www/.well-known/acme-challenge

테스트 파일을 생성한다.

echo "hello certbot" > \
certbot/www/.well-known/acme-challenge/test

외부에서 접근이 가능한지 확인한다.

curl http://{도메인}/.well-known/acme-challenge/test

정상이라면 아래와 같이 응답한다.

hello certbot

image.png

이 단계가 정상 동작하지 않으면 인증서 발급도 실패한다.


인증서 발급 스크립트 작성

#!/bin/bash

if [ $# -ne 1 ]; then
  echo "사용법: $0 <DOMAIN>"
  exit 1
fi

DOMAIN=$1

WEBROOT="$(pwd)/certbot/www"
EMAIL="{이메일주소}"

if [ ! -d "$WEBROOT" ]; then
  echo "WEBROOT 경로 없음: $WEBROOT"
  exit 1
fi

sudo certbot certonly \
  --webroot \
  -w "$WEBROOT" \
  -d "$DOMAIN" \
  --non-interactive \
  --agree-tos \
  -m "$EMAIL"

if [ $? -eq 0 ]; then

  echo ""
  echo "인증서 복사 중..."

  sudo mkdir -p ./certbot/config/live/$DOMAIN

  sudo cp \
    /etc/letsencrypt/live/$DOMAIN/fullchain.pem \
    ./certbot/config/live/$DOMAIN/

  sudo cp \
    /etc/letsencrypt/live/$DOMAIN/privkey.pem \
    ./certbot/config/live/$DOMAIN/

  echo ""
  echo "인증서 발급 성공"

  echo ""
  echo "인증서 경로"
  echo "./certbot/config/live/$DOMAIN/fullchain.pem"
  echo "./certbot/config/live/$DOMAIN/privkey.pem"

else
  echo "인증서 발급 실패"
  exit 1
fi

실행 권한 부여

chmod +x certbot-init.sh

실행

./certbot-init.sh {도메인}

image.png

최초 발급 스크립트를 실행한후 ssl 관련 파일이 들어온것을 확인할수 있다. image.png


발급한 SSL 인증서를 Nginx 컨테이너에 적용하기

이전 단계에서 Certbot을 통해 SSL 인증서를 발급받았다.

이번에는 발급된 인증서를 Nginx 컨테이너에 적용하여 HTTPS로 서비스를 제공해보자.

발급된 인증서 확인

Certbot으로 발급한 인증서는 아래 경로에 복사되어 있다.

./certbot/config/live/{도메인}/fullchain.pem
./certbot/config/live/{도메인}/privkey.pem

각 파일의 역할은 다음과 같다.

파일설명
fullchain.pem서버 인증서 + 중간 인증서
privkey.pem개인키

Nginx HTTPS 설정 추가

기존 설정 파일을 수정한다.

upstream certbot-test-app {
    server host.docker.internal:3000;
}

# HTTP → HTTPS 리다이렉트
server {
    listen 80;
    server_name {도메인};

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

    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS 서비스
server {
    listen 443 ssl http2;
    server_name {도메인};

    ssl_certificate /etc/letsencrypt/live/{도메인}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{도메인}/privkey.pem;

    location / {
        proxy_pass http://certbot-test-app;
        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;
    }
}

image.png


Docker Compose 수정

HTTPS 서비스를 위해 443 포트를 추가한다.

services:
  nginx:
    image: nginx:latest
    container_name: certbot-test-app-nginx
    restart: always

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - ./config/certbot-test-app.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certbot/www:/var/www/certbot
      - ./certbot/config:/etc/letsencrypt

    extra_hosts:
      - "host.docker.internal:host-gateway"

Nginx 컨테이너 재시작

설정 파일과 인증서가 변경되었으므로 컨테이너를 재시작한다.

docker compose down

docker compose up -d

실행 상태를 확인한다.

docker ps

image.png


HTTPS 접속 확인

브라우저에서 아래 주소로 접속한다.

https://{도메인}

// ex
https://test.hwaneehwanee.app

정상적으로 적용되었다면 주소창에 자물쇠 아이콘이 표시되고 HTTPS로 접속된다.

이제 서비스는 HTTPS를 통해 암호화된 통신을 사용할 수 있게 되었다. 다음 단계에서는 Certbot 인증서 자동 갱신 설정과 갱신 테스트를 진행해보자.


Certbot 인증서 강제 갱신 및 적용

Certbot으로 발급한 인증서는 기본적으로 만료일이 가까워졌을 때만 갱신된다.

따라서 인증서를 처음 구성하거나 자동 갱신 설정을 테스트하는 경우에는 갱신이 수행되지 않을 수 있다.

이번 글에서는 인증서를 강제로 재발급하여 갱신 프로세스와 Nginx 컨테이너 적용 여부를 확인해보자.


기본 갱신 명령

일반적으로 Certbot은 아래 명령을 통해 갱신한다.

sudo certbot renew

하지만 인증서 만료일이 충분히 남아있다면 아래와 같은 메시지를 출력하며 아무 작업도 수행하지 않는다.

image.png

이는 정상 동작이다.


강제 갱신 스크립트 작성

갱신 시점을 기다리지 않고 즉시 재발급하려면 --force-renewal 옵션을 사용할 수 있다.

#!/bin/bash

set -e

# -------------------------
# 사용법 체크
# -------------------------
if [ $# -ne 1 ]; then
  echo "사용법: $0 <DOMAIN>"
  echo "예: $0 home.onecall1.co.kr"
  exit 1
fi

DOMAIN=$1

EMAIL="{이메일주소}"
WEBROOT="$(pwd)/certbot/www"

echo "======================================"
echo "Certbot FORCE RENEW START"
echo "DOMAIN: $DOMAIN"
echo "WEBROOT: $WEBROOT"
echo "======================================"

# -------------------------
# webroot 확인
# -------------------------
if [ ! -d "$WEBROOT" ]; then
  echo "WEBROOT 경로 없음: $WEBROOT"
  exit 1
fi

# -------------------------
# certbot 실행 (강제 재발급)
# -------------------------
echo ""
echo "Running certbot (force renewal)..."
echo ""

sudo certbot certonly \
  --webroot \
  -w "$WEBROOT" \
  -d "$DOMAIN" \
  --force-renewal \
  --non-interactive \
  --agree-tos \
  -m "$EMAIL"

# -------------------------
# 인증서 복사
# -------------------------
echo ""
echo "Copy certificate..."
echo ""

sudo cp \
  /etc/letsencrypt/live/$DOMAIN/fullchain.pem \
  ./certbot/config/live/$DOMAIN/

sudo cp \
  /etc/letsencrypt/live/$DOMAIN/privkey.pem \
  ./certbot/config/live/$DOMAIN/

# -------------------------
# nginx 재적용
# -------------------------
echo ""
echo "Restart nginx container..."
echo ""

docker compose restart nginx

echo ""
echo "======================================"
echo "Certbot 재발급 완료!"
echo "DOMAIN: $DOMAIN"
echo "======================================"

실행 권한 부여

chmod +x certbot-force-renew.sh

강제 갱신 실행

./certbot-force-renew.sh {도메인}

// ex
./certbot-force-renew.sh test.hwaneehwanee.app

정상적으로 완료되면 새로운 인증서가 발급되고 Nginx 컨테이너가 재시작되면서 새 인증서가 적용된다. image.png


Certbot 자동 갱신 설정 (Cron)

이전 글에서는 Certbot 인증서를 강제로 재발급하여 갱신 프로세스를 확인했다.

실제 운영 환경에서는 주기적으로 Certbot을 실행하여 인증서 만료 전에 자동으로 갱신되도록 구성하는 것이 일반적이다.

이번 글에서는 Cron을 이용하여 매월 자동으로 인증서 갱신을 수행하도록 설정해보자.

Cron 등록

0 3 1 * * /home/{user}/certbot-force-renew.sh >> /var/log/certbot-force-renew.log 2>&1

마무리

이번 글에서는 Nginx가 컨테이너 환경일때 Certbot 인증서를 발급받고 강제로 갱신해보았다.

지금까지 진행한 내용을 정리하면 다음과 같다.

  • 컨테이너 환경의 Nginx 설치
  • Next.js 애플리케이션 연결
  • Certbot을 이용한 SSL 인증서 최초 발급
  • 컨테이너 환경의 Nginx HTTPS 설정 및 인증서 적용
  • 인증서 강제 갱신 테스트
  • Cron을 이용한 강제 자동 갱신 설정

따라서 매월 한 번 정도 강제갱신을 실행하도록 설정해두면 별도의 수동 작업 없이 안정적으로 HTTPS 서비스를 운영해볼 수 있다.

연관 포스트