docker部署certbot与nginx来获取ssl证书添加https及自动更新
起因
-
原先一直有一个腾讯云的1核和1G的服务器在闲着,只是偶尔用下
frp
来映射开发演示,这次想再利用下,于是试下搭建bitwarden
,转而找到更加小巧的vaultwarden
,但实际浏览器插件测试必须是要https
才行,所以必须要有域名及证书,顺便就想试下certbot
来自动续签。 -
域名随便在阿里云上弄了个
top
的,10年¥189,要想解析到服务器是要备案的,所以又在腾讯云上备案,大约有7、8个工作日吧,备案才通过,此时可以正常访问了,然后还要30日内进行公安备案。 -
由于服务器上的
nginx
、frp
、vaultwarden
都是docker
来部署的,那certbot
也顺便就用docker
,官方虽然提供了docker
镜像,但实际上并不是很推荐使用,因为容器内比较难触发外部的一些操作,会导致一些自动配置的功能很难使用,参考官方文档:Running with Docker。没实际安装前个人也觉得用docker
更方便,但实际操作完成后会发现还是官网说的对,没特殊情况不要用docker
来做这些。
certbot
发放证书前需要验证域名的合法性,确保你申请证书的域名是你的,验证方式主要分两种:
-
通过http请求验证,会向
http://你的域名/.well-known/acme-challenge/
发送一条请求。 -
通过dns解析,需要在域名解析中增加一条
_acme-challenge.你的域名
的TXT记录用于验证。
certbot
的命令有很多种不同的参数,这些参数也主要是为了自动生成http验证文件或自动添加dns解析等功能,主要的大体说下:
-
--manual
,手动配置,你可以自己选择http验证还是dns验证都行,需要自己纯手工去配置http验证文件或是dns解析,个人没有做测试。 -
--nginx
,走http验证,如果不用docker
且用nginx感觉是最方便的,会自动修改nginx的配置文件,增加http请求的访问配置及后续的ssl配置,但由于我们certbot
是在docker
容器内,所以要修改另一个nginx
容器的配置文件及控制启停不太好实现,所以不采用这种方式。查资料时发现有人会将nginx
和certbot
通过dockerfile
打包到一个镜像内,这样此命令就比较方便的执行了,感觉是个好办法,但个人没有测试了解。 -
--dns-xxx
,走dns解析验证,这个也很方便,配置好后会自动在域名解析中增加一条记录进行域名验证,但官方目前只支持国外的一些dns服务商,国内的阿里云、dnspod等貌似也有第三方的插件,可以自己去github
上找下。如果想申请通配符泛域名的,则只能通过此方式,目前个人没有测试。 -
--standalone
,走http验证,这种方式你就算是没有nginx
等服务,仍然可以验证域名并获取证书,它会虚拟一个服务来进行验证,但需要保证你的80
端口别被占用,由于我这边有nginx
了,所以便没有用此类方法测试。 -
--webroot
,走http验证,已有类似nginx等服务的情况下可以用此方法,会自动在你配置的目录下生成http验证文件,与standalone
相比好处是不需要预留80端口,也就是原有的网站不需要停止就可以申请证书,而缺点就是你还需要在nginx
等服务中手动配置/.well-known/acme-challenge/
使其对应到自动生成的验证文件上。后续我们用的就是这种。
通过webroot
的方式运行后,会在两个地方产生文件:
-
一个是在命令行配置的
--webroot-path
指定目录下产生临时文件,用于http验证。 -
一个是验证成功会产生存放证书的文件,默认是在
/etc/letsencrypt/live/你的域名
目录下,需要注意的是这些证书是个软链接,对应着../archive
下,所以我们在做volume
映射时不要只映射到live
这个目录,而是要映射/etc/letsencrypt
这个目录,否则无法找到相关的证书文件。
以上两个文件虽然是certbot
容器生成的,但我们nginx
容器都会用到,所以我们需要用宿主机做为中转,在certbot
和nginx
容器中都配置映射到宿主机的同一个目录,从而实现certbot
容器和nginx
容器共用文件。我的服务器home/ubuntu/docker
下的目录可参照:
├── certbot
│ ├── ssl //存放证书
│ └── www //存放http验证的临时文件
├── nginx
│ ├── conf
│ │ └── conf.d //配置文件
│ │ └── valtwarden.conf //配置文件
│ ├── log //日志
│ └── web //存放网站
└── docker-compose.yml //docker-compse文件
准备
-
有个服务器
-
有个已经备案且解析到服务器上的域名
-
安装
docker
和docker compose
,照着官网来就行,很详细:Docker Documentation,docker
如果下载镜像慢,可以注册个阿里云账号,然后在【容器镜像服务】中找到【镜像工具】下的【镜像加速器】中按照说明来配置。 -
docker
镜像会用到:
开始
-
主要是把
volume
映射弄好,顺便配置好nginx
的相关http验证:-
docker-compose.yml:
services: nginx: container_name: nginx image: nginx restart: unless-stopped ports: - "80:80" - "443:443" environment: TZ : 'Asia/Shanghai' volumes: - /home/ubuntu/docker/nginx/conf:/etc/nginx # 配置文件 - /home/ubuntu/docker/nginx/web:/usr/share/nginx # 网站 - /home/ubuntu/docker/nginx/log:/var/log/nginx #日志 - /home/ubuntu/docker/certbot/www:/usr/share/certbot/www:ro #http验证目录,可设置ro为只读,因为文件最终时通过certbot容器映射来的 - /home/ubuntu/docker/certbot/ssl:/usr/share/certbot/ssl:ro #证书位置,同上 command: nginx -g 'daemon off;' certbot: container_name: certbot image: certbot/certbot volumes: - /home/ubuntu/docker/certbot/www:/usr/share/certbot/www:rw #http验证目录,可设置rw可写,与nginx容器对应的宿主机目录时一致的 - /home/ubuntu/docker/certbot/ssl:/etc/letsencrypt:rw #证书位置,同上,注意不要只映射到live,而是它的上一级
-
nginx配置valtwarden.conf:
server { listen 80; listen [::]:80; server_name bitwarden.xxx.top;#域名 server_tokens off; #配置http验证可访问 location /.well-known/acme-challenge/ { #此目录都是nginx容器内的目录,对应宿主机volumes中的http验证目录,而宿主机的又与certbot容器中命令--webroot-path指定目录一致,从而就整个串起来了,解决了http验证问题 root /usr/share/certbot/www; } #http跳转到https location / { return 301 https://bitwarden.xxx.top$request_uri; } }
-
-
上方的volumes映射串联关系如下,通过宿主机来实现
certbot
和nginx
中相关的目录实际上是同一个目录,除了certbot
中的/etc/letsencrypt
是固定的外,其余的目录都是可以随便找目录放,只要对应起来就行。目录 certbot 宿主机 nginx http验证目录 /usr/share/certbot/www /home/ubuntu/docker/certbot/www /usr/share/certbot/www 证书目录 /etc/letsencrypt /home/ubuntu/docker/certbot/ssl /usr/share/certbot/ssl -
docker compose up -d nginx
先把nginx开启,然后测试证书发放是否可以:# --dry-run是只测试不实际生成; --webroot-path对应着certbot内的http验证目录;-d后面是域名;--rm是运行后接着删除,certbot容器不需要一直开启,只是启动下生成证书即可 docker compose run --rm certbot certonly --webroot --webroot-path /usr/share/certbot/www/ --dry-run -d bitwarden.xxx.top
-
输入邮箱等信息,如果提示
The dry run was successful
则说明成功,可以去掉--dry-run
参数来进行实际的获取证书docker compose run --rm certbot certonly --webroot --webroot-path /usr/share/certbot/www/ -d bitwarden.xxx.top
-
如果上方正常,则会在
certbot
容器下的/etc/letsencrypt
下也就是nginx
容器下的/usr/share/certbot/ssl
下产生证书,此时,只需要配置nginx配置ssl即可,还是在文件valtwarden.conf中:server { listen 80; listen [::]:80; server_name bitwarden.xxx.top;#域名 server_tokens off; #配置http验证可访问 location /.well-known/acme-challenge/ { #此目录都是nginx容器内的目录,对应宿主机volumes中的http验证目录,而宿主机的又与certbot容器中命令--webroot-path指定目录一致,从而就整个串起来了,解决了http验证问题 root /usr/share/certbot/www; } #http跳转到https location / { return 301 https://bitwarden.xxx.top$request_uri; } } #ssl配置 server { listen 443 ssl; server_name bitwarden.xxx.top; #日志 access_log /var/log/nginx/bitwarden.access.log main; error_log /var/log/nginx/bitwarden.error.log; #证书信息,这里的路径是nginx容器内的 ssl_certificate /usr/share/certbot/ssl/live/bitwarden.xxx.top/fullchain.pem; ssl_certificate_key /usr/share/certbot/ssl/live/bitwarden.xxx.top/privkey.pem; ssl_session_timeout 5m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; location / { proxy_pass http://vaultwarden;#这里是代理到valutwarden容器上了 proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }
-
以上
docker compose exec nginx nginx -t
检测配置nginx文件是否有问题,docker compose exec nginx nginx -s reload
或者是直接docker compose restart nginx
来重启nginx -
正常启动的话应该就没问题了,我们接着需要做定期更新证书,主要就是为了这个,否则直接腾讯云、阿里云申请一年的免费证书好了。增加计划任务,
sudo crontab -e
打开定时任务编辑,每月1号和15号运行一次#1、切换到docker-compose.yml所在目录;2、然后更新证书,只有距离过期时间30天内才会真正成功;3、然后重载nginx使新证书生效 0 0 1,15 * * cd /home/ubuntu/docker && /usr/bin/docker compose run --rm certbot renew && /usr/bin/docker compose exec nginx nginx -s reload #上方命令测试可以获取新证书,但是reload后实际测试新证书没有生效,可以换成下方的直接重启nginx试试,网上有人说reload可以,有人说必须restart,我这边的情况是第一次没自动生效,手动执行了reload后刷新浏览器可以了,但第二次同样没自动生效,手动reload也不行,restart后可以了,所以就很怪。而且都没自动生效,怀疑nginx的操作并没有等待renew执行完成后,可能在renew时间长在nginx重启之后才执行完成了。总之目前还没结论,所以这里可能还是不应该用docker,而应该用原生的钩子来触发比较好? 0 0 1,15 * * cd /home/ubuntu/docker && /usr/bin/docker compose run --rm certbot renew && /usr/bin/docker compose restart nginx
以上是计划任务相关。
certbot
容器中renew
虽然有--deploy-hook
等实际更新后触发钩子的功能,但由于和nginx
不在同一个容器,因此无法触发nginx
重启,所以上述命令不管是否证书真正更新成功nginx
是一定会重启的,最好放到深夜执行。搜索了下从一个容器中控制另一个容器的操作,一种是本容器内装docker
,然后映射宿主机内的docker.sock
,这样容器内操作docker
就和宿主机内操作一致了;还有一种就是ssh
来连接另一个容器来操作了,这两种都挺麻烦的,这里就没必要也不做尝试了。
其它
certbot
申请证书是有次数限制的,重复申请5次以上,再次请求会报too many certificates already issued...
等错误,会停用一周后才能再继续,此时只能再换个域名或者是等一周了,参考速率限制。- 除官网外的参考文档:HTTPS using Nginx and Let's encrypt in Docker,写的很详细。
- 最后的最后,感觉还是官网说的对,压根没必要用
docker
来运行certbot
,首先这个容器不需要常开,只是用一次就关闭的,而容器的各种空间映射增加了复杂度,其次一些回调钩子由于容器的原因无法调用,所以还是建议不要有强迫症,直接在宿主机安装就好了。而且更建议安装dns插件来用dns方式验证,这样可以申请通配符ssl,非常适用于多个二级域名的情况,但这些dns插件用docker的话更不容易找到相关的镜像,自己实现起来也很麻烦。 - 个人的网站可以尝试下
caddy
,自带https
,不需要这么麻烦的配置。