docker+certbot+nginx 로 인증서 발급 및 자동갱신하기

mmoo·2022년 7월 2일
1
post-thumbnail

Docker로 일회성 certbot 컨테이너를 띄워 인증서를 발급하고 크론탭으로 자동 갱신하도록 설정해 보자🤗
( nginx 컨테이너를 띄워 리버스 프록시를 사용하고 있는 환경에서 진행할 것이다! )

nginx 설정

nginx.conf 파일 수정

우선 nginx의 설정을 변경해줘야 한다. 사용할 도메인으로 챌린지 값을 받아 저장할 수 있도록 nginx의 configuration 파일인 nginx.conf에서 리버스 프록시 설정을 수정해준다.

👇nginx.conf 파일

server {
    listen 80; 
    server_name <도메인>;

    # Allow only for register SSL (Certbot)
    location /.well-known/acme-challenge {
        allow all;
		# 챌린지를 저장할 디렉토리
        root /var/www/certbot; 
    }
}

nginx 컨테이너 볼륨 마운트

다음은 ssl 파일을 읽어올 디렉토리와 인증값을 저장하는 디렉토리를 마운트하여 nginx가 참고할 수 있도록 해줄 것이다.

👇nginx docker-compose 파일

    volumes:
      - /app/docker/nginx/conf/nginx.conf:/etc/nginx/nginx.conf
      - /app/docker/certbot/data:/var/www/certbot:ro
      - /app/docker/certbot/conf:/etc/nginx/ssl:ro

ro 설정으로 수정권한을 제외했다🙄
/etc/nginx/ssl : 발급된 인증서가 있는 디렉토리
/var/www/certbot : 챌린지 저장 디렉토리

두 디렉토리를 추가로 마운트 해줄 수 있도록 설정해준 뒤, 기존에 사용하고 있던 nginx라면 컨테이너를 다시 띄울 수 있도록 한다.
(restart가 아닌 recreate이 되어야함)


이제 nginx는 준비가 완료됐다.😎



certbot 컨테이너를 배포하여 인증서 발급받기

docker로 일회성 certbot 컨테이너를 띄워서 인증서를 발급할 것이다.

docker-compose.yaml 파일

version: "3.7"
services:
  certbot:
    image: certbot/certbot:latest
    container_name: cmd_certbot
    command: certonly --webroot --webroot-path=/var/www/certbot --email <이메일> --agree-tos --no-eff-email -d <도메인>
    volumes:
      - /app/docker/certbot/conf:/etc/letsencrypt:rw
      - /app/docker/certbot/logs:/var/log/letsencrypt:rw
      - /app/docker/certbot/data:/var/www/certbot:rw

rw로 수정할 수 있는 권한을 부여한다.

--webroot-path 플래그에 nginx에서 설정했던 챌린지 저장 디렉토리를 설정해준다.
-d 플래그에는 인증서를 발급할 도메인을 넣어준다.

마운트할 볼륨 디렉토리는 nginxdocker-compose.yaml파일에서 설정했던 디렉토리와 동일하게 해줘야 한다.

/etc/letsencrypt : 인증서가 발급 될 디렉토리
/var/log/letsencrypt : 인증서 관련 로그 저장 디렉토리
/var/www/certbot : 챌린지 저장 디렉토리

위의 내용으로 yaml파일을 생성한 뒤 컨테이너를 띄운다!

docker-compose up -d

결과

Requesting a certificate for <도메인>

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/<도메인>/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/<도메인>/privkey.pem
This certificate expires on 2022-09-11.
These files will be updated when the certificate renews.
NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.
Saving debug log to /var/log/letsencrypt/letsencrypt.log

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

🥳해당 컨테이너의 로그를 찍었을 때 이런 결괏값이 나온다면 성공한 것이다🥳
(인증서 재발급을 위해 이 컨테이너는 반드시 종료된 뒤 삭제해주자!)



nginx 리버스 프록시 설정하기

이제 해당 도메인으로 접속했을 때 원하는 주소로 연결될 수 있도록 nginx conf파일을 설정해보자.

server {
    listen 80;
    server_name <도메인>;

    # Allow only for register SSL (Certbot)
    location /.well-known/acme-challenge {
        allow all;
        root /var/www/certbot;
    }

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

설정해 둔 도메인의 http 신호를 https로 보내도록 location을 추가해준다.

🚨 location으로 설정하지 않고 그냥 return 해버리면 추후 인증서 재발급 시 챌린지가 제대로 전달되지 않으므로 주의!

이제 https에서 목표하는 주소로 신호를 보내도록 연결하면 된다.

stream {
    upstream stream_one {
        least_conn;
        server <Another IP1>:<Port>;
    }
    upstream another1 {
        server <Another IP2>:<Port>;
    }
    upstream another2 {
        server <Another IP3>:<Port>;
    }
    upstream goal {
        server 127.0.0.1:4443;
    }

    server {
		# 인증서 인식을 위해 ssl 추가
        listen 127.0.0.1:4443 ssl;
		# 마운트된 볼륨의 디렉토리를 참고하여 인증서의 위치를 명시한다
        ssl_certificate /etc/nginx/ssl/live/<도메인>/fullchain.pem;
        ssl_certificate_key /etc/nginx/ssl/live/<도메인>/privkey.pem;

        proxy_pass https_goal;
    }

    upstream https_goal {
		# 최종적으로 목표하는 주소로 연결
        server <IP>:<Port>;
    }
}

map $ssl_preread_server_name $maps {
    hostnames;
    default stream_one;
    <other 도메인1> another1;
    <other 도메인2> another2;
    <도메인> goal;
}

server {
    listen          443;
	# 발급 받은 인증서를 인식할 수 있도록 설정
    ssl_preread     on;
	proxy_pass      $maps;
    proxy_timeout   3s;
    proxy_connect_timeout 1s;
}

이 예제에서는 여러 다른 도메인들을 사용한다는 가정 하에 진행됐으므로, upstream을 사용하여 여러 도메인에 대한 리버스 프록시를 설정했다.

이런 식으로 nginx.conf파일을 수정한 뒤 변경점을 nginx 컨테이너에 적용해주면 끝!🤠

docker exec <nginx_컨테이너 이름> nginx -s reload


++crontab으로 인증서 자동 갱신 설정하기

(크론탭 설정 부분을 빼먹은걸 이제야 알고 추가..😂..)
우선 인증서가 잘 발급되었는지 확인해보자.

docker run --rm --name cmd_certbot \
-v '/app/docker/certbot/conf:/etc/letsencrypt' \
-v '/app/docker/certbot/logs:/var/log/letsencrypt' \
-v '/app/docker/certbot/data:/var/www/certbot' \
certbot/certbot certificates

위의 명령어를 실행하면 현재 인증서에 대한 정보가 출력된다.

👇 결과

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Found the following certs:
  Certificate Name: <도메인>
    Serial Number: 385fa92dfca1624a679c038a74f6d22e334
    Key Type: RSA
    Domains: <도메인>
    Expiry Date: 2022-12-01 14:00:09+00:00 (VALID: 33 days)
    Certificate Path: /etc/letsencrypt/live/<도메인>/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/<도메인>/privkey.pem
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

인증서가 만료되기 전에 재발급하여 연속적으로 인증서의 유효기간을 유지해야 하므로, 만료되지 않은 인증서가 필요하다.

만료되지 않은 인증서는 재발급이 불가하다. (인증서는 90일동안 사용할 수 있으며, 만료 기간이 30일 이내인 인증서는 재발급이 가능합니다.)
여기서는 renew가 아닌 force-renew로 주기적인 강제 재발급을 하도록 설정할 것이다.
(renew로 진행하고 싶다면 크론탭의 주기를 만료일 30일 이내에 발동되도록 설정해주시면 됩니다!)

먼저 강제 재발급 명령어를 스크립트 파일로 만들어 놓는다.
++ 🚨 재발급된 인증서를 적용해주려면 nginx reload가 필요합니다!

👇 renew.sh

#!/bin/bash

docker run --rm --name cmd_certbot \
-v '/app/docker/certbot/conf:/etc/letsencrypt' \
-v '/app/docker/certbot/logs:/var/log/letsencrypt' \
-v '/app/docker/certbot/data:/var/www/certbot' \
certbot/certbot certonly --webroot -w /var/www/certbot --force-renewal --server https://acme-v02.api.letsencrypt.org/directory \
--cert-name <도메인> &&

docker exec <nginx_컨테이너 이름> nginx -s reload

아래의 명령어로 크론탭을 설정해주면 끝이다!

# crontab을 생성한다. 처음에 editor를 선택하게 하는데, 주로 vim을 쓰니 2번을 택해주자.
# editor를 변경하고 싶으면 select-editor 명령어로 다시 선택할 수 있다.
crontab -e
# 2개월을 텀으로 실행하도록 설정해준다. 
# 날짜나 실행 명령어를 문법에 맞지 않게 지정하면 저장되지 않고 tmp 파일로 넘어간다.
0 0 1 */2 * <renew.sh 경로>
#저장된 크론탭 목록 확인
crontab -l

이제 2개월 간격으로 그 달의 1일마다 스크립트를 실행하게 된다🥳🥳

profile
팔수록 모르는게 계속 나와💩

4개의 댓글

comment-user-thumbnail
2022년 11월 7일

재발급 스크립트 실행된 후 nginx restart해줘야지 않나요? 재시작 안해줘도 적용이 되는건가요?

1개의 답글