SSL证书申请与部署:从零到自动化,避开95%的坑

先说说为什么要自己折腾SSL证书。免费的啊兄弟们!Let’s Encrypt和ZeroSSL都提供90天免费证书,关键是要自动化续期。别跟我说用付费证书,小项目一年几百上千块,还不如把这钱省下来买咖啡。

第一步:选择证书类型和申请方式

现在主流免费证书就两种:

  • Let’s Encrypt:老牌,支持泛域名,推荐用acme.sh工具
  • ZeroSSL:有管理后台,支持90天证书,可以用官网或acme.sh

我个人推荐Let’s Encrypt + acme.sh的组合。acme.sh这个工具写的真的良心,纯Shell脚本,安装简单,自动续期。

bash

安装acme.sh

curl https://get.acme.sh | sh -s email=your@email.com

设置别名方便使用

alias acme.sh=~/.acme.sh/acme.sh
`

安装完成后,申请证书有几种验证方式。最推荐的是DNS API模式,适合泛域名。以阿里云为例:

`bash

先设置DNS API密钥

export Ali_Key="你的AccessKey"
export Ali_Secret="你的AccessKey Secret"

申请泛域名证书

acme.sh --issue --dns dns_ali -d example.com -d *.example.com
`

这个设计真的很反人类,但一旦配好了就一劳永逸。注意AccessKey权限不要给太大,只给DNS解析的权限就够了,安全第一。

开篇先亮出工具,这张图展示acme.sh的安装和证书申请全流程,从终端输入命令到成功拿到证书文件。

第二步:Nginx配置——最容易踩坑的地方

证书拿到手了,接下来就是配置Nginx。这里我踩过一个巨坑:配置完后浏览器一直报ERR_CERT_COMMON_NAME_INVALID。查了半天,原来是证书文件路径写错了,实际指向了一个旧证书。

正确的配置姿势是这样的:

`nginx

强制HTTPS重定向

server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}

HTTPS服务

server {
listen 443 ssl http2;
server_name example.com www.example.com;

# 证书路径一定要用绝对路径,别用相对路径
ssl_certificate /root/.acme.sh/example.com_ecc/fullchain.cer;
ssl_certificate_key /root/.acme.s

h/example.com_ecc/example.com.key;

# 安全配置,这些参数我测试过,评级是A+
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# 这行很重要,开启HSTS
add_header Strict-Transport-Security "max-age=63072000" always;

location / {
root /var/www/html;
index index.html;
}
}
`

配置完后记得测试一下语法:

`bash
nginx -t

如果没问题就重载

systemctl reload nginx
`

这里展示部署后的SSL测试结果,来自SSL Labs的A+评级截图,以及浏览器地址栏的小锁标志。

第三步:自动化续期——90天证书的命脉

免费证书有效期只有90天,手动续期是不可能的。acme.sh默认会添加cron任务自动续期,但有个坑:续期后证书文件路径变了!

我刚开始用默认配置,续期后nginx指向的还是旧证书,导致服务中断。正确的做法是续期后执行reload命令:

`bash

设置续期后自动重启nginx

acme.sh --install-cert -d example.com \
--reloadcmd "systemctl reload nginx"

查看已添加的cron任务

crontab -l
`

验证自动续期是否正常:

`bash

强制测试续期

acme.sh --renew -d example.com --force

查看续期日志

tail -f ~/.acme.sh/acme.sh.log
`

还有个技巧:可以在续期前检查证书剩余天数,提前预警。写个简单的脚本:

`bash
#!/bin/bash

检查证书剩余天数

expire_date=$(openssl x509 -in /root/.acme.sh/example.com_ecc/fullchain.cer -noout -enddate | cut -d= -f2)
expire_seconds=$(date -d "$expire_date" +%s)
now_seconds=$(date +%s)
days_left=$(( ($expire_seconds - $now_seconds) / 86400 ))

if [ $days_left -lt 30 ]; then
echo "证书剩余 $days_left 天,即将过期"
# 可以发送通知
fi
`

总结前放一张自动化续期的流程图,展示从cron触发到续期、重载nginx的完整链路。

另一个坑:中间证书链

这个坑我印象特别深。配置完SSL后用curl -v https://example.com测试,发现证书链不完整。浏览器访问没问题,但某些老版本Android手机会报错。

解决方案是使用fullchain.cer而不是cert.cer,因为fullchain包含了中间证书。acme.sh默认生成的fullchain.cer就是完整链,所以用那个就行。

如果想验证证书链是否完整:

`bash
openssl s_client -connect example.com:443 -showcerts

看输出里是否包含两个或更多证书,第一个是服务器证书,第二个是中间证书。

总结一下,你可以立刻用的三个点

  • 用acme.sh + DNS API模式申请泛域名证书,一次配置,90天自动续期,零维护成本
  • Nginx配置必须用fullchain.cer和绝对路径,加上HSTS和SSL安全参数,评级直接A+
  • 续期后必须reload nginx,否则证书更新了但服务没生效,这个坑我踩烂了
  • 最后说一句:别在证书上省钱,也别在配置上省心。免费工具用对了,比付费的还香。

    滚动至顶部