Hsüan's Blog

2017-01-07

在 docker nginx 下使用 docker let's encrypt

2018-03-02 更新 建議用輕量級的 acme.sh 取代 certbot

注意

  • 此文滿滿都是 docker,排斥的可以右轉出去看這篇,或是這篇就好。
  • 此文是假設擁有 example.com, www.example.com 的狀況。後面設定若有這兩個 domain,請都換成你自己的。

certbot 工具說明

certbot 是 let's encrypt 幫忙獲取 ssl 證書的命令行工具。

這工具主要有兩種模式

  • --standalone 模式 (不採用)

此模式下,certbot 會把自己當成 web server。自動處理完所有證書相關的事情。然後把檔案丟到 /etc/letsencrypt 下面。運作原理是 certbot 發起請求 -> let's encrypt 網站對你的 domain 發起請求驗證你擁有此網站 -> 取得證書。

缺點明顯。因為要佔用端口,所以 nginx 必須停下,讓 certbot 弄完證書之後才能啟動。

  • --webroot 模式 (本文採用)

此模式下,certbot 是利用原本的 web server 做驗證。原理是 certbot 在 nginx 訪問的下寫入特殊的路徑檔案 -> let's encrypt 訪問該路徑檔來驗證 -> 取得證書。

此方式雖然需要對 web server 的設定做些改動,但是不用每次刷新證書都要停機。

docker nginx 配置

  • /data/app/nginx/cert 主要是放 let's encrypt 的證書。下面 nginx.conf 設定會有相關路徑。
docker run \
--restart=always -d \
--name nginx \
-p 80:80 \
-p 443:443 \
-v /data/log/nginx:/var/log/nginx \
-v /data/app/nginx/nginx.conf:/etc/nginx/nginx.conf \
-v /data/app/nginx/conf.d:/etc/nginx/conf.d \
-v /data/app/nginx/cert:/etc/nginx/cert \
-v /data/app/nginx/html:/usr/share/nginx/html \
nginx:1-alpine
  • nginx 版本
docker exec nginx -v
# nginx version: nginx/1.11.6

nginx 配置

nginx.conf

  • /.well-known/acme-challenge 是給 let's encrypt 驗證用的。
  • https 的 server block,在還沒拿到證書之前要先註釋掉。不然 nginx 會因為找不到檔案而不啟動。
...
http {
    ...
    ssl_prefer_server_ciphers on;
    ssl_stapling on;
    ssl_stapling_verify on;
    server {
        listen      80 default_server;
        listen [::]:80;
        server_name example.com www.example.com;

        # 給 let's encrypt 的 webroot 模式驗證的
        location ^~ /.well-known/acme-challenge/ {
            default_type "text/plain";
            root     /usr/share/nginx/html;
        }
        location = /.well-known/acme-challenge/ {
           return 404;
        }

        # 消滅 http ,全部都是 https
        return 301 https://$server_name$request_uri;
    }
    # 沒拿到證書前要先註解 https server 部份
    server {
        listen      443 ssl http2;
        listen [::]:443 ssl http2;
        ssl_certificate         /etc/nginx/cert/live/example.com/fullchain.pem;
        ssl_certificate_key     /etc/nginx/cert/live/example.com/privkey.pem;
        ssl_trusted_certificate /etc/nginx/cert/live/example.com/chain.pem;
        server_name example.com;
        location / {
            proxy_pass http://127.0.0.1:3000;
        }
    }
    server {
        listen      443 ssl http2;
        listen [::]:443 ssl http2;
        ssl_certificate     /etc/nginx/cert/live/www.example.com/fullchain.pem;
        ssl_certificate_key /etc/nginx/cert/live/www.example.com/privkey.pem;
        server_name www.example.com;
        location / {
            proxy_pass http://127.0.0.1:3000;
        }
    }
}

docker certbot 使用

  • certbot 並沒有官方的 docker image...,雖是有個 Dockerfile,但是內容過長。我建議還是下面這樣就好
FROM alpine:3.4
RUN apk add --update bash certbot
VOLUME ["/etc/letsencrypt"]
  • 先 build 一個 image
docker build -t my:certbot .
  • 為了方便使用跟自動更新續命,寫了一隻 /data/renew_cert.sh
    • /etc/letsencrypt 是 certbot 下載訊息跟證書的存放路徑。
    • -w 是指定的 nginx root。 certbot 要靠這個寫入指定路徑檔案讓 let's encrypt 檢查。
    • -d 是要申請的 domain。其實可以一次把全部 domain 都放進去 (-d example.com -d www.example.com),這樣會簽發共用證書。不過這邊是把每個 domain 證書分開。
    • 最新證書會放在 /etc/letsencrypt/live/example.com 下。所以 nginx 設定的 ssl_certificate 路徑不用擔心因為刷新證書而改變。
    • 要注意 /var/log/letsencrypt 底下的 log 每次使用 certbot 都會被清空重寫。所以還是自己 >> le.log 2>> le.error.log 吧。
#!/bin/bash
if test -z "$1";then
LIST=(
'example.com'
'www.example.com'
)
else
LIST=(
$1
)
fi

FAILED_LIST=()
WWW_ROOT=/usr/share/nginx/html

for domain in ${LIST[@]};do
docker run \
--rm \
-v /data/app/nginx/cert:/etc/letsencrypt \
-v /data/log/letsencrypt:/var/log/letsencrypt \
-v /data/app/nginx/html:${WWW_ROOT} \
my:certbot \
certbot certonly --verbose --noninteractive --quiet --agree-tos \
--webroot -w ${WWW_ROOT} \
--email="[email protected]" \
-d "$domain"
CODE=$?
if [ $CODE -ne 0 ];then
    FAILED_LIST+=($domain)
fi
done

# output failed domains
if [ ${#FAILED_LIST[@]} -ne 0 ];then
    echo 'failed domain:'
    for (( i=0; i<${#FAILED_LIST[@]}; i++ ));
    do
        echo ${FAILED_LIST[$i]}
    done
fi
  • 執行一次 /data/renew_cert.sh 等證書都下載好。
  • 記得把 nginx.conf 註解掉的 https server block 取消註解。然後 docker exec nginx nginx -s reload 讓 nginx 吃新證書。

自動更新

  • 因為申請下來的證書只有 90 天。所以要刷新他。這邊是用 sudo certbot -e 設置
    • 每週嘗試刷一次
    • 刷完之後隔天 nginx 重載設定
0 3 * * 1 /data/renew_cert.sh >> /data/log/renew_cert.log 2>> /data/log/renew_cert.error.log
0 0 * * 2 docker exec nginx -s reload

檢查 https 運作是否正常

  • 可在 ssllabs 輸入你的 domain 檢查。

其他問題

  • 微信 http 開啟正常,https 卻不能開。

我使用 let's encrypt 證書有的站可以開有的不行。所以確定跟證書沒關係。

微信的白名單機制,我也不是很懂。

  • android 2.3, ie 6 ... 打不開?

這跟瀏覽器沒有支援 SNI (Server Name Indication) 有關,多 domain 的情況下,只能選擇使用共用(泛解析)證書。不然只能把不同 domain 放到不同 ip 下。

  • java HttpPost exception?

嗯,請升級 java 到 8 版本,或換 lib。請看這篇

© 2019 ~ 2024 Hsüan, Powered by Gatsby, Theme Material UI