Architecture & Tool/Nginx

[NGINX] HTTPS 서버 구성하기 (Let's Encrypt)

jaamong 2024. 4. 12. 08:45
목차
  • SSL/TLS
  • Certbot 설정과 SSL 적용
    • 설정 과정
    • 더미 인증서 생성
  • 인증서 갱신 자동화
  • ⭐️ 실제 진행 순서

 

 

SSL/TLS

전송 계승 상에서 클라이언트, 서버에 관한 인증 및 데이터 암호화 수행

  • 클라이언트와 서버 양단간 응용 계층 및 TCP 전송 계층 사이에서 수행
  • 보안용 프로토콜로 안전한 보안 채널을 형성하는 역할
SSL = Secure Socket Layer
TLS = Transport Layer Security
HTTPS = HTTP over SSL

 

HTTP에 SSL이 적용된 HTTPS를 이용하여 통신을 암호화하면 보안을 높일 수 있다. 이를 위해서는 SSL 인증서가 필요하다. 우리는 CA 중 하나인 Let's Encrypt를 통해 무료로 인증서를 발급받을 것이다. Let's Encrypt는 SSL 인증서를 발급해 주는 공인된 기관 중 하나이다.

Certbot은 Let's Encrypt에서 SSL 인증서 사용을 관리하는 무료 오픈소스 도구이다. certbot 명령을 사용하여 무료로 사용할 수 있는 SSL 인증서를 발급, 갱신, 삭제할 수 있다.

certbot을 활용하면 CLI를 통해 SSL 인증서를 발급받을 수 있지만, 90일마다 인증서를 재발급해야 하는 번거로움이 있다. 이러한 번거로움은 docker compose + nginx + certbot을 사용하여 이 과정을 자동화한 설정 파일을 제공하는 곳을 통해 해결할 수 있다!

🔗 Guide: https://pentacent.medium.com/nginx-and-lets-encrypt-with-docker-in-less-than-5-minutes-b4b8a60d3a71
🔗 GitHub: https://github.com/wmnnd/nginx-certbot

 

 

SSL/TLS

설정 과정

  1. docker-compose.yml에 아래와 같이 nginx와 certbot 이미지를 정의한다. `image` 태그 명은 원하는 대로 작성하면 된다.
    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
    
      certbot:
        image: certbot/certbot    
    
    	...
    `volumes` 블록 작성 시 nginx.conf의 위치를 확인하고 정확하게 작성한다. 나는 docker-compose.yml과 같은 위치에 nginx 디렉토리를 만들고 그 안에 nginx.conf를 만들었다. 
  2. 이번에는 모든 요청을 HTTPS로 리다이렉트 하는 코드를 담은 nginx.conf를 작성한다. AWS EC2의 경우, 퍼블릭 IPv4 DNS가 아닌 Route53에 등록한 도메인을 입력한다.
    server {
    	listen 443 ssl;
    	server_name {your_domain};  
    
    	location / {
    		proxy_pass http://{your_domain};  # upstream 사용 시 upstream 이름 작성
    	}
    }
    
    server {
    	listen 80;
    	server_name {your_domain};  
    
    	location / {
    		return 301 https://$host$request_uri;  # redirects to https
    	}
    }
  3. 이제 nginx와 certbot을 연결한다. Let’s Encrypt는 도메인에서 `well-known URL`을 요청하여 도메인 검증을 수행한다. 도메인이 특정 응답(”challenge”)을 받으면 유효한 것이다. 해당 응답 데이터는 certbot에서 제공되므로, nginx 컨테이너가 certbot의 파일을 제공할 수 있는 방법이 필요하다. 

    우선 두 개의 docker 볼륨이 필요하다. 하나는 유효성 검사용, 하나는 실제 인증서용이다. 1번의 docker-compose.yml 파일에 코드를 추가한다.
    - ./data/certbot/conf:/etc/letsencrypt
    - ./data/certbot/www:/var/www/certbot
    `data` 디렉토리는 수동으로 생성하지 않아도 된다.

    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        volumes:
        	- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
            - ./data/certbot/conf:/etc/letsencrypt
            - ./data/certbot/www:/var/www/certbot
    
      certbot:
        image: certbot/certbot    
        volumes:
            - ./data/certbot/conf:/etc/letsencrypt
            - ./data/certbot/www:/var/www/certbot
    	...
  4. 이제 nginx가 certbot에서 받은 challenge 파일을 제공할 수 있도록 하자. 아래 코드를 nginx.conf의 port 80 섹션에 추가한다.
    location /.well-known/acme-challenge/ {
    	root /var/www/certbot;
    }
    server {
    	listen 443 ssl;
    	server_name {your_domain};  
    
    	location / {
        	proxy_pass http://{your_domain};  
    	}
    }
    
    server {
    	listen 80;
    	server_name {your_domain};  
    
    	location / {
    		return 301 https://$host$request_uri;  
    	}
    		
    	location /.well-known/acme-challenge/ {
    		root /var/www/certbot;
    	}
    }
  5. 이제 HTTPS 인증서를 참조해야 한다. 곧 `생성될 인증서`와 인증서의 `private key`를 port 443 섹션에 추가해야 한다.
    ssl_certificate /etc/letsencrypt/live/example.org/fullchain.pem;
    ssl_certificate_key /etc/letencrypt/live/example.org/privkey.pem;
    server {
    	listen 443 ssl;
    	server_name {your_domain};  
    
    	ssl_certificate /etc/letsencrypt/live/{your_domain}/fullchain.pem;
    	ssl_certificate_key /etc/letencrypt/live/{your_domain}/privkey.pem;
    
    	location / {
    		proxy_pass http://{your_domain};  
    	}
    }
    
    server {
    	listen 80;
    	server_name {your_domain};  
    
    	location / {
    		return 301 https://$host$request_uri;  
    	}
    		
    	location /.well-known/acme-challenge/ {
    		root /var/www/certbot;
    	}
    }

    보안과 관련된 코드 두 줄을 추가해 보자. 반드시 추가할 필요는 없으나, 보안을 생각한다면 넣는 것이 좋다.
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letencrypt/ssl-dhparams.pem;

    🔹include /…/options-ssl-nginx.conf
        options-ssl-nginx.conf 파일을 열어보면 SSL 관련 설정들이 적혀 있다.
    # This file contains important security parameters. If you modify this file
    # manually, Certbot will be unable to automatically provide future security
    # updates. Instead, Certbot will print and log an error message with a path to
    # the up-to-date file that you will need to refer to when manually updating
    # this file. Contents are based on https://ssl-config.mozilla.org
    
    ssl_session_cache shared:le_nginx_SSL:10m;
    ssl_session_timeout 1440m;
    ssl_session_tickets off;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    
    ssl_ciphers "...";
    적혀있는 주석을 보면 알 수 있듯이, 해당 설정을 추가함으로써 Certbot에서 자동으로 보안 업데이트를 하도록 한다.

    🔹ssl_dhparam
        `dhparam`을 사용하면 client와 server 간의 key를 exchange 할 때 perfect security를 보장한다.

    아래가 최종 nginx.conf이다.
    server {
    	listen 443 ssl;
    	server_name {your_domain};  
    
    	# managed by Certbot
    	ssl_certificate /etc/letsencrypt/live/{your_domain}/fullchain.pem;
    	ssl_certificate_key /etc/letencrypt/live/{your_domain}/privkey.pem;
    	include /etc/letsencrypt/options-ssl-nginx.conf;
    	ssl_dhparam /etc/letencrypt/ssl-dhparams.pem;
    
    	location / {
    		proxy_pass http://{your_domain};  
    	}
    }
    
    server {
    	listen 80;
    	server_name {your_domain};  
    
    	location / {
    		return 301 https://$host$request_uri;  
    	}
    		
    	location /.well-known/acme-challenge/ {
    		root /var/www/certbot;
    	}
    }

 

더미 인증서 생성

Nginx가 Let’s Encrypt 유효성 검증을 수행하도록 해야 하는데, Nginx는 인증서가 없으면 시작하지 않을 것이다. 그래서 더미 인증서를 먼저 생성해야 한다. 그리고 Nginx를 시작하고 더미 인증서를 지운 다음, 실제 인증서를 요청하는 과정으로 진행한다.

다행인 점은! 위 가이드 링크에서 이 과정을 자동화한 스크립트를 제공한다.

 

  1. 작업 디렉토리에 아래 명령어로 설치를 진행한다.
    $ curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
  2. 도메인과 이메일 주소를 변경하려면 스크립트를 수정해야 한다. 도메인에는 nginx.conf에 적었던 도메인과 동일한 주소를 입력하고, 이메일은 인증서가 만료될 때 알림을 받을 용도이다. 
    도커 볼륨의 디렉토리를 변경하는 경우, `data_path` 변수에도 적용해야 한다. 그러고 나서 아래 명령어를 실행한다. 
    $ chmod +x init-letsencrypt.sh 
    $ sudo ./init-letsencrypt.sh

    `./init-...` 명령으로 진행되지 않는다면 아래 `bash` 명령어로 진행한다.
    $ sudo bash init-letsencyprt.sh

 

 

인증서 갱신 자동화

앞에서 언급한 것처럼 인증서는 유효기간이 있으므로 갱신해줘야 한다. 그런데 생각보다 갱신하는 주기를 잊기가 쉬워서 나중에 문제가 발생하고 나서 조치를 취하는 경우가 많다. 그러한 문제를 방지할 겸 인증서 갱신을 자동화해 보자.

 

  1. docker-compose.yml의 `certbot` 섹션에 아래 코드를 추가한다. 이 코드는 Let’s Encrypt에서 권장하는 것처럼 12시간마다 인증서가 갱신되는지 확인한다.
    entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
    
      certbot:
        image: certbot/certbot    
        volumes:
        	- ./data/certbot/conf:/etc/letsencrypt
            - ./data/certbot/www:/var/www/certbot
        entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    
    ...
  2. docker-compose.yml의 `nginx` 섹션에 Nginx가 새로운 인증서를 리로드 하도록 코드를 추가해야 한다.
    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
    
      certbot:
        image: certbot/certbot    
        volumes:
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
        entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    
    	...

 

 

⭐️ 실제 진행 순서

여기에서는 간략하게 실제 진행 순서를 다룬다. 위 순서대로 진행하면 좋겠지만, 더미 인증서를 발급받아야 하므로 그렇지 않다🥲

 

  1. docker-compose.yml을 작성한다.
    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
    
      certbot:
        image: certbot/certbot    
    	volumes:
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
    	...
  2. nginx.conf를 작성한다.
    server {
    	listen 80;
    	server_name {your_domain};  
    		
    	location /.well-known/acme-challenge/ {
        	root /var/www/certbot;
    	}
    
    	...
    }
  3. `docker compose up`을 진행한다.
  4. 아래 명령어를 입력하여 쉘 스크립트를 설치하고, 해당 스크립트를 수정한다. 스크립트에서 이메일과 도메인을 수정한 다음 실행한다.
    $ curl -L https://raw.githubusercontent.com/wmnnd/nginx-certbot/master/init-letsencrypt.sh > init-letsencrypt.sh
    $ chmod +x init-letsencrypt.sh
    $ vi init-letsencrypt.sh 
    $ sudo ./init-letsencrypt.sh
  5. 스크립트 실행 후 로그를 보면서 인증서 발급이 성공했는지 확인한다. 에러 발생으로 발급되지 않았다면 로그를 보면서 수정해 나간다.
  6. 인증서 발급이 완료되었으면 이제 HTTPS를 적용하자. nginx.conf를 수정한다.
    server {
    	listen 443 ssl;
    	server_name {your_domain};  
    
    	# managed by Certbot
    	ssl_certificate /etc/letsencrypt/live/{your_domain}/fullchain.pem;
    	ssl_certificate_key /etc/letencrypt/live/{your_domain}/privkey.pem;
    	include /etc/letsencrypt/options-ssl-nginx.conf;
    	ssl_dhparam /etc/letencrypt/ssl-dhparams.pem;
    
    	location / {
    		proxy_pass http://{your_domain};  
    	}
    }
    
    server {
    	listen 80;
    	server_name {your_domain};  
    
    	location / {
    		return 301 https://$host$request_uri;  
    	}
    		
    	location /.well-known/acme-challenge/ {
    		root /var/www/certbot;
    	}
    }
  7. 인증서를 자동 갱신하도록 설정하자. docker-compose.yml 파일을 수정한다.
    version: "3.5"
    
    services:
      ...
    
      nginx:
        image: project-nginx:latest
        command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
        volumes:
          - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro  # 호스트 머신에서의 nginx.conf 위치:도커 컨테이너에서의 nginx.conf 위치 
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
    
      certbot:
        image: certbot/certbot    
        volumes:
          - ./data/certbot/conf:/etc/letsencrypt
          - ./data/certbot/www:/var/www/certbot
        entrypoint: "/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h & wait $${!}; done;'"
    
    	...
  8. 수정 완료 후 다시 `docker compose up`을 실행한다. 그리고 HTTPS 프로토콜이 적용됐는지 확인한다.
  9. 잘 적용됐다면 완료!