问题描述

为了省略每次手动申请SSL证书的麻烦,自己手工编译了nginx,并使用了nginx-acme模块来自动申请和续订证书。但是在实际使用过程中,发现无法成功申请证书。

使用了官方的文档进行配置:

nginx的http模块增加

# 配置 DNS 解析器(用于 ACME 客户端与 CA 服务器通信)
resolver 127.0.0.1:53;

# 定义一个名为 “au92” 的 ACME 颁发者配置
acme_issuer au92 {
    # 替换为实际的 ACME 目录服务地址,例如 Let‘s Encrypt 是 https://acme-v02.api.letsencrypt.org/directory
    uri https://acme-v02.api.letsencrypt.org/directory; # **需要替换:改为你的 ACME CA 地址**
    # 可选:联系邮箱
    contact iam@au92.com; # **需要替换:改为你的邮箱(可选)**
    # 指定状态文件存储路径
    state_path /var/cache/nginx/acme-au92;
    # 接受 CA 的服务条款
    accept_terms_of_service;
}

# 可选:共享内存区域,用于工作进程间同步 ACME 数据
acme_shared_zone zone=ngx_acme_shared:1M;

server模块增加

server {
   listen 80;
   server_name history.au92.com;
   # return 301 https://history.au92.com$request_uri;
   # 一个安全实践:将其他所有 80 端口的非验证请求返回 404 或重定向到 HTTPS
   # ACME 模块会自动处理 /.well-known/acme-challenge/ 的请求,此 location 用于处理所有其他请求
   location / {
      # 将所有非 ACME 验证的 HTTP 流量强制重定向到 HTTPS
      return 301 https://$host$request_uri;
   }
}
server {
   access_log /home/logs/nginx/history.au92.com.log main if=$loggable;

   # HTTP/1.1 + TLS
   listen 443 ssl;

   # QUIC (HTTP/3)
   listen 443 quic reuseport;

   # 推荐的 HTTP/2 现代写法
   http2 on;
   server_name history.au92.com;
   index index.html index.htm;

   # 使用 nginx-acme 模块提供的变量指定证书和密钥路径
   acme_certificate au92;
   ssl_certificate $acme_certificate;
   ssl_certificate_key $acme_certificate_key;
   ssl_certificate_cache max=2;

   ssl_protocols TLSv1.3;
   ssl_prefer_server_ciphers off;
   add_header Alt-Svc 'h3=":443"; ma=86400';
}

但是实际运行后,发现无法成功申请证书。

现状

/var/cache/nginx/acme-au92目录下只有一个account.key文件,没有任何证书文件。

排查过程

经过一圈Google后,找到一篇文章NGINX 原生 ACME 支持:从根本上重塑 TLS 自动化部署,文章中的实例是把resolver指向了resolver 8.8.8.8 1.1.1.1;,于是尝试修改了resolver的配置,但是依然无法成功申请证书。

查看nginx的错误日志发现:[error] 325871#325871: connect() to acme-v02.api.letsencrypt.org failed (101: Network is unreachable)

但是此时ping acme-v02.api.letsencrypt.org是可以通的,说明网络没有问题。

请教了AI(Gemini),它建议我修改DNS为只解析IPV4resolver 8.8.8.8 1.1.1.1 ipv6=off;

Ping 成功: 你运行 ping acme-v02.api.letsencrypt.org 时,输出显示连接的是 172.65.32.248(IPv4 地址)。这说明你的服务器 IPv4 网络是正常的。
Nginx 失败: Nginx 使用你配置的 resolver 8.8.8.8 解析域名。Let's Encrypt 同时支持 IPv4 和 IPv6。如果 8.8.8.8 返回了 IPv6 地址(AAAA 记录),Nginx 会尝试使用 IPv6 连接。
错误 101: Network is unreachable 通常意味着 Nginx 拿到了 IPv6 地址,尝试建立连接,但你的服务器 不支持 IPv6 或者 IPv6 路由配置错误(很多 VPS 默认开启 IPv6 接口但没有配置实际的公网 IPv6 路由)。

修改之后发现依然申请不到证书,查看日志发现没有任何输出,怀疑是日志级别不够,于是把nginx的error_log级别调成了infoerror_log /var/log/nginx/error.log info; (失误的是AI第一次告诉我把这一行加在http模块内),经过几轮往复最后依然是Gemini告诉我需要加载http模块外边。

最终终于拿到了日志:

upstream SSL certificate verify error: (20:"unable to get local issuer certificate")

最终解决方案

apt-get update
apt-get install -y ca-certificates

然后确认ls -lh /etc/ssl/certs/ca-certificates.crt文件存在

修改nginx.service,增加一行:Environment="SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"

vi /etc/systemd/system/nginx.service


[Unit]
Description=nginx - high performance web server
Documentation=https://nginx.org/en/docs/
After=network-online.target remote-fs.target nss-lookup.target
Wants=network-online.target

[Service]
Type=forking
PIDFile=/run/nginx.pid
Environment="CONFFILE=/etc/nginx/nginx.conf"
ExecStart=/usr/share/nginx/sbin/nginx -c ${CONFFILE}
ExecReload=/usr/share/nginx/sbin/nginx -s reload
ExecStop=/usr/share/nginx/sbin/nginx -s quit
Environment="SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt"
[Install]
WantedBy=multi-user.target
systemctl enable nginx

nginx.conf的http模块内增加:

    # 配置 DNS 解析器(用于 ACME 客户端与 CA 服务器通信)
    resolver 8.8.8.8 1.1.1.1 ipv6=off;

    # 定义一个名为 “au92” 的 ACME 颁发者配置
    acme_issuer au92 {
        # 替换为实际的 ACME 目录服务地址,例如 Let‘s Encrypt 是 https://acme-v02.api.letsencrypt.org/directory
        uri https://acme-v02.api.letsencrypt.org/directory; # **需要替换:改为你的 ACME CA 地址**
        # 可选:联系邮箱
        contact iam@au92.com; # **需要替换:改为你的邮箱(可选)**
        # 指定状态文件存储路径
        state_path /var/cache/nginx/acme-au92;
        # 接受 CA 的服务条款
        accept_terms_of_service;
    }

    # 可选:共享内存区域,用于工作进程间同步 ACME 数据
    acme_shared_zone zone=ngx_acme_shared:1M;

server模块内增加(注意80的server模块要一定保留):

# 使用 nginx-acme 模块提供的变量指定证书和密钥路径
acme_certificate au92;
ssl_certificate $acme_certificate;
ssl_certificate_key $acme_certificate_key;
ssl_certificate_cache max=2;

原因:

我是手动编译的 OpenSSL 3.6.0。系统自带的 OpenSSL 知道 Debian 的根证书存放位置(通常在 /etc/ssl/certs),但你手动编译的 OpenSSL 默认会在它自己的安装目录(例如 /usr/local/ssl)里找证书,那里是空的。因此,它认为 Let's Encrypt 的证书是“伪造”的,拒绝连接。