이번 포스팅에서는 무료 인증서 서비스인 Let’s encrypt 를 이용해서 nginxhttps 를적용하는 법에 대해 작성할 예정입니다. 사실 HTTPS 를 적용하는 이유는 너무 많이 알려져 있으니 따로 설명드리지는 않도록 하겠습니다.

이 포스팅을 진행하기 전에 domain 설정이 잘 되어 있는지 확인해 보시기 바랍니다. 필자 같은 경우는 api.kscory.com 이라는 도메인을 이용할 예정이며 ubuntu 18.04 기반으로 진행할 예정입니다.

또한 nginx 설치프록시 설정 에 대해서는 이전에 설명드렸으므로 궁금하신 분들은 이전 포스팅들을 참고해주시면 될 것 같습니다.

인증서 발급받기

Let’s encrypt 결국 CA(Certificate Authority) 업체입니다. 다만 다른 업체와 달리 무료 라는 점과 쉬운 발급 절차로 인해 인기가 많다고 생각하시면 될 것 같습니다. 최근에는 certbot 프로그램을 통해서 발급 방법이 더 간단해 졌습니다.

먼저 공식문서 를 참고해서 certbot-auto 를 설치하도록 하겠습니다.

# 설치하기
$ wget https://dl.eff.org/certbot-auto
$ sudo mv certbot-auto /usr/local/bin/certbot-auto
$ sudo chown root /usr/local/bin/certbot-auto
$ sudo chmod 0755 /usr/local/bin/certbot-auto

# 의존성 설치 및 버전확인
$ certbot-auto --version

혹시 python virtualenv 때문에 에러가 난다면 공식문서 에 해결 방법이 있으니 참고 바랍니다.

인증서를 발급하기 전에 인증 기관에서 인증 절차를 거쳐야만 합니다. Let’s encrypt 에서는 주로 webroot 방식, Standalone 방식, DNS 방식을 사용해서 인증 절차를 거친 후에 인증서를 발급하게 됩니다.

  1. webroot 방식
    • 실제 돌아가고 있는 웹 서버에 특정 파일 쓰기 작업을 통해 인증하는 방식입니다. 즉, 접근할 수 있는 특정 디렉터리를 제공해서 접근이 가능한지 확인하는 방식입니다.
    • 이를 이용하면 nginx 를 내렸다가 다시 띄울 필요가 없어진다는 장점이 존재합니다.
    • 단 한번에 하나의 도메인만 발급 가능하다는 단점이 존재합니다.
  2. Standalone 방식
    • certbot 에서 80 포트의 standalone webserver 를 따로 띄워서 인증서를 발급받는 방식입니다.
    • 즉, 80, 443 포트를 이미 열고 있다면 발급시마다 nginx 를 다시 내려야 한다는 단점이 존재합니다.
  3. DNS 방식
    • 도메인 TXT 레코드를 이용해서 인증서를 발급 받는 방식입니다.
    • 이 방식을 이용하면 세 방식 중 유일하게 wild card 방식으로 인증서를 발급받을 수 있습니다.

이번 포스팅에서는 webroot 방식을 이용해서 인증서를 발급받겠습니다.

먼저 nginx 를 이용해서 webroot 디렉토리에 접근할 수 있도록 통로를 열어주도록 하겠습니다. 이 때 webroot-plugin 은 ${webroot-path}/.well-known/acme-challenge 에 파일을 만들게 되므로 이 80 포트에서 이 경로로 들어 올 수 있도록 작성해 줍니다. 또한 webroot-path/var/www/letsencrypt 로 이용할 예정입니다.

이제 이전 포스팅과 마찬가지로 /etc/nginx/sites-available/proxy.conf 파일을 생성해 주도록 하겠습니다.

# let's encrypt 가 접근할 디렉토리 생성
$ sudo mkdir -p /var/www/letsencrypt

# proxy.conf vim 으로 실행
$ sudo vim /etc/nginx/sites-available/proxy.conf

proxy.conf 파일을 수정하도록 하겠습니다.

# proxy.conf 파일

server {
  listen  80;
  server_name api.kscory.com

  access_log /var/log/nginx/proxy/access.log;
  error_log /var/log/nginx/proxy/error.log;

  location ~ /\.well-known/acme-challenge/ {
      allow all;
      root /var/www/letsencrypt;
  }
}

작성을 마치셨으면 nginx 를 다시 실행시킨 후 certbot-auto 를 이용해서 인증서를 발급 받도록 합니다.

# certbot-auto certonly -a --webroot -w {파일 경로명} -d {domain} --agree-tos -m {email 명}
#   -a: authenticator plugin
#   --agree-tos: ACME 서버의 가입자 동의서에 동의
$ certbot-auto certonly --webroot -w /var/www/letsencrypt -d api.kscory.com --agree-tos -m seok@kscory.com

인증서 발급에 성공했다면 아래와 같은 출력을 확인하실 수 있을 것입니다.

nginx-ssl-01

발급된 인증서는 /etc/letsencrypt/live/{도메인명} 에 존재합니다. 확인해 보도록 하겠습니다.

# 인증서가 발급되었는지 확인
sudo ls /etc/letsencrypt/live/api.kscory.com

아래 4 가지의 파일을 발견하실 수 있을 것입니다.

  1. cert.pem: 도메인 인증서
  2. chain.pem: Let’s Encrypt chain 인증서
  3. fullchain.pem: cert.pem + chain.pem
  4. privkey.pem: 인증서의 개인키

즉 여기서 사용될 파일은 fullchain.pemprivkey.pem 입니다. 이제 키 발급이 완료 되었습니다.

nginx 에 SSL 적용하기

발급된 인증서를 가지고 nginx 에서 SSL 을 적용해보겠습니다. /etc/nginx/sites-available/proxy.conf 파일을 다시 수정하도록 합니다. 저는 domain 을 api.kscory.com 로 적용했으므로 각각의 경우에 맞게 수정해주시기 바랍니다. 또한 시작할 때 이야기 했듯 reverse proxy 를 적용했습니다.

server {
  listen  80;
  server_name api.kscory.com;

  access_log /var/log/nginx/proxy/access.log;
  error_log /var/log/nginx/proxy/error.log;

  location ~ /\.well-known/acme-challenge/ {
    allow all;
    root /var/www/letsencrypt;
  }

  location / {
    # http 로 들어올 경우 https 로 리다이렉트 한다.
    return 301 https://$server_name$request_uri;
  } 
}

server {
  # http2 를 활성화 시키기 위해 http2 추가
  listen  443 ssl http2;
  server_name api.kscory.com;

  access_log /var/log/nginx/proxy/access.log;
  error_log /var/log/nginx/proxy/error.log;
  ssl_certificate /etc/letsencrypt/live/api.kscory.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/api.kscory.com/privkey.pem;

  location / {
    include /etc/nginx/proxy_params;
    proxy_pass http://127.0.0.1:3000;
  }
}

이제 이 config 를 적용해서 nginx 를 재실행 시킨 후 한번 https 로 접속해 보겠습니다. 아래 스크린샷처럼 https 가 적용된 채로 접속이 된다면 성공입니다. 성공하셨다면 이번에는 스키마를 http 로 변경했을 때 https 로 리다이렉트가 잘 되는지도 확인해 보시기 바랍니다. ^^

nginx-ssl-02

암호화 설정하기 (디피 헬만 그룹, HSTS 등)

https 는 성공적으로 적용 되었으나 https://www.ssllabs.com/ssltest/analyze.html?d={도메인명} 사이트로 접속해서 ssl Lab 보안등급을 살펴 보도록 하겠습니다.

nginx-ssl-03

SSL Lab의 평가에서 SSL 보안점수가 60 이상이면 B, 80 이상이면 A 등급이 나오게 됩니다. 즉, 저희 서버의 보안성이 좀 약하다는 뜻입니다. 그럼 이 등급을 A 등급까지 올리기 위해 레미 반 엘스트의 조언 을 참고하여 암호화 설정을 하도록 하겠습니다.

우선 클라이언트와 정보 교환 과정에서 Perfect Forward Secrecy 를 유지하기 위해 디피 헬만 그룹을 생성하겠습니다.

# 4096 비트로도 사용 가능
$ sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

그럼 dhparam.pem 파일이 생긴 것을 볼 수 있습니다.

이제 암호화 설정 파일을 설정해 주도록 하겠습니다. 이 파일은 snippets 폴더에서 설정하여 적용해 주도록 하겠습니다. 그럼 /etc/nginx/snippets/ssl-params.conf 파일을 열어 아래와 같은 암호화 설정을 해주도록 합니다.

ssl_protocols TLSv1.3;# Requires nginx >= 1.13.0 else use TLSv1.2
ssl_prefer_server_ciphers on;
ssl_dhparam /etc/ssl/certs/dhparam.pem; # openssl dhparam -out /etc/nginx/dhparam.pem 4096
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
ssl_session_timeout  10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off; # Requires nginx >= 1.5.9
ssl_stapling on; # Requires nginx >= 1.3.7
ssl_stapling_verify on; # Requires nginx => 1.3.7
resolver 8.8.8.8 8.8.4.4 valid=300s; # resolver $DNS-IP-1 $DNS-IP-2 valid=300s;
resolver_timeout 5s; 
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

이 설정값을 이전에 적용한 ssl 에 적용해 주도록 합니다. /etc/nginx/sites-available/proxy.conf 파일을 열어 아래처럼 적용해 주도록 합니다.

server {
  # 80 포트 설정
  # 생략 ...
}

server {
  # 443 포트 설정
  # 생략 ...

  include snippets/ssl-params.conf;

  location / {
    # 생략 ...
  }
}

그럼 이제 nginx 를 다시 실행시킨 뒤 다시 한번 보안테스트를 진행해 보겠습니다. 아래 처럼 A 등급 이상으로 잘 나오시나요?? 고생하셨습니다!

nginx-ssl-04

인증서 자동 갱신하기

Let’s encrypt 인증서로 발급받은 인증서는 3개월 만기기 때문에 이를 반복해서 갱신해주어야 합니다. 아래 명령어로 인증서 갱신을 할 수 있습니다.

$ certbot-auto renew

그러나 매번 이렇게 하기 보다 자동갱신할 수 있는 방법이 없나 싶어 많은 블로그를 뒤져보았는데 결국 crontab 에 등록해서 사용하는 경우가 대부분이었습니다. 아래처럼 크론탭을 등록해 보도록 하겠습니다.

# 크롭탭 열기
$ sudo crontab -e

그 후 텍스트 에디터에 스케줄을 적어 주도록 하겠습니다.

# ubuntu 16.04
15 3 * * * certbot-auto renew && /etc/init.d/nginx reload

# ubuntu 18.04
15 3 * * * certbot-auto renew --quiet --renew-hook "/etc/init.d/nginx reload"

마무리

드디어 nginx 에서 ssl 적용하는 방법까지 완료 했습니다. 아마 모든 웹에 기본적으로 적용되어있는 암호화 설정이라 조금 쉬웠을지도 모른다는 생각도 듭니다. 다만, 생각보다 개발할때 한번 설정하고 자주 변경하는 일은 없기 때문에 잊어버리기 쉬울 수도 있습니다. 혹시 이 포스팅이 끝난 후에는 nginx 가 아니라 직접 application server 에 ssl 은 어떻게 적용할 수 있는지 직접 해보면 어떨까 싶네요. ^^ 다음 nginx 포스팅은 load balancing 에 대한 설명을 할 예정입니다. 시간이 난다면 얼른 포스팅하고 싶네요.

참고 자료

https://certbot.eff.org/docs/

https://cipherli.st/

https://twpower.github.io/44-set-free-https-by-using-letsencrypt

https://perfectacle.github.io/2017/10/05/letsencrypt-with-certbot-feat-aws/

https://jootc.com/p/201901062488

https://medium.com/@dipeshwagle/add-https-using-lets-encrypt-to-nginx-configured-as-a-reverse-proxy-on-ubuntu-b4455a729176