SSL Reverse Proxy with Lets Encrypt

    版本為 01:54, 27 Dec 2024

    到這個版本。

    返回到 版本存檔.

    查閱目前版本

    說明

    故事情境:我有一個主要網站 Main Web 是用 Nginx 架設,目前有以下需求需要完成:

    1. 租用另一部主機並建置 Reverse Proxy 服務,目的是避免主要網站主機直接曝露在公用網路上。
      注意:基於安全理由,主網站與 Reverse Proxy 應該是在不同的主機並且是不同的 IP 位址。
    2. 網站需要使用 Let's Encrypt 憑證的 SSL 加密,所以架構是
      Main Web -----[HTTP]----- Reverse Proxy -----[HTTPS]----- Client
    3. 防火牆設定原則
      • Main Web 禁止所有外部存取,僅開放來自 Reverse Proxy 的 HTTP 連線
      • Reverse Proxy 開放外部所有連線可存取 HTTPS
    4. 使用 docker 環境建置 Reverse Proxy 系統,包括 Lets Encrypt 憑證自動更新

    Reverse Proxy with Nginx

    新增 docker-compose.yml

    version: "2"
    
    services:
      nginx-proxy:
        restart: always
        image: nginx
        container_name: nginx-proxy
        ports:
          - "80:80"
          - "443:443"
        volumes:
          - "/docker_vol/nginx-proxy/etc-nginx/conf.d:/etc/nginx/conf.d"
          - "/docker_vol/cert-letsencrypt:/etc/letsencrypt"   
          - "/docker_vol/data-letsencrypt:/data/letsencrypt"
    

    建立需要的目錄

    mkdir -p /docker_vol/nginx-proxy/etc-nginx/conf.d
    mkdir -p /docker_vol/cert-letsencrypt
    mkdir -p /docker_vol/data-letsencrypt  

    新增 Nginx 設定檔
    /docker_vol/nginx-proxy/etc-nginx/conf.d/proxy.conf

    server {
        listen 80;
        server_name www.your.domain;
    
        location / {
            proxy_pass http://your-main-web-ip;
            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;
        }
    }
    

    http://your-main-web-ip 這裡填入主網站的 IP 位址

    啟動nginx-proxy 服務 (使用 docker-compose)

    第一次啟動,系統會自動新增與佈署需要的 containers

    #> docker-compose up -d
    

    佈署後檢查服務狀態

    #> docker-compose ps
    
        Name             Command          State                    Ports                  
    -------------------------------------------------------------------------------------
    nginx-proxy   nginx -g daemon off;   Up      0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
    

    TIPs:

    如果一直無法正常啟動服務,試著先將 proxy.conf 移除,然後啟動試試;如果啟動正常,再將 proxy.conf 回復後重新啟動。

    Reverse Proxy 基本測試

    如果 nginx-proxy 服務啟動正常,若在瀏覽器上輸入 http://reverse-proxy-ip 有顯示主網站的首頁,表示服務運作正常。

    Lets Encrypt

    使用 Reverse Proxy 同部主機,運用 docker 技術,將 Let's Encrypt 憑證服務與 Reverse Proxy 做整合。

    事前準備:

    1. 需要一個有效的網域名稱例如 www.your.domain
    2. 上述網域在公眾 DNS 主機必須有 A/AAAAA 紀錄指向到 Reverse Proxy 主機 IP
       

    需要的步驟有二:

    1. 第一次新增憑證至主機
    2. 每3個月自動更新憑證有效期
    第一次新增憑證

    編輯 /docker_vol/nginx-proxy/etc-nginx/conf.d/proxy.conf
    加上以下這幾行

    ...
        # Statically serve all files in .well-known, which is the location where letsencrypt stores the proof file
       location /.well-known/ {
            alias /data/letsencrypt/.well-known/;
        }
    

    重啟 nginx-proxy 服務

    docker-compose stop
    docker-compose start 
    

    線上建立憑證

    docker run -it --rm \
     -v "/docker_vol/cert-letsencrypt:/etc/letsencrypt" \
     -v "/docker_vol/data-letsencrypt:/data/letsencrypt" \
     -v "/docker_vol/log-letsencrypt:/var/log/letsencrypt" \
     deliverous/certbot \
     certonly \
     --webroot --webroot-path=/data/letsencrypt \
     -d www.your.domain
    

    TIPs:

    - www.your.domain 請改成主要網站的實際網域名稱。如果一次要建立多個網站的憑證,最後一行改成 -d first.my.web -d second.my.web -d third.my.web

    - 執行後,系統會要求輸入 email,請輸入有效的 email。

    - 建立憑證時,網站必須開放 HTTP 可讀取網站目錄 /.well-known/,若憑證建立失敗,請檢查此目錄是否可正常被讀取,網頁目錄為 /data/letsencrypt。

    憑證建立成功

    成功的畫面輸出內容如下

    IMPORTANT NOTES:
     - Congratulations! Your certificate and chain have been saved at:
       /etc/letsencrypt/live/www.your.domain/fullchain.pem
       Your key file has been saved at:
       /etc/letsencrypt/live/www.your.domain/privkey.pem
       Your cert will expire on 2018-05-06. To obtain a new or tweaked
       version of this certificate in the future, simply run certbot
       again. To non-interactively renew *all* of your certificates, run
       "certbot renew"
     - 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
     

    憑證檔存放路徑
    container 系統

    • /etc/letsencrypt/live/www.your.domain/fullchain.pem
    • /etc/letsencrypt/live/www.your.domain/privkey.pem

    Host 系統

    • /docker_vol/cert-letsencrypt/live/www.your.domain/fullchain.pem
    • /docker_vol/cert-letsencrypt/live/www.your.domain/privkey.pem

    TIP:

    之後若在同一部主機需要新增其他主機網域的憑證,只要再執行一次上述的 docker 指令,將 -d <new-host-name> 改成新主機名稱,過程中不需要再輸入 email(第一次輸入的 email 已儲存在 /docker_vol/cert-letsencrypt/)。

    自動更新憑證

    Lets Encrypt 憑證每次簽署只有三個月的有效期,自動更新步驟如下:

    #> docker run -it --rm \
     -v "/docker_vol/cert-letsencrypt:/etc/letsencrypt" \
     -v "/docker_vol/data-letsencrypt:/data/letsencrypt" \
     -v "/docker_vol/log-letsencrypt:/var/log/letsencrypt" \
     deliverous/certbot \
     renew \
     --webroot --webroot-path=/data/letsencrypt
    
     #> docker-compose kill -s HUP nginx-proxy

    NOTE: 不可以加上 -d your.domain.com,只能一次更新所有網域。

    Cronjob:

    0 0 */15 * * docker run -it --rm -v "/docker_vol/cert-letsencrypt:/etc/letsencrypt" -v "/docker_vol/data-letsencrypt:/data/letsencrypt" deliverous/certbot renew --webroot --webroot-path=/data/letsencrypt && docker-compose kill -s HUP nginx-proxy
    

    TIP:

    - 官方建議每隔 60 天對憑證做更新,每次憑證有效期為 90 天。

    - 更新指令無法指定單一 domain,必須一次對所有網域做更新。

    - 如果更新失敗,檢查 log 檔 /docker_vol/log-letsencrypt/letsencrypt.log

    設定 Reverse Proxy 使用 SSL with Letsencrypt

    一旦上述步驟都有正確完成,就可以將 Reverse Proxy 設定成 SSL 加密模式。

    編輯 /docker_vol/nginx-proxy/etc-nginx/conf.d/proxy.conf
    加上以下這幾行

        listen 443;    #這行記得改
    ...
        # SSL Settings
        ssl on;
        ssl_certificate     /etc/letsencrypt/live/www.your.domain/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/www.your.domain/privkey.pem;
    
        ssl_session_timeout 5m;
        ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # omit SSLv3 because of POODLE (CVE-2014-3566)
        ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
        ssl_prefer_server_ciphers on; 

    重啟 nginx-proxy 服務

    docker-compose stop
    docker-compose start 
    

    FAQ

    Q: 更新憑證時發生錯誤

    Currently, the renew verb is capable of either renewing all installed certificates that are due to be renewed or renewing a single certificate specified by its name. If you would like to renew specific certificates by their domains, use the certonly command instead. The renew verb may provide other options for selecting certificates to renew in the future.

    A: 更新時不能指定網域,必須更新所有設定的網域

    #> docker run -it --rm \
     -v "/docker_vol/cert-letsencrypt:/etc/letsencrypt" \
     -v "/docker_vol/data-letsencrypt:/data/letsencrypt" \
     -v "/docker_vol/log-letsencrypt:/var/log/letsencrypt" \
     deliverous/certbot \
     renew \
     --webroot --webroot-path=/data/letsencrypt
    

    NOTE:不可以加上 -d your.domain.com

    Powered by MindTouch Core