部署 使用 acme.sh 给 Nginx 安装 Let’ s Encrypt 提供的免费 SSL 证书

huacnlee · 2016年12月23日 · 最后由 kikyous 回复于 2022年01月23日 · 53024 次阅读
本帖已被管理员设置为精华贴

最近 Apple 在强推 SSL 了,非 SSL 的网络请求无法通过审核,于是发现有好多的朋友都在给网站配置 HTTPS,同时他们也遇到了好多坑。

其实有更简单的方式,使用 Let's Encrypt,关于这个,这里就不多讲了,之前有人介绍过:

以及前面还有一篇介绍用 Let's Encrypt 官方教程里面的工具安装的文章:https://ruby-china.org/topics/31942

我这里要介绍的是另外一个 acme.sh 这个是用 Shell 脚本编写的,安装更容易,Let's Encrypt 那个 certbot 工具需要安装一大堆系统库以及 Python 库,Python 的 pip 在国内还会有墙的问题...

安装 acme.sh

curl https://get.acme.sh | sh

PS: 第一次可能会遇到 curl: (6) Couldn't resolve host 'get.acme.sh' 重试一下就可以了。

然后重新载入一下 .bashrc

source ~/.bashrc 

现在你就有了 acme.sh 的命令。

申请签发 SSL 证书

acme.sh --issue -d www.your-app.com -w /home/ubuntu/www/your-app/current/public

NOTE! 上面这段过程将会往 /home/ubuntu/www/your-app/current/public 创建一个 .well-known 的文件夹,同时 Let’s Encrypt 将会通过你要注册的域名去访问那个文件来确定权限,它可能会去访问 http://www.your-app.com/.well-known/ 这个路径。

所以你需要确保 /home/ubuntu/www/your-app/current/public 是在 Nginx 上配置成 root 目录,里面任意文件可以直接域名访问的。

如果成功的话,你就会看到这样的结果:

[Fri Dec 23 11:20:15 CST 2016] Renew: 'www.your-app.com'
[Fri Dec 23 11:20:15 CST 2016] Single domain='www.your-app.com'
[Fri Dec 23 11:20:15 CST 2016] Getting domain auth token for each domain
[Fri Dec 23 11:20:15 CST 2016] Getting webroot for domain='www.your-app.com'
[Fri Dec 23 11:20:15 CST 2016] _w='/home/ubuntu/www/your-app/current/public/'
[Fri Dec 23 11:20:15 CST 2016] Getting new-authz for domain='www.your-app.com'
[Fri Dec 23 11:08:57 CST 2016] The new-authz request is ok.
[Fri Dec 23 11:08:57 CST 2016] Verifying:www.your-app.com
[Fri Dec 23 11:09:01 CST 2016] Success
[Fri Dec 23 11:09:01 CST 2016] Verify finished, start to sign.
[Fri Dec 23 11:09:02 CST 2016] Cert success.
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
[Fri Dec 23 11:09:02 CST 2016] Your cert is in  /home/ubuntu/.acme.sh/www.your-app.com/www.your-app.com.cer 
[Fri Dec 23 11:09:02 CST 2016] Your cert key is in  /home/ubuntu/.acme.sh/www.your-app.com/www.your-app.com.key 
[Fri Dec 23 11:09:04 CST 2016] The intermediate CA cert is in  /home/ubuntu/.acme.sh/www.your-app.com/ca.cer 
[Fri Dec 23 11:09:04 CST 2016] And the full chain certs is there:  /home/ubuntu/.acme.sh/www.your-app.com/fullchain.cer 

你的证书文件已经申请成功了,并放到了 ~/.acme.sh/ 目录里面。

TIP: 所有的 acme.sh 配置都记录在 ~/.acme.sh/ 目录里面,acme.sh 有自动的配置读取,并按域名划分,下次你再次执行的时候,它知道你之前是用的那个目录,只需要告诉它域名就好了。

将 SSL 证书安装到网站的路径,并配置好 restart Nginx 的动作

NOTE: 这个比较重要,因为它会让 acme.sh 记住重启 Nginx 的命令,以后自动更新证书的动作需要重启 Nginx

acme.sh --installcert -d www.your-app.com \
               --keypath       /home/ubuntu/www/ssl/www.your-app.com.key  \
               --fullchainpath /home/ubuntu/www/ssl/www.your-app.com.key.pem \
               --reloadcmd     "sudo service nginx force-reload"

然后你会看到结果

[Fri Dec 23 11:59:57 CST 2016] Installing key to:/home/ubuntu/www/ssl/www.your-app.com.key
[Fri Dec 23 11:59:57 CST 2016] Installing full chain to:/home/ubuntu/www/ssl/www.your-app.com.key.pem
[Fri Dec 23 11:59:57 CST 2016] Run Le_ReloadCmd: sudo service nginx force-reload
Restarting nginx: nginx.
[Fri Dec 23 11:59:58 CST 2016] Reload success

修改一下 sudoer 文件,让 sudo service nginx force-reload 不需要输入密码

sudo visudo

打开文件以后新增:

NOTE: ubuntu 是 acme.sh 安装所用的账号

ubuntu  ALL=(ALL) NOPASSWD: /usr/sbin/service nginx force-reload

生成 dhparam.pem 文件

openssl dhparam -out /home/ubuntu/www/ssl/dhparam.pem 2048

修改 Nginx 启用 SSL

http {
  # 新增
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  # 兼容其他老浏览器的 ssl_ciphers 设置请访问 https://wiki.mozilla.org/Security/Server_Side_TLS

  server {
    listen 80 default_server;
    # 新增
    listen 443 ssl;
    ssl_certificate         /home/ubuntu/www/ssl/www.your-app.com.key.pem;
    ssl_certificate_key     /home/ubuntu/www/ssl/www.your-app.com.key;
    # ssl_dhparam 
    ssl_dhparam             /home/ubuntu/www/ssl/dhparam.pem;

    # 其他省略
  }
}

检查 Nginx 配置是否正确后重启

sudo service nginx configtest
sudo service nginx restart

验证 SSL

访问 ssllabs.com 输入你的域名,检查 SSL 的配置是否都正常:

https://ssllabs.com/ssltest/analyze.html?d=ruby-china.org

确保验证结果有 A 以上,否则根据提示调整问题

后续维护

Let's Encrypt 的证书有效期是 90 天的,你需要定期 renew 重新申请,这部分 acme.sh 以及帮你做了,在安装的时候往 crontab 增加了一行每天执行的命令 acme.sh --cron:

$ crontab -l
0 0 * * * "/home/ubuntu/.acme.sh"/acme.sh --cron --home "/home/ubuntu/.acme.sh" > /dev/null

PS: 下面这段你可以尝试执行一下,看看是否正确

"/home/ubuntu/.acme.sh"/acme.sh --cron --home "/home/ubuntu/.acme.sh"

这样就是正常的:

[Fri Dec 23 11:50:30 CST 2016] Renew: 'www.your-app.com'
[Fri Dec 23 11:50:30 CST 2016] Skip, Next renewal time is: Tue Feb 21 03:20:54 UTC 2017
[Fri Dec 23 11:50:30 CST 2016] Add '--force' to force to renew.
[Fri Dec 23 11:50:30 CST 2016] Skipped www.your-app.com

acme.sh --cron 命令执行以后将会 申请新的证书 并放到相同的文件路径。由于前面执行 --installcert 的时候告知了重新 Nginx 的方法,acme.sh 也同时会在证书更新以后重启 Nginx。

最后走一下 acme.sh --cron 的流程看看能否正确执行

acme.sh --cron -f

这个过程应该会得到这样的结果,并在最后重启 Nginx (不需要输入密码)

[Tue Dec 27 14:28:09 CST 2016] Renew: 'www.your-app.com'
[Tue Dec 27 14:28:09 CST 2016] Single domain='www.your-app.com'
[Tue Dec 27 14:28:09 CST 2016] Getting domain auth token for each domain
[Tue Dec 27 14:28:09 CST 2016] Getting webroot for domain='www.your-app.com'
[Tue Dec 27 14:28:09 CST 2016] _w='/home/ubuntu/www/your-app/current/public/'
[Tue Dec 27 14:28:09 CST 2016] Getting new-authz for domain='www.your-app.com'
[Tue Dec 27 14:28:16 CST 2016] The new-authz request is ok.
[Tue Dec 27 14:28:16 CST 2016] www.your-app.com is already verified, skip.
[Tue Dec 27 14:28:16 CST 2016] www.your-app.com is already verified, skip http-01.
[Tue Dec 27 14:28:16 CST 2016] www.your-app.com is already verified, skip http-01.
[Tue Dec 27 14:28:16 CST 2016] Verify finished, start to sign.
[Tue Dec 27 14:28:19 CST 2016] Cert success.
... 省略
[Fri Dec 23 11:09:02 CST 2016] Your cert is in  /home/ubuntu/.acme.sh/www.your-app.com/www.your-app.com.cer 
[Fri Dec 23 11:09:02 CST 2016] Your cert key is in  /home/ubuntu/.acme.sh/www.your-app.com/www.your-app.com.key 
[Fri Dec 23 11:09:04 CST 2016] The intermediate CA cert is in  /home/ubuntu/.acme.sh/www.your-app.com/ca.cer 
[Fri Dec 23 11:09:04 CST 2016] And the full chain certs is there:  /home/ubuntu/.acme.sh/www.your-app.com/fullchain.cer 
[Tue Dec 27 14:28:22 CST 2016] Run Le_ReloadCmd: sudo service nginx force-reload
 * Reloading nginx nginx                                                                                                                                     [ OK ] 
[Tue Dec 27 14:28:22 CST 2016] Reload success

一些注意事项

  • ssl_dhparam 未配置,将导致 ssllabs.com 的评分降到 B,并给 This server supports weak Diffie-Hellman (DH) key exchange parameters. Grade capped to B. 的警告。
  • ssl_prefer_server_ciphers on 也是一个必要的配置,否则会 A+ 变成 A-;
  • 如果你需要兼容老系统或老浏览器的话,你需要配置 ssl_ciphers,详见 Mozilla Server_Side_TLS 的介绍,Nginx 里面 ssl_ciphers 默认值是 HIGH:!aNULL:!MD5; ref

2021 年更新

现在建议大家使用 Caddy 来实现自动 SSL,会简单很多,目前 Ruby China 已经用这个方式了,不再需要手动维护。

https://caddyserver.com/docs/quick-starts/https

Cool, Let's Encrypt!

现在又拍支持一键申请 Let’s Encrypt 证书,并且能够自动续期,还蛮不错的。

哈哈,我的 nginx ssl 配置检查结果是 A+。

今天我做了相同的操作,不过是使用的它官方推荐的工具 certbot-auto,跟 Nginx 集合得也蛮不错的

没错 这个贼好用。

lgn21st 将本帖设为了精华贴。 12月23日 16:04

@huacnlee 干得漂亮,不知道为什么,我反而觉得 Let's Encrypt 的证书在技术圈要比那些 VerifySign 的商业证书显得有逼格。

#3 楼 @samport 更新了一下,少了 ssl_prefer_server_ciphers 的配置

9 楼 已删除
huacnlee 最近几天,网站一直无法打开 提及了此话题。 12月26日 09:59

感谢分享,刚好为不怎么使用的个人域名加层护甲

刚花 100 多块钱,买了 3 年的的 cheapssl 哀伤😭

安利一下自己的 HTTPS-PORTAL,差不多就是这些东西,不过包装成 Docker container 适合和 docker 部署的 app 一起使用。

这个不错,,,,加密加密

startssl 直接 3 年免费

@huacnlee

# /etc/letsencrypt/options-ssl-nginx.conf
ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;

ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-SHA ECDHE-ECDSA-AES256-SHA ECDHE-ECDSA-AES128-SHA256 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES128-GCM-SHA256 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-AES128-SHA ECDHE-RSA-AES128-SHA256 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES128-SHA DHE-RSA-AES256-SHA DHE-RSA-AES128-SHA256 DHE-RSA-AES256-SHA256 EDH-RSA-DES-CBC3-SHA";

#15 楼 @sunfjun 这个东西的主要意义不是免费,而是自动化。

@huacnlee 看了下 ruby-china 评测是 A+ 啊,按照教程我自己网站配完后只有 A,ie8 那减分了,楼主是怎么配的呢。

#18 楼 @tim_lang ssl_ciphers 要配置,看 #16 楼 @mimosa 的回复

#19 楼 @huacnlee 非常感谢,话说 https 对 seo 也是很有好处的,当然指 google,百度不知道怎么样,可以看看这篇文章:https://ahrefs.com/blog/http-vs-https-for-seo/

公司网站的静态资源都放阿里云存储的,试了下,直接访问 https 的资源显示无证书,让人很无语啊。

22 楼 已删除
24 楼 已删除

对于生成 DH 参数的命令:openssl dhparam -out /home/ubuntu/www/ssl/dhparam.pem 2048

我们最近遇到一个问题,是快递 100 由于使用 java 6,不支持 2048 位,必须使用 1024,所以如果系统有和其他系统连接,还是要注意一下。

@huacnlee 要开启 ssl_stabling,ssl_trusted_certificate 这个后面应该是啥?

ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate ...;

ssl_prefer_server_ciphers on; 这个会导致 nginx 配置失败

nginx 版本是 nginx/1.10.3

您好,我照着做了生成了证书但最后一步 acme.sh --cron -f 报错了 Run reload cmd: service nginx force-reload nginx.service is not active, cannot reload. Reload error for :tisfeng.com Error renew tisfeng.com.

请问这是什么原因

自己乱搞搞,发现突然又好了。。。诶

请问现在这个还能签发证书么?

我输入

acme.sh --issue -d subdomain.domain.com --webroot /home/app/public 

之后得到 challenge 不存在的报错,但我十分确定目录没问题,查看没有 challenge 因为是它自动完成后删除了,以下是 log

Changing owner/group of .well-known to root:root
[Thu Aug 17 15:06:09 CST 2017] url='https://acme-v01.api.letsencrypt.org/acme/challenge/u0FsYtePfxD34xYlhDA_dqdvYhMIHnybo_vx30r56wA/1769980106'
[Thu Aug 17 15:06:09 CST 2017] payload='{"resource": "challenge", "keyAuthorization": "MAkvAMKQ3bCT3it0-sx4ZufmZxtEsTkUncsAqVy-iRw._VvrU6EE4CB0GRLOlaXCczJU63JAchPg8nrv4mbtF7w"}'
[Thu Aug 17 15:06:09 CST 2017] POST
[Thu Aug 17 15:06:09 CST 2017] url='https://acme-v01.api.letsencrypt.org/acme/challenge/u0FsYtePfxD34xYlhDA_dqdvYhMIHnybo_vx30r56wA/1769980106'
[Thu Aug 17 15:06:09 CST 2017] _CURL='curl -L --silent --dump-header /root/.acme.sh/http.header '
[Thu Aug 17 15:06:11 CST 2017] _ret='0'
[Thu Aug 17 15:06:11 CST 2017] code='202'
[Thu Aug 17 15:06:11 CST 2017] sleep 2 secs to verify
[Thu Aug 17 15:06:13 CST 2017] checking
[Thu Aug 17 15:06:13 CST 2017] GET
[Thu Aug 17 15:06:13 CST 2017] url='https://acme-v01.api.letsencrypt.org/acme/challenge/u0FsYtePfxD34xYlhDA_dqdvYhMIHnybo_vx30r56wA/1769980106'
[Thu Aug 17 15:06:13 CST 2017] timeout

好象是获取 api 失败了,这是脚本的问题么?


不是脚本的问题,我 public 路径没有配置好

根据官方文档,这里 --reloadcmd "sudo service nginx reload 要修改为 --reloadcmd "sudo service nginx force-reload

一个小提醒,这里用的是 service nginx force-reload, 不是 service nginx reload, 据测试,reload 并不会重新加载证书,所以用的 force-reload)

@huacnlee

rocLv 回复

已更新

执行:

acme.sh --installcert -d www.your-app.com \
               --keypath       /home/ubuntu/www/ssl/www.your-app.com.key  \
               --fullchainpath /home/ubuntu/www/ssl/www.your-app.com.key.pem \
               --reloadcmd     "sudo service nginx force-reload"

会提示:

[Thu Nov 30 22:40:54 CST 2017] Installing key to:/home/ubuntu/www/ssl/www.your-app.com.key
/home/deploy/.acme.sh/acme.sh: line 4349: /home/ubuntu/www/ssl/www.your-app.com.key: No such file or directory
[Thu Nov 30 22:40:54 CST 2017] Installing full chain to:/home/ubuntu/www/ssl/www.your-app.com.key.pem
/home/deploy/.acme.sh/acme.sh: line 4357: /home/ubuntu/www/ssl/www.your-app.com.key.pem: No such file or directory
[Thu Nov 30 22:40:54 CST 2017] Run reload cmd: sudo service nginx force-reload
[Thu Nov 30 22:40:54 CST 2017] Reload success

文件不存在,不知道是因为什么原因呢

运行 "/home/ubuntu/.acme.sh"/acme.sh --cron --home "/home/ubuntu/.acme.sh" 结果:

[Mon Dec 11 00:10:02 PST 2017] Renew: 'www.xxx.com'
[Mon Dec 11 00:10:02 PST 2017] Skip, Next renewal time is: Fri Feb  9 08:09:43 UTC 2018
[Mon Dec 11 00:10:02 PST 2017] Add '--force' to force to renew.
[Mon Dec 11 00:10:02 PST 2017] Skipped www.xxx.com
[Mon Dec 11 00:10:02 PST 2017] ===End cron===

因此 crontab 里边应该加入-f 参数吧,否则直接跳过了!

之前设置过几次,一直都是 A,而成不了 A+,在网上搜了很多,最后得出结论是,需要设置 HSTS。

果然设置了 HSTS 之后,就变成了 A+ 了。

Nginx 的配置如下,Apatch、lighthttpd 配置方法参见链接

server {
         ....
         add_header Strict-Transport-Security "max-age=63072000; includeSubdomains;";
}

这里有几个需要注意的:

  • 根据最新官方文档,ssl_certificate 需要设置为fullchain.cer 的值(有时生成的是 pem,有时生成的是 cer);否则网站评级会是 B 级。
ssl_certificate /home/ubuntu/www/ssl/fullchain.cer;
  • ssl_ciphers 的设置可以参考链接

基本上注意这两个问题,就可以拿到A+

执行 acme.sh --cron -f 提示 80 端口被 nginx 占用,咋办

@huacnlee caddy 是可以自己管理证书,但是网站使用的云存储(七牛,阿里云)等的证书呢?还是要手动管理吗? acme.sh 可以自动部署 nginx 和相关云存储的证书

需要 登录 后方可回复, 如果你还没有账号请 注册新账号