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。請看這篇