nginx(docker) 如何更新 letsencrypt 证书
HumphreyDan当前的个人主页是运行在 docker nginx 上的。为了安全起见,把所有的 http 请求已经转发到了 https, 免费的 SSL 证书过期了,所以使用了 letsencrypt 的免费证书来签名。但是它默认的是 3 个月有效期,所以必须得定期去更新。否则就会导致 nginx 服务不可用了。以下为如何在 docker 上更新 letsencrypt 证书.
部署架构
众所周知,docker 的命令配置大都很长,一般是通过shell 脚本或者 docker-compose.yaml 文件来进行管理的。而我采用的就是后者。
如果一台主机上运行了多个 docker 容器,那么这个 docker-compose.yaml 就可能会很大。每次调用 docker-compose up/down 时都会导致所有的容器被销毁重建。所以这里采用了 sidecar 的模式,既可以保证各个容器的配置文件各自保存,又能保证容器之间可以互相访问。
以下以 nginx 和 couchdb 为例.
couchdb/docker-compose.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
   | version: "3.6"
  services:   couchdb:     image: couchdb     container_name: couchdb     volumes:       - "./config/local.ini:/opt/couchdb/etc/local.ini"       - "./data:/opt/couchdb/data"     restart: always     ports:       - '5984:5984'     networks:       - default       - main networks:     main:       external: true
   | 
 
这里新建了一个名为 main 的 networks, 属性 external 设置为 true, 同时设置容器的 networks 为 default + main, 后面只要其他容器都在 main 这个网络内,都是可以直接 localhost 或者端口互相访问的。
nginx/docker-compose.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | version: "3.6"
  services:   nginx:     container_name: "home"     restart: always     image: nginx     ports:       - "80:80"       - "443:443"     volumes:       - ./nginx/html:/usr/share/nginx/html:rw       - ./nginx/etc/nginx:/etc/nginx:rw       - ./certbot/letsencrypt/live:/etc/letsencrypt/live:rw       - ./certbot/letsencrypt/archive:/etc/letsencrypt/archive:rw       - ./certbot/dhparam-2048.pem:/etc/letsencrypt/dhparam-2048.pem:rw     networks:       - default       - main     external_links:       - obsidian_couchdb     environment:       - TZ=Asia/Shanghai networks:    main:      external: true
   | 
 
那么在 nginx 中就可以直接访问 couchdb 了.
1 2 3 4
   | location /couchdb {   rewrite /couchdb(.*) $1 break;   proxy_pass http://couchdb:5984; }
  | 
 

使用 letsencrypt 生成证书
首先看下 letsencrypt 的 docker-compose
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | version: "3.6"
  services:   certbot:     image: certbot/certbot:latest     container_name: home_certbot                    volumes:       - ./letsencrypt:/etc/letsencrypt:rw       - ./log:/var/log:rw     command: certonly -n --webroot -w=/etc/letsencrypt --email dang8080@qq.com -d dang8080.cn  --agree-tos --force-renewal     
   | 
 
letsencrypt 提供了 certbot 命令行工具来生成 SSL 证书. 这里同样使用 docker-compose.
certbot 运行时,需要本机提供一个 http 服务,即外部能访问的 http://[本机域名]/.well-known/acme-challenge/[随机字符串] http 服务。本机域名就是要申请 SSL 证书的域名。
这里的 http 服务有两个选择:
1、如果已经有了 nginx/apache 等服务,那么使用它就行,不过需要配置路由和文件映射(因为是运行在 docker 中)
2、使用 certbot 提供的 http 服务
这里我选择了方案1, nginx 需要添加一条新 location 满足 letsencrypt 访问的需求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | server { 	listen 80; 	listen [::]:80; 	server_name dang8080.cn localhost; 	root /usr/shar/nginx/html;
  	location / { 		root /usr/share/nginx/html; 		return 301 https://dang8080.cn$request_uri; 	}
  	location ^~ /.well-known/acme-challenge/ { 		allow all; 		default_type "text/plain"; 		root /var/www/dang8080.cn; 	} }
  | 
 
注意: /.well-known/acme-challenge 的 root 必须是 /var/www/dang8080.cn 否则会报 404.
certbot 使用的参数
–webroot -w 指定生成的 ssl 证书目录,注意这里是在 docker 中运行的,所以需要将其映射到本地文件系统
–agree-tos 跳过 cli 中的交互式问询
–force-renewal 如果指定目录有证书还没过期,那么也要强制重新更新
–standalone 就是 certbot 开启一个新的 http 服务.
执行完后就能在本地看到生成的 ssl 证书了

配置 nginx
因为 nginx 运行在 docker 中,而生成的 ssl 证书在其他目录,所以需要将 ssl 证书目录也映射到 ngixn 容器中.
1 2 3
   | - ./certbot/letsencrypt/live:/etc/letsencrypt/live:rw - ./certbot/letsencrypt/archive:/etc/letsencrypt/archive:rw - ./certbot/dhparam-2048.pem:/etc/letsencrypt/dhparam-2048.pem:rw
   | 
 
需要说明的是:letsencrypt 生成的 live 目录证书会软链接到 archive 目录,而 docker 对软链接支持不友好,所以需要同时映射 archive 目录和 live 目录
同时为了 ssl 安全,使用 2048位的 DH 参数. 生成方式 openssl dhparam -out dhparam-2048.pem 2048
https.conf
1 2 3 4 5
   | ssl_certificate /etc/letsencrypt/live/dang8080.cn/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/dang8080.cn/privkey.pem; ssl_trusted_certificate /etc/letsencrypt/live/dang8080.cn/chain.pem;
  ssl_dhparam /etc/letsencrypt/dhparam-2048.pem;
   | 
 
定期更新
前面提到,letsencrypt 生成的 ssl 证书一般只有3个月有效期,所以得定期更新.这里使用 crontab 更新就行.
rewnew.sh
1 2 3 4 5 6 7
   | #!/bin/bash
  cd ~/homepage/certbot && sudo docker compose up -d && sudo docker kill --signal=HUP certbot && sudo docker compose down && cd -
   | 
 
或者
1 2 3 4 5 6 7
   | #!/usr/bin/env sh
  sudo docker run -it --rm \   -v /home/[]certbot/letsencrypt:/etc/letsencrypt \   -v /home/[]certbot/log:/var/log \   certbot/certbot \   renew --webroot -w /etc/letsencrypt --quiet && sudo docker kill --signal=HUP certbot
   | 
 
编辑 crontab: crontab -e
1
   | 0 1 * * 0 ~/homepage/certbot/renew.sh
   | 
 
注意: 如果是 crontab 定期更新证书,那么需要修改上面的 nginx 配置.以确保非 /.well-known/acme-challenge/ 请求不会被转发到 https(这时候 https 肯定是不可用的)
1 2 3 4 5 6 7 8 9 10 11
   | # 处理 Let's Encrypt 验证请求 location ^~ /.well-known/acme-challenge/ {     allow all;     default_type "text/plain";     root /var/www/$host; }
  # 其他所有请求重定向到 HTTPS location / {     return 301 https://$host$request_uri; }
   |