在 Debian 12 上确定 Certbot 是否会自动续期

在 Debian 12 系统上,Certbot 通常会自动配置证书续期。您可以通过以下方法确认:

1. 检查 systemd 计时器

systemctl list-timers | grep certbot

正常情况下,您应该能看到 certbot.timer 被启用并计划在特定时间运行。

2. 检查 cron 配置

如果使用 cron 而不是 systemd:

cat /etc/cron.d/certbot

或查看当前用户的 crontab:

crontab -l | grep certbot

3. 执行模拟续期测试

sudo certbot renew --dry-run

这会模拟证书续期过程,确认一切配置正常。

4. 检查 Certbot 配置

cat /etc/letsencrypt/renewal/你的域名.conf

确认配置中有 renew_before_expiry 和 autorenew 相关设置。

5. 查看日志记录

sudo journalctl -u certbot

或

sudo cat /var/log/letsencrypt/letsencrypt.log

6. 查看证书信息

sudo certbot certificates

这将显示所有证书及其过期日期信息。

在 Debian 12 上,Certbot 默认会设置自动续期,通常通过 systemd timer 每天尝试续期(仅对即将到期的证书生效,通常是到期前 30 天)。

相关阅读:使用certbot自动获取SSL证书

使用certbot自动获取SSL证书

Certbot 是一个由 EFF(Electronic Frontier Foundation) 推出的开源工具,用于自动从 Let’s Encrypt 获取免费 SSL/TLS 证书,并自动完成证书安装与续期。它极大地简化了为网站启用 HTTPS 的流程。

本文介绍在debian12上使用certbot。

sudo apt update
sudo apt install certbot

sudo docker-compose down  # 停止容器以释放80端口

sudo certbot certonly --standalone -d notes.wujie.me

# 获取证书后,修改 docker-compose.yml 文件挂载新证书

修改 docker-compose.yml 配置

sudo vi docker-compose.yml

将证书挂载部分修改为:

volumes:
  - ../:/var/www/html
  - ./apache/site_host.conf:/etc/apache2/sites-available/000-default.conf
  # 使用 Let's Encrypt 证书
  - /etc/letsencrypt/live/notes.wujie.me/fullchain.pem:/etc/apache2/ssl/server.crt
  - /etc/letsencrypt/live/notes.wujie.me/privkey.pem:/etc/apache2/ssl/server.key

 确保证书可被访问

Let’s Encrypt 证书默认权限较严格,需要确保容器可以读取:

# 添加读取权限
sudo chmod -R 755 /etc/letsencrypt/live
sudo chmod -R 755 /etc/letsencrypt/archive

 更新 Apache 配置

您的 Apache 配置文件site_host.conf 已经正确配置了,不需要修改。它已经引用了:

SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key

这正是我们在 docker-compose.yml 中挂载 Let’s Encrypt 证书的位置。

重启 Docker 容器

# 停止当前容器
sudo docker-compose down
# 重新启动容器
sudo docker-compose up -d

自动续期

如果在安装完certbot时,看到Certbot has set up a scheduled task to automatically renew this certificate in the background.,就表示自动续期已经自动设置了。

但您还需要确保证书更新后重启 Docker 容器。创建一个续期钩子脚本:

sudo mkdir -p /etc/letsencrypt/renewal-hooks/post
sudo vi /etc/letsencrypt/renewal-hooks/post/restart-docker.sh

添加以下内容:

#!/bin/bash
cd /your-docker-file/path
docker-compose restart web

设置执行权限:

sudo chmod +x /etc/letsencrypt/renewal-hooks/post/restart-docker.sh

最后重启容器:

sudo docker-compose restart

相关阅读:在 Debian 12 上确定 Certbot 是否会自动续期

nginx https配置模板

server {
    listen 443 ssl http2 default_server;
    server_name  notes.wujie.me;
    root /var/www/notes.wujie.me;
    index  index.html index.htm index.php;

    #文件上传大小限制  必须要放在server下的server_name下
    client_max_body_size 200m;

    # 因为是默认的 https 站点,所以有可能是从 IP 进来的请求,那么把它跳转到域名
    if ($host != 'notes.wujie.me) {
        rewrite ^/(.*)$ https://notes.wujie.me/$1 permanent;
        break;
    }

    ssl_certificate /etc/nginx/ssl/notes.wujie.me.pem;
    ssl_certificate_key /etc/nginx/ssl/notes.wujie.me.key;

    #加上TLSv1,HTTPS检测会报PCI DSS不合规
    ssl_protocols  TLSv1.2 TLSv1.3;# Requires nginx >= 1.13.0 else use TLSv1.2
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
    ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off; # Requires nginx >= 1.5.9
    ssl_stapling on; # Requires nginx >= 1.3.7
    ssl_stapling_verify on; # Requires nginx => 1.3.7

    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    # pass PHP scripts to FastCGI server
    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

acme.sh 用法

安装 acme.sh

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

设置别名,方便使用:

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

申请证书:

acme.sh --issue -d notes.wujie.me  --nginx

或

acme.sh --issue -d notes.wujie.me  --webroot /var/www/notes.wujie.me

部署证书:

acme.sh --install-cert -d notes.wujie.me --key-file /etc/nginx/ssl/notes.wujie.me.key --fullchain-file /etc/nginx/ssl/notes.wujie.me.pem --reloadcmd "service nginx force-reload"

删除证书:

acme.sh --remove -d notes.wujie.me

删除后记得到 /root/.acme.sh/ 目录下找到域名文件夹,一并删除。

官方文档:https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E

Debian+Nginx+PHP HTTPS配置模板

Nginx+PHP+HTTPS的配置模板,如果要部署其它网站,可以基于此进行修改:

# 处理 HTTP 请求,所有请求都重定向到 https://notes.wujie.me
server {
    listen 80;
    server_name notes.wujie.me notes.wujie.me;  # 同时匹配带 www 和不带 www 的域名

    # 所有请求都重定向到 https://notes.wujie.me
    return 301 https://notes.wujie.me$request_uri;
}

# 处理不带 www 的 HTTPS 请求,重定向到带 www 的域名
server {
    listen 443 ssl http2;
    server_name notes.wujie.me;  # 不带 www 的域名

    ssl_certificate /etc/nginx/ssl/notes.wujie.me.pem;  # 证书路径
    ssl_certificate_key /etc/nginx/ssl/notes.wujie.me.key;  # 证书路径

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
    ssl_ecdh_curve secp384r1;
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;
    ssl_stapling on;
    ssl_stapling_verify on;

    # 重定向所有不带 www 的 HTTPS 请求到 notes.wujie.me
    return 301 https://notes.wujie.me$request_uri;
}

# 处理带 www 的 HTTPS 请求
server {
    listen 443 ssl http2;
    root /var/www/notes.wujie.me; # 确保这里是正确的根目录路径
    index index.html index.htm index.php index.nginx-debian.html;

    server_name  notes.wujie.me;
    client_max_body_size 20m;

    ssl_certificate /etc/nginx/ssl/notes.wujie.me.pem; # 确保证书路径正确
    ssl_certificate_key /etc/nginx/ssl/notes.wujie.me.key; # 确保证书路径正确

    #加上TLSv1,HTTPS检测会报PCI DSS不合规
    ssl_protocols  TLSv1.2 TLSv1.3;# Requires nginx >= 1.13.0 else use TLSv1.2
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
    ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0
    ssl_session_timeout 10m;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off; # Requires nginx >= 1.5.9
    ssl_stapling on; # Requires nginx >= 1.3.7
    ssl_stapling_verify on; # Requires nginx => 1.3.7

    location / {
        try_files $uri $uri/ /index.php?$args;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }
}

ACME.sh自动证书管理

https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E

按文档做就行。

acme.sh 实现了 acme 协议, 可以从 letsencrypt 生成免费的证书.

主要步骤:

  1. 安装 acme.sh
  2. 生成证书
  3. copy 证书到 nginx/apache 或者其他服务
  4. 更新证书
  5. 更新 acme.sh
  6. 出错怎么办, 如何调试

下面详细介绍.

1. 安装 acme.sh

安装很简单, 一个命令:

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

普通用户和 root 用户都可以安装使用. 安装过程进行了以下几步:

  1. 把 acme.sh 安装到你的 home 目录下:

~/.acme.sh/

并创建 一个 shell 的 alias, 例如 .bashrc,方便你的使用: alias acme.sh=~/.acme.sh/acme.sh

  1. 自动为你创建 cronjob, 每天 0:00 点自动检测所有的证书, 如果快过期了, 需要更新, 则会自动更新证书.

更高级的安装选项请参考:  https://github.com/Neilpang/acme.sh/wiki/How-to-install

安装过程不会污染已有的系统任何功能和文件, 所有的修改都限制在安装目录中: ~/.acme.sh/

2. 生成证书

acme.sh 实现了 acme 协议支持的所有验证协议. 一般有两种方式验证: http 和 dns 验证.

1. http 方式需要在你的网站根目录下放置一个文件, 来验证你的域名所有权,完成验证. 然后就可以生成证书了.

acme.sh --issue -d mydomain.com -d www.mydomain.com --webroot /home/wwwroot/mydomain.com/

只需要指定域名, 并指定域名所在的网站根目录. acme.sh 会全自动的生成验证文件, 并放到网站的根目录, 然后自动完成验证. 最后会聪明的删除验证文件. 整个过程没有任何副作用.

如果你用的 apache服务器, acme.sh 还可以智能的从 apache的配置中自动完成验证, 你不需要指定网站根目录:

acme.sh --issue -d mydomain.com --apache

如果你用的 nginx服务器, 或者反代, acme.sh 还可以智能的从 nginx的配置中自动完成验证, 你不需要指定网站根目录:

acme.sh --issue -d mydomain.com --nginx

注意, 无论是 apache 还是 nginx 模式, acme.sh在完成验证之后, 会恢复到之前的状态, 都不会私自更改你本身的配置. 好处是你不用担心配置被搞坏, 也有一个缺点, 你需要自己配置 ssl 的配置, 否则只能成功生成证书, 你的网站还是无法访问https. 但是为了安全, 你还是自己手动改配置吧.

如果你还没有运行任何 web 服务, 80 端口是空闲的, 那么 acme.sh 还能假装自己是一个webserver, 临时听在80 端口, 完成验证:

acme.sh --issue -d mydomain.com --standalone

更高级的用法请参考:  https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert

2. 手动 dns 方式, 手动在域名上添加一条 txt 解析记录, 验证域名所有权.

这种方式的好处是, 你不需要任何服务器, 不需要任何公网 ip, 只需要 dns 的解析记录即可完成验证. 坏处是,如果不同时配置 Automatic DNS API,使用这种方式 acme.sh 将无法自动更新证书,每次都需要手动再次重新解析验证域名所有权。

acme.sh --issue --dns -d mydomain.com \
 --yes-I-know-dns-manual-mode-enough-go-ahead-please

然后, acme.sh 会生成相应的解析记录显示出来, 你只需要在你的域名管理面板中添加这条 txt 记录即可.

等待解析完成之后, 重新生成证书:

acme.sh --renew -d mydomain.com \
  --yes-I-know-dns-manual-mode-enough-go-ahead-please

注意第二次这里用的是 --renew

dns 方式的真正强大之处在于可以使用域名解析商提供的 api 自动添加 txt 记录完成验证.

acme.sh 目前支持 cloudflare, dnspod, cloudxns, godaddy 以及 ovh 等数十种解析商的自动集成.

以 dnspod 为例, 你需要先登录到 dnspod 账号, 生成你的 api id 和 api key, 都是免费的. 然后:

export DP_Id="1234"
export DP_Key="sADDsdasdgdsf"
acme.sh --issue --dns dns_dp -d aa.com -d www.aa.com

证书就会自动生成了. 这里给出的 api id 和 api key 会被自动记录下来, 将来你在使用 dnspod api 的时候, 就不需要再次指定了. 直接生成就好了:

acme.sh --issue -d mydomain2.com --dns  dns_dp

更详细的 api 用法:  https://github.com/Neilpang/acme.sh/blob/master/dnsapi/README.md

3. copy/安装 证书

前面证书生成以后, 接下来需要把证书 copy 到真正需要用它的地方.

注意, 默认生成的证书都放在安装目录下: ~/.acme.sh/, 请不要直接使用此目录下的文件, 例如: 不要直接让 nginx/apache 的配置文件使用这下面的文件. 这里面的文件都是内部使用, 而且目录结构可能会变化.

正确的使用方法是使用--install-cert 命令,并指定目标位置, 然后证书文件会被copy到相应的位置, 例如:

Apache example:

acme.sh --install-cert -d example.com \
--cert-file      /path/to/certfile/in/apache/cert.pem  \
--key-file       /path/to/keyfile/in/apache/key.pem  \
--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \
--reloadcmd     "service apache2 force-reload"

Nginx example:

acme.sh --install-cert -d example.com \
--key-file       /path/to/keyfile/in/nginx/key.pem  \
--fullchain-file /path/to/fullchain/nginx/cert.pem \
--reloadcmd     "service nginx force-reload"

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

Nginx 的配置 ssl_certificate 使用 /etc/nginx/ssl/fullchain.cer ,而非 /etc/nginx/ssl/<domain>.cer ,否则 SSL Labs 的测试会报 Chain issues Incomplete 错误。

--install-cert命令可以携带很多参数, 来指定目标文件. 并且可以指定 reloadcmd, 当证书更新以后, reloadcmd会被自动调用,让服务器生效.

详细参数请参考:  https://github.com/Neilpang/acme.sh#3-install-the-issued-cert-to-apachenginx-etc

值得注意的是, 这里指定的所有参数都会被自动记录下来, 并在将来证书自动更新以后, 被再次自动调用.

4. 查看已安装证书信息

acme.sh --info -d example.com

会输出如下内容:

DOMAIN_CONF=/root/.acme.sh/example.com/example.com.conf
Le_Domain=example.com
Le_Alt=no
Le_Webroot=dns_ali
Le_PreHook=
Le_PostHook=
Le_RenewHook=
Le_API=https://acme-v02.api.letsencrypt.org/directory
Le_Keylength=
Le_OrderFinalize=https://acme-v02.api.letsencrypt.org/acme/finalize/23xxxx150/781xxxx4310
Le_LinkOrder=https://acme-v02.api.letsencrypt.org/acme/order/233xxx150/781xxxx4310
Le_LinkCert=https://acme-v02.api.letsencrypt.org/acme/cert/04cbd28xxxxxx349ecaea8d07
Le_CertCreateTime=1649358725
Le_CertCreateTimeStr=Thu Apr  7 19:12:05 UTC 2022
Le_NextRenewTimeStr=Mon Jun  6 19:12:05 UTC 2022
Le_NextRenewTime=1654456325
Le_RealCertPath=
Le_RealCACertPath=
Le_RealKeyPath=/etc/acme/example.com/privkey.pem
Le_ReloadCmd=service nginx force-reload
Le_RealFullChainPath=/etc/acme/example.com/chain.pem

5. 更新证书

目前证书在 60 天以后会自动更新, 你无需任何操作. 今后有可能会缩短这个时间, 不过都是自动的, 你不用关心.

请确保 cronjob 正确安装, 看起来是类似这样的:

crontab  -l
56 * * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

6. 关于修改ReloadCmd

目前修改ReloadCmd没有专门的命令,可以通过重新安装证书来实现修改reloadCmd的目的。 此外,安装证书后,相关信息是保存在~/.acme.sh/example.com/example.conf文件下的,内容就是acme.sh --info -d example.com输出的信息,不过ReloadCmd在文件中使用了Base64编码。理论上可以通过直接修改该文件来修改ReloadCmd,且修改时,无需Base64编码,直接写命令原文acme.sh也可以识别。 不过,example.conf文件的位置和内容格式以后可能会改变!example.conf一直都是内部使用, 后面有可能会改为用 sqlite 或者mysql 格式存储. 所以一般不建议自己修改。

7. 更新 acme.sh

目前由于 acme 协议和 letsencrypt CA 都在频繁的更新, 因此 acme.sh 也经常更新以保持同步.

升级 acme.sh 到最新版 :

acme.sh --upgrade

如果你不想手动升级, 可以开启自动升级:

acme.sh --upgrade --auto-upgrade

之后, acme.sh 就会自动保持更新了.

你也可以随时关闭自动更新:

acme.sh --upgrade --auto-upgrade  0

8. 出错怎么办:

如果出错, 请添加 debug log:

acme.sh --issue  .....  --debug

或者:

acme.sh --issue  .....  --debug  2

请参考:  https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh

在DNS验证模式下如果debug中出现诸如”timed out”等字样可能是因为GFW拦截了相应请求,需要添加http(s) proxy环境变量。(请按照自己实际设定修改)

export http_proxy="socks5h://localhost:1081" && export https_proxy="socks5h://localhost:1081"

如果是使用docker则完整示例配置如下:

docker run --rm  -it  \
  -v "/etc/acme":/acme.sh  \
  -e "CF_Token=[填入自己的信息]" \
  -e "CF_Account_ID=[填入自己的信息]" \
  -e "CF_Zone_ID=[填入自己的信息]" \
  -e http_proxy="socks5h://[代理A]:1234" \
  -e https_proxy="socks5h://[代理A]:1234" \
  --network container:[代理A]\
  neilpang/acme.sh \
  --issue -d example.com --dns dns_cf --debug

上述例子中使用cloudflare的DNS来签发证书,并通过把acme.sh链接到容器[代理A],来转发curl请求(请按照自己实际设定修改)

最后, 本文并非完全的使用说明, 还有很多高级的功能, 更高级的用法请参看其他 wiki 页面.

https://github.com/Neilpang/acme.sh/wiki

解决 Nginx SSL 证书报错

闲来无事,查看了一下 nginx 的 error.log 日志文件,发现里面大量的报错信息:cannot load certificate “data:”: PEM_read_bio_X509_AUX() failed (SSL: error:0909006C:PEM routines:get_name:no start line:Expecting: TRUSTED CERTIFICATE) while SSL handshaking…

是 ssl 证书报错了,想了一下,整个服务器只有这个博客使用了 https ,理所当然的从博客的 nginx 配置文件和证书文件入手,历时三天也没找到原因,甚至一度把证书都更换了,从腾讯云的 SSL 证书换到了阿里云的 SSL 证书,结果还是报错,说明证书没问题。

就在快要放弃的时候,突然想到,之前为了屏蔽直接从 ip 来的请求,加了一些配置,屏蔽了http://ip和https://ip两种访问形式,而屏蔽 https+ip 也涉及到了 SSL 证书的问题,当时是通过 map 映射给了一个空证书,会不会是这个原因?

于是,为了验证这个猜想,把博客站点的错误日志和其它站点的分开,单独统计。过了一夜,第二天打开日志文件查看,果然,博客的错误日志中并没有此错误,只有 error.log 里面有,这就说明错误不是博客站点的 SSL 证书引起的,那么只可能是之前屏蔽 https+ip 的配置引起的。

找到了原因,那么解决起来就快了。

先帖一下之前的配置:

map "" $empty {
    default "";
}
server {
    listen 80 default_server;
    listen 443 ssl http2 default_server;
    listen [::]:80 default_server;
    listen [::]:443 ssl http2 default_server;
    server_name _;

    ssl_ciphers aNULL;
    ssl_certificate data:$empty;
    ssl_certificate_key data:$empty;
    return 444;
}

把上面的配置改成如下:

# 禁止直接通过IP访问网站
server {
    listen 80 default_server;
    server_name _;
    return 444;
}

删除了屏蔽 https+ip 的配置,只保留屏蔽 http+ip 的配置。然后在博客站点的 nginx 配置文件中添加如下代码:

# 通过 default_server 把博客站点设置为默认的 https 站点
listen 443 ssl http2 default_server;

if ($host != 'wujie.me') {
    rewrite ^/(.*)$ https://wujie.me/$1 permanent;
    break;
}

把直接从非域名来的请求(包括从 ip 来的请求)跳转到域名就可以了。

这样修改后,经过两天的观察,没有再出现错误,问题解决!