一些说明

什么时候会用acme?

  • 在你想为服务开启https但是又不想花钱的时候
  • 在你证书到期又不想手动去替换的时候。 这里的nginx只是个web服务的示例,你也可以使用apache或者其他服务。 (本文中acme=acme.sh,后续申请证书的网址demo为xxx.com

准备工作

docker带来维护的便利,在同一个实例(机器)中可以多次部署而不污染宿主机环境。

1. acme.sh

相关链接: Git链接|How to issue a cert|Run acme.sh in docker|docker-acme部署证书到其他容器

  • acme申请的免费证书通常是3个月有效期,每60天自动续期。
  • 在docker中使用的话,要将acme作为一个docker守护进程运行,然后你就可以用docker exec acme.sh 来执行acme所有命令了,并且用于续期的crontab也运行在容器内而不是宿主机。

acme的两种证书校验模式

  1. HTTP-01挑战:需要服务器有web服务(通常是nginx或apache,并且需要监听http的80端口,如果你没有web服务,可以使用acme.sh脚本自带的web服务,添加—standalone即可)。缺点是不能做泛域名证书申请,并需要使用80端口的服务。
  2. DNS-01挑战:需要DNS服务商提供api去生成校验所需的DNS txt记录(临时,校验完会删除),可以生成泛域名证书。缺点是需要服务商提供api(现在大部分DNS服务商都有提供)。ps:cloudflare不支持tk,ml,ga等免费域名使用api去做DNS自动化挑战(即freenom的免费域名都不能使用cloudflare的api进行DNS-01挑战),所以如果你要用cloudflare管理freenom免费域名的DNS,申请https证书的时候就只能使用HTTP-01挑战

2. nginx

如果你使用付费域名,acme使用DNS-01挑战,那么nginx的配置就非常简单了(因为不需要定制80端口的一条规则,供acme.sh的HTTP-01挑战校验)。

所以这里本文只举使用HTTP-01挑战的例子:

  1. nginx配置文件
# default.conf
server {
    listen 80;
    listen [::]:80;
    server_name xxx.com; 
    location /.well-known/acme-challenge {
	    # acme.sh HTTP-01挑战时,acme.sh会在你指定的路径下生成一个随机文件,供证书颁发机构访问,以证明你对网站的控制权,这里我们使用/var/www/acme-challenge
        root /var/www/acme-challenge;
    }
}
  • 上述配置中没有https 443端口的相关配置,因为此时证书还没有申请下来,如果强行加上443的相关配置nginx会报错。
  • 此时nginx容器的/var/www/acme-challenge目录要和acme.sh容器共享。由acme.sh容器生成文件,nginx容器提供外部访问服务。

开始

使用docker-compose组装

都使用docker了,那多容器肯定使用docker-compose了

一. 申请证书

按照上述内容,加入acme.sh和nginx两个服务:

version: '3.4'
services:
  acme.sh:
    image: neilpang/acme.sh
    container_name: acme.sh    
    command: daemon # docker守护进程
    volumes:
      - ./acme.sh:/acme.sh # 生成证书的默认位置,映射到宿主机,可能你在其他应用会用到
      - ./_acme-challenge:/var/www/acme-challenge # acme和nginx的共享目录
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "443:443"
    restart: always
    volumes:
      - ./web:/var/www/html
      - ./nginx:/etc/nginx/conf.d
      # 配合acme.sh的证书申请校验
      - ./_acme-challenge:/var/www/acme-challenge # acme和nginx的共享目录
 
# 可以看到,上述volums配置中,两个容器的`/var/www/acme-challenge`目录实际上是宿主机上的同一个位置,这样在acme.sh生成校验文件的时候,外界通过nginx服务也能访问到校验文件。

注意:上述docker-compose.yml并不完整,只是为了展示关键步骤。以下3步可以直接执行,也可以在docker-compose.yml写完整之后(见后文<部署证书>)再执行

  1. docker-compose.yml文件所在目录执行:docker-compose up -d,将acme和nginx容器启动。
  2. 注册账号:docker exec acme.sh --register-account -m xxxx@gmail.com
  3. 申请证书,二选一:
    1. 使用http-01申请证书(http-01的方式校验的话,必须加-w参数指定生成校验文件的路径):docker exec acme.sh --issue -d xxx.com -w /var/www/acme-challenge
    2. 使用dns挑战的方式申请证书,更多细节见x-ui使用acme.sh实现https证书签发和自动续期
export CF_Key="9af71xx941344" # 这是cf全局key
export CF_Email="abc@foxmail.com"
docker exec acme.sh --issue --dns dns_cf -d xxx.com -d *.xxx.com
# 添加*.xxx.com是申请泛域名证书

二、部署证书

想要把证书部署到nginx,需要在docker-compose.yml增加部分内容(参考文章顶部acme相关链接,主要是指定label和使用label):

version: '3.4'
services:
  acme.sh:
    image: neilpang/acme.sh
    container_name: acme.sh    
    command: daemon
    volumes:
      - ./acme.sh:/acme.sh
      - /var/run/docker.sock:/var/run/docker.sock # 映射宿主机的docker通信,acme.sh会用此来通知docker守护服务重启nginx服务
	  - ./_acme-challenge:/var/www/acme-challenge
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL=sh.acme.autoload.domain=xxx.com
      # 指定acme.sh部署证书文件位置,nginx的配置里要同步
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE="/acme/key.pem"
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE="/acme/cert.pem"
      - DEPLOY_DOCKER_CONTAINER_CA_FILE="/acme/ca.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/acme/full.pem"
      # 到期续签新证书之后,acme.sh会通知docker守护进程重启nginx
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
  nginx:
    image: nginx
    labels:
      - sh.acme.autoload.domain=xxx.com
    ports:
      - "80:80"
      - "443:443"
    restart: always
    volumes:
      - ./web:/var/www/html
      - ./nginx:/etc/nginx/conf.d
      - ./_acme-challenge:/var/www/acme-challenge

如上使用最终版docker-compose.yml,在执行申请证书里的3个指令之后,执行如下指令: 4. 部署证书到nginx容器:docker exec acme.sh --deploy -d xxx.com --deploy-hook docker 5. 修改nginx配置,加上https的配置:

server {
    listen 80;
    listen [::]:80;
    server_name xxx.com;
    # 续签证书用
    location /.well-known/acme-challenge {
        root /var/www/acme-challenge;
    }
    # 重写http流量到https协议上
    location / {
        rewrite  ^/(.*)$ https://$host/$1 permanent;
    }
}

# https
server {
    listen 443 ssl http2 reuseport;
    server_name xxx.com;
    root /var/www/html;
    # 指定秘钥
    ssl_certificate /acme/cert.pem;
    ssl_certificate_key /acme/key.pem;
    ssl_session_cache shared:SSL:1m;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
}
  1. 重启nginx容器:docker restart 你的nginx容器名或者docker-compose down && docker-compose up -d
  2. 之后每60天acme会自动续期证书,续期完成后会重启你的nginx服务(不重启新证书不生效)。

结语

nginx在一个应用里扮演的角色只是一个领路人,acme带来的https只是基础建设。所以这两个容器只是你服务的一个基础部分,你可以基于此做很多很多事情,比如x-ui使用acme.sh实现https证书签发和自动续期,当然,都是在docker里的。 如果你同时需要端口转发、ddns等服务,可以考虑lucky使用lucky进行ddns、端口转发、https证书acme续签