
群晖的DSM默认带了一个Synology自签名证书,使用web访问时浏览器会提示网站是不受信任的网站。作为一个强迫症,这个可不能忍!于是花了点时间研究了一下怎么通过DDNS申请Let's Encrypt的证书。
本次计划有这么几个原则:
- 尽量使用虚拟化方案,方便后续迁移(虽然后续更换NAS的可能性微乎其微)
- 对整个系统的侵入性尽量的低,能少做配置类工作就少做
幸好目前DSM已经支持了Docker虚拟化,可以基本满足上面两个需求。既然需求和目标已经明确了,接下来开工!
acme.sh申请证书
首先介绍一下笔者环境的基本信息:笔者用duckdns动态域名将DSM映射到了公网以方便访问,所以笔者倾向于给duckdns的域名申请Let's Encrypt的证书。同时笔者比较喜欢用acme.sh,而该程序内置了对duckdns的支持。
问题是,虽然有公网ip,但是由于国内运营商封禁了80和443端口,因此并不能使用http的方式申请ddns域名的证书,经过调研,发现duckdns可以设置txt记录,因此决定使用dns的txt记录方式申请证书。
登录DSM,使用如下命令创建acme.sh镜像:
- docker run -itd -v <保存证书的绝对路径>:/acme.sh --env DuckDNS_Token="xxxxxxxx-oooo-xxxx-zzzz-aaaaaaaaaaa" --net=host --name=acme.sh --restart=always neilpang/acme.sh daemon
neilpang/acme.sh为网上其他人做的镜像,经过调研完全满足本次的需求。注意将上述命令 <保存证书的绝对路径> 部分替换为你自己存储证书的路径, 同时如果你用的也是duckdns,需要设置一个环境变量DuckDNS_Token,acme.sh对其他ddns的支持请参考文末的参考1。
如果上述容器创建成功,便可以在web页面看到刚才创建的镜像:

然后执行如下命令正式开始申请证书,其中xxxx.duckdns.org为笔者的ddns域名:
- docker exec acme.sh --set-default-ca --server letsencrypt
- docker exec acme.sh --issue --dns dns_duckdns -d <你的域名> --insecure --debug
网络没有问题的情况下,会在 <保存证书的绝对路径> 位置,以<你的域名> 为文件夹名,存放申请到的证书:

acme.sh更新证书
由于Let's Encrypt申请的证书有效期为3个月,因此需要考虑定期更新证书的问题。
好消息是这个问题neilpang/acme.sh容器已经为我们考虑到了,只要你是按照上文的daemon模式启动的这个容器,容器会自动创建crontab任务,每天检查证书是否需要更新:

因此我们只需要保证acme.sh容器一直运行,即可自动更新申请到的证书。
让DSM帮我们初始化证书
OK,现在证书有了,接下来需要配置DSM使用申请的证书。这里笔者偷个懒,先让DSM帮我们做一遍初始化,登录DSM依次选择“控制面板”、“安全性”、“证书”,选择“添加新证书”并点“下一步”:

第二步选择导入证书,名字随意起,建议使用你的域名, 这一步注意一定要勾选“设为默认证书”,因为后文中我们的更新脚本依赖默认证书这一特性:

第三步选择上面使用acme.sh申请的证书:

点击确定之后,就可以在DSM的证书管理中看到刚才申请的证书了:

此时选中刚才导入的证书, 点击上方的设置按钮, 将所有系统的证书设置为刚才导入的证书:

点击确定后会提示你要重启Web服务,重启完成之后,退出到登录页面,发现使用ddns域名访问DSM就会被标记为安全链接了:

配置证书自动更新
通过研究发现,DSM将所有证书信息存放在了/usr/syno/etc/certificate/_archive目录下,且/usr/syno/etc/certificate/_archive/DEFAULT文件中记录了当前的默认证书(此处关联上文中提到的勾选默认证书动作)。
因此,思路是先从DEFAULT文件中读出当前证书的文件夹名, 然后把默认证书替换掉系统服务下的证书文件,最后重启系统服务即可。形成的更新脚本如下(假设你将如下内容保存为update-certificate.sh文件),使用方法:
./update-certificate.sh “<保存证书的绝对路径>/<你的域名>”
脚本内容如下:
- #!/bin/sh
- ACME_CERTIFICATE_PATH=${@:1:1}
- ACME_DOMAIN_NAME=$(ls ${ACME_CERTIFICATE_PATH}/*.csr | tr -d "\n")
- ACME_DOMAIN_NAME=$(basename ${ACME_DOMAIN_NAME})
- ACME_DOMAIN_NAME=${ACME_DOMAIN_NAME//.csr/}
- SYNOLOGY_CERTIFICATE_PATH="/usr/syno/etc/certificate"
- SYNOLOGY_DEFAULT_CERTIFICATE_NAME=$(cat "${SYNOLOGY_CERTIFICATE_PATH}/_archive/DEFAULT" | tr -d "\n")
- SYNOLOGY_DEFAULT_CERTIFICATE_PATH="${SYNOLOGY_CERTIFICATE_PATH}/_archive/${SYNOLOGY_DEFAULT_CERTIFICATE_NAME}"
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/fullchain.cer' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/fullchain.pem"
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.cer' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/cert.pem"
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.key' ${SYNOLOGY_DEFAULT_CERTIFICATE_PATH}/privkey.pem"
- for each_file in `find ${SYNOLOGY_CERTIFICATE_PATH} -name info -not -path "${SYNOLOGY_CERTIFICATE_PATH}/_archive/*"`;do
- each_directory=$(dirname ${each_file})
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/fullchain.cer' ${each_directory}/fullchain.pem"
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.cer' ${each_directory}/cert.pem"
- /bin/sh -c "cp '${ACME_CERTIFICATE_PATH}/${ACME_DOMAIN_NAME}.key' ${each_directory}/privkey.pem"
- done
最后,在DSM的“控制面板”、“任务计划”中新建一个任务, 每个页面的设置如下,这样每个月的1号会将容器中申请(更新)的证书拷贝到DSM系统的证书目录下:
![]() | ![]() | ![]() |
注意: 由于笔者是个NAS关机党,NAS每天晚上12点会关机,第二天早上9点会启动,因此上面的脚本中没有加入重启服务的功能,你们可以根据自己的需求加入需要重启的服务!