Docker环境中使用Let's Encrypt为Nginx提供免费SSL证书

为网站增加SSL层加密,可以有效保护网站内容,防止包括用户账户等敏感信息泄露事件的发生。在这个隐私泄漏泛滥的社会,很有必要为自己的网站增加SSL加密功能。

获取SSL证书通常需要向权威的证书注册机构(Registry Authority,CA)申请,然后由证书认证机构(Certification Authority,CA)签发,市面上一般的SSL证书价格昂贵,对于个人网站来说负担较重。本文介绍一种在Docker环境中,从认证机构Let's Encrypt获取免费SSL证书,并应用在Nginx服务器中的方法。

Let's Encrypt简介

Let's Encrypt作为一个公共且免费的SSL项目逐渐被广大用户传播和使用,由Mozilla、Cisco、Akamai、IdenTrust、EFF、Facebook等组织人员发起,主要的目的也是为了推进网站从HTTP向HTTPS过度的进程,兼容包括FireFox、Chrome在内的主流浏览器。

安装Docker CE

本文运行Nginx和生成证书均在Docker环境中,因此首先需要安装Docker运行环境,安装方法参考官方教程,或参考我的博客Kali 2018.1 中安装Docker CE,对于Debian系统只需要去掉其中的修改配置文件章节即可。

使用Certbot生成SSL证书

Certbot软件实现了Let's Encrypt认证,下面介绍使用Certbot生成SSL证书的方法。

Certbot以插件形式提供多种证书生成方式,包括apache、nginx、webroot、standalone、DNS plugins、manual等。由于本文使用Docker环境下运行Certbot,官方版本中没有安装Nginx插件,本文使用webroot形式产生SSL证书。

创建文件/文件夹

  1. 使用webroot插件需要在${webroot-path}/.well-known/acme-challenge路径下创建临时文件,且需要能够通过网络访问该文件夹。本文中Nginx和Certbot在Docker环境中运行,因此可以在本地创建该文件,然后挂载到Nginx和Certbot容器中指定的路径下。创建文件夹:
$ mkdir -p ./nginx/html/.well-known/acme-challenge/
  1. 创建文件夹certbot用于存放生成的秘钥信息。
$ mkdir ./certbot/

启动Nginx容器

为了能够通过网络访问${webroot-path}/.well-known/acme-challenge文件夹,需要启动Nginx服务器,并进行相应的配置。

创建文件夹存放配置文件:

$ mkdir -p ./nginx/conf.d/

创建Nginx配置文件:

$ touch ./nginx/conf.d/default.conf
$ vi ./nginx/conf.d/default.conf

default.conf文件中编辑如下内容:

server {
    listen 80;
    server_name your.domain.com;
    location ^~ /.well-known/acme-challenge/ {
        root  /usr/share/nginx/html;
    }
}

然后启动Nginx容器:

$ sudo docker run -d --name nginx -p 80:80 -p 443:443 \
  -v `pwd`/nginx/conf.d/:/etc/nginx/conf.d/ \
  -v `pwd`/nginx/html/:/usr/share/nginx/html/ \
  -v `pwd`/certbot/letsencrypt/:/etc/nginx/letsencrypt/ \
  nginx:alpine

生成SSL证书

假设Nginx容器挂载了本地的文件夹,用于存放其部署的网页信息,文件夹路径为:./nginx/html/。生成SSL证书的命令如下:

(更改其中的your.domain.comyour@email.com

$ sudo docker run -it --rm \
  -v `pwd`/certbot/letsencrypt/:/etc/letsencrypt/ \
  -v `pwd`/nginx/html/.well-known/acme-challenge/:/var/www/acm-challenge/ \
  -v `pwd`/nginx/html/:/var/www/html/ \
  -v `pwd`/certbot/log/:/var/log/letsencrypt/ \
  certbot/certbot \
    certonly --webroot -w /var/www/html/ \
    -d your.domain.com --renew-by-default \
    --email your@email.com --agree-tos

当然,Nginx也可只作为反向代理服务器使用,此时可能不需要挂载本地文件夹,这种情况下可直接将/var/www/acm-challenge/作为webroot参数,执行命令如下:

$ sudo docker run -it --rm \
  -v `pwd`/certbot/letsencrypt/:/etc/letsencrypt/ \
  -v `pwd`/nginx/html/.well-known/acme-challenge/:/var/www/acm-challenge/ \
  -v `pwd`/certbot/log/:/var/log/letsencrypt/ \
  certbot/certbot \
    certonly --webroot -w /var/www/acm-challenge/ \
    -d your.domain.com --renew-by-default \
    --email your@email.com --agree-tos

生成的证书位于./certbot/letsencrypt/文件夹中。该证书的有效期为90天,后文会介绍如何自动的在失效前更新证书。

配置并启动Nginx容器

本文将证书所在目录挂载到Nginx容器的/etc/nginx/letsencrypt/路径下,将./nginx/html/挂载到/usr/share/nginx/html路径下,修改Nginx配置文件default.conf如下:

server {
    listen 80;
    server_name your.domain.com;
    # 重定向到443端口
    location / {
      return 301 https://your.domain.com$request_uri;
    } 
    location ^~ /.well-known/acme-challenge/ {
        root  /usr/share/nginx/html;
    }
}
server {
    listen       443 ssl http2;
    server_name  your.domain.com;
    ssl on;
    # 添加SSL证书
    ssl_certificate /etc/nginx/letsencrypt/live/your.domain.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/letsencrypt/live/your.domain.com/privkey.pem;
    
    location / {
        proxy_pass  your.domain.com:port;
        ...
    }
}

停用并删除运行的Nginx容器,并重新运行:

$ sudo docker stop nginx
$ sudo docker rm nginx
$ sudo docker run -d --name nginx -p 80:80 -p 443:443 \
  -v `pwd`/nginx/conf.d/:/etc/nginx/conf.d/ \
  -v `pwd`/nginx/html/:/usr/share/nginx/html/ \
  -v `pwd`/nginx/log/:/var/log/nginx/ \
  -v `pwd`/certbot/letsencrypt/:/etc/nginx/letsencrypt/ \
  nginx:alpine

自动更新证书

基于上述Nginx的配置,使用如下命令可检查证书是否即将失效,如失效则会更新证书,并将日志保存在本地./certbot/log/文件夹中。

$ sudo docker run -it --rm \
  -v `pwd`/certbot/letsencrypt/:/etc/letsencrypt/ \
  -v `pwd`/nginx/html/.well-known/acme-challenge/:/var/www/acm-challenge/ \
  -v `pwd`/nginx/html/:/var/www/html/ \
  -v `pwd`/certbot/log/:/var/log/letsencrypt/ \
  certbot/certbot \
    renew --webroot -w /var/www/html/

为了使电脑能够自动检查并更新证书,可通过创建计划任务crontab实现。首先创建更新脚本certbotrenew.sh,保存在./certbot/shell/路径下,文件内容为:

#!/bin/bash
docker run -it --rm \
  -v `pwd`/certbot/letsencrypt/:/etc/letsencrypt/  \
  -v `pwd`/nginx/html/.well-known/acme-challenge/:/var/www/acm-challenge/ \
  -v `pwd`/nginx/html/:/var/www/html/ \
  -v `pwd`/certbot/log/:/var/log/letsencrypt/ \
  certbot/certbot \
    renew --webroot -w /var/www/html/

为该文件增加执行权限:

$ chmod +x path/to/certbotrenew.sh

然后使用root用户创建crontab:

# crontab -e

添加如下内容:

0 0 * * 5 /path/to/certbotrenew.sh

即每周五的0点0分执行脚本certbotrenew.sh,检查证书是否即将过期,在有效期还有30天时会更新证书。