Nginx SSL Reverse Proxy with Cache Example

(Last Updated On: December 12, 2020)

해외서버를 이용하다보면 네트워크와 관련된 스펙은 좋은데 성능자체가 크게 좋지 않은 서버가 있는 반면, 반대로 성능자체는 좋은데 네트워크에 문제가 있는 경우가 있었다. 이것을 적절히 이용할 수 있는 방법은 없을까? 생각하여 부하가 굉장히 낮은 WAS (웹어플리케이션서버)인 nginx에 Reverse Proxy 서버를 구현해보게 되었다. 실제로 많은 리소스를 요하는 처리는 Origin Web Server에서 처리할 것이다.

on front-side server (198.51.100.32)

proxy_cache_path  mycacheserver  levels=1:2    keys_zone=mycacheserver:10m inactive=24h  max_size=2g loader_files=1000;

upstream proxy-backend {
  # keepalive는 nginx가 최대로 허용하는 업스트림 커넥션 수
  keepalive 32;
  keepalive_timeout 24h;
  server 203.0.113.4:443;
}

server {

  listen 443 ssl http2;
  server_name web.kerus.net;

  # $server_name 값이 항상 server_name 인자의 첫 번째 값이기 때문에 선언필요
  set $vserver_name $server_name;

  http2_push_preload on;

  # 기본 HTTPS 서버
  add_header Strict-Transport-Security "max-age=2147483647; includeSubDomains; preload" always;
  add_header Content-Security-Policy "block-all-mixed-content";

  ssl_certificate /etc/letsencrypt/live/kerus.net/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/kerus.net/privkey.pem;

  # 필요에 따라 설정
  #ssl_client_certificate /etc/ssl/certs/cloudflare.crt;
  #ssl_verify_client off;
  #ssl_stapling off;
  #ssl_stapling_verify off;
  #ssl_session_timeout 1d;
  #ssl_session_cache shared:MozSSL:10m;
  #ssl_session_tickets off;
  #proxy_ssl_certificate         /etc/nginx/certs/cert.pem;
  #proxy_ssl_certificate_key     /etc/nginx/certs/cert.key;
  #proxy_ssl_verify on;
  
  # 캐시 대상
  location ~* .(ico|jpg|png|gif|jpeg|css|swf|js|woff)$ {
    # 필요에 따라 설정
    #access_log off;
    #expires 1h;
    gzip_static on;
    gzip_comp_level 5;
    add_header Cache-Control private;

    # 이미지 배포의 경우 nopush가 좋다고 생각한다..
    tcp_nopush on;

    try_files $uri @cached-proxy;
  }

  location @cached-proxy {

    # 프록시 필수 설정
    proxy_ssl_server_name on;
    proxy_ssl_name $vserver_name;
    proxy_ssl_session_reuse on;
    proxy_set_header Host $vserver_name;
    proxy_pass https://proxy-backend;

    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 https;

    proxy_redirect off;

    # 필요에 따라 설정
    proxy_ssl_protocols TLSv1.3;
    #proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
    proxy_hide_header Strict-Transport-Security;
    proxy_hide_header Content-Security-Policy;
    proxy_hide_header X-Powered-By;
    
    # 성능을 위한 설정
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering on;
    proxy_max_temp_file_size 50M;
    proxy_buffers 8 16K;
    proxy_buffer_size 16K;
    proxy_send_timeout 24h;
    proxy_read_timeout 24h;
    proxy_connect_timeout 10s;

    # 캐시
    proxy_cache            mycacheserver;
    proxy_cache_valid      200  1d;
    proxy_cache_use_stale  error timeout invalid_header updating
                                   http_500 http_502 http_503 http_504;
    proxy_cache_key $scheme$proxy_host$uri$is_args$args;

    # nginx slice module 필요
    proxy_set_header Range $slice_range;

    # 캐시 디버깅용
    add_header K-Cache-Status $upstream_cache_status;

    # 이미 한 번 hide 된 헤더를 클라이언트에게 보내려면 hide_header 이후에 다시 선언해야함
    add_header Strict-Transport-Security "max-age=2147483647; includeSubDomains; preload" always;
    add_header Content-Security-Policy "block-all-mixed-content";
    
  }

  location ~ {

    # 프록시 필수 설정
    proxy_ssl_server_name on;
    proxy_ssl_name $vserver_name;
    proxy_ssl_session_reuse on;
    proxy_set_header Host $vserver_name;
    proxy_pass https://proxy-backend;

    proxy_redirect off;

    # 필요에 따라 설정
    proxy_ssl_protocols TLSv1.3;
    #proxy_set_header X-SSL-CERT $ssl_client_escaped_cert;
    proxy_hide_header Strict-Transport-Security;
    proxy_hide_header Content-Security-Policy;
    proxy_hide_header X-Powered-By;
    
    # 성능을 위한 설정
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering on;
    proxy_max_temp_file_size 50M;
    proxy_buffers 8 16K;
    proxy_buffer_size 16K;
    proxy_send_timeout 24h;
    proxy_read_timeout 24h;
    proxy_connect_timeout 10s;

    # 캐시
    proxy_cache off;

    # nginx slice module 필요
    proxy_set_header Range $slice_range;

    # 캐시 디버깅용
    add_header K-Cache-Status FORCE-DYNAMIC;
    
    # 이미 한 번 hide 된 헤더를 클라이언트에게 보내려면 hide_header 이후에 다시 선언해야함
    add_header Strict-Transport-Security "max-age=2147483647; includeSubDomains; preload" always;
    add_header Content-Security-Policy "block-all-mixed-content";

  }

}

on backend server (203.0.113.4) KEEP-ALIVE

nginx 설정 http 이나 server 내에

keepalive_timeout 24h;

위처럼 Keep-Alive를 해주는 것이 중요하다. 이 경우 악의적인 공격에 의해 세션이 메모리에 계속 남아 있으면 곤란하지만 TCP+TLS의 handshake가 발생하면 상당히 TTFB가 증가하므로 연결을 유지시켜주도록 한다.

Purging cache Manually

Note

현재 설정은 Frontend 웹서버와 Backend 웹서버 양쪽 모두 신뢰하는 ROOT CA에 인증서를 받은 상태이지만 Origin Web Server에 Self-Signed 인증서를 적용 한 경우 proxy_ssl_verify 를 off로 하거나 proxy_ssl_trusted_certificate 옵션을 따로 적용해야한다.