znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第07章 - 证书部署与安装

证书申请成功后,需要将证书部署到实际的服务或系统中。acme.sh 提供了 --install-cert 命令和丰富的 deploy hooks(部署钩子),支持几乎所有主流的服务器软件和平台。


7.1 --install-cert 命令详解

7.1.1 为什么不能直接使用 ~/.acme.sh 中的文件

~/.acme.sh/<domain>/ 目录下的证书文件是 acme.sh 的内部存储,不应在 Nginx/Apache 等服务中直接引用:

  1. 每次续期后内部文件会被覆盖,但服务的 reload 命令需要在正确时机执行
  2. 内部路径格式可能随版本变化
  3. 无法跨机器同步(如需要将证书复制到多台服务器)

正确做法:使用 --install-cert 将证书复制到指定路径,并配置续期后自动执行的 reload 命令。

7.1.2 命令语法

acme.sh --install-cert -d <domain> \
  [--cert-file <path>]        # 域名证书(不含中间证书),较少使用
  [--key-file <path>]         # 私钥文件(必须保密)
  [--fullchain-file <path>]   # 完整证书链(推荐使用这个配置 HTTPS)
  [--ca-file <path>]          # CA 中间证书
  [--reloadcmd <command>]     # 证书安装/续期后执行的重载命令
  • --reloadcmd 支持复杂命令和脚本(用引号括起来)
  • 所有参数都会保存到 ~/.acme.sh/<domain>/<domain>.conf,后续自动续期时会自动执行

7.1.3 ECC 证书安装

申请 ECC 证书时,安装时需要加 --ecc 标志:

# 申请 ECC 证书
acme.sh --issue --dns dns_cf -d example.com -d *.example.com --keylength ec-256

# 安装 ECC 证书(注意加 --ecc)
acme.sh --install-cert -d example.com --ecc \
  --key-file       /etc/nginx/ssl/privkey.pem \
  --fullchain-file /etc/nginx/ssl/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

7.2 部署到 Nginx

7.2.1 基础配置

# 创建证书目录(建议按域名分目录管理)
mkdir -p /etc/nginx/ssl/example.com

# 安装证书
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com/privkey.pem \
  --fullchain-file /etc/nginx/ssl/example.com/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

7.2.2 Nginx HTTPS 最佳实践配置

# /etc/nginx/conf.d/example.com.conf

# HTTP → HTTPS 重定向
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

# HTTPS 主配置
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com www.example.com;

    # 证书配置
    ssl_certificate     /etc/nginx/ssl/example.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/privkey.pem;

    # SSL 安全配置
    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:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;

    # SSL 会话缓存
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # 安全头部
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    # 站点根目录
    root /var/www/html;
    index index.html index.php;

    location / {
        try_files $uri $uri/ =404;
    }
}

7.2.3 同域名 RSA + ECC 双证书(Nginx 1.11+)

从 Nginx 1.11.0 起,支持同一域名配置两套证书(RSA 和 ECC),Nginx 会根据客户端能力自动选择:

# 申请 RSA 证书
acme.sh --issue --dns dns_cf -d example.com -d *.example.com

# 申请 ECC 证书
acme.sh --issue --dns dns_cf -d example.com -d *.example.com --keylength ec-256

# 安装 RSA 证书
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/example.com/rsa/privkey.pem \
  --fullchain-file /etc/nginx/ssl/example.com/rsa/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

# 安装 ECC 证书
acme.sh --install-cert -d example.com --ecc \
  --key-file       /etc/nginx/ssl/example.com/ecc/privkey.pem \
  --fullchain-file /etc/nginx/ssl/example.com/ecc/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

Nginx 双证书配置:

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

    # RSA 证书
    ssl_certificate     /etc/nginx/ssl/example.com/rsa/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/rsa/privkey.pem;

    # ECC 证书(同时配置,Nginx 会自动选择)
    ssl_certificate     /etc/nginx/ssl/example.com/ecc/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/example.com/ecc/privkey.pem;
}

7.3 部署到 Apache

# 创建证书目录
mkdir -p /etc/apache2/ssl/example.com

# 安装证书
acme.sh --install-cert -d example.com \
  --cert-file      /etc/apache2/ssl/example.com/cert.pem \
  --key-file       /etc/apache2/ssl/example.com/privkey.pem \
  --fullchain-file /etc/apache2/ssl/example.com/fullchain.pem \
  --ca-file        /etc/apache2/ssl/example.com/ca.pem \
  --reloadcmd      "systemctl reload apache2"

Apache HTTPS 虚拟主机配置(/etc/apache2/sites-available/example.com-ssl.conf):

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/html

    SSLEngine on
    SSLCertificateFile      /etc/apache2/ssl/example.com/cert.pem
    SSLCertificateKeyFile   /etc/apache2/ssl/example.com/privkey.pem
    SSLCertificateChainFile /etc/apache2/ssl/example.com/fullchain.pem

    # 推荐 SSL 配置
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder off
    SSLSessionTickets off

    Header always set Strict-Transport-Security "max-age=63072000"
</VirtualHost>

7.4 部署到 HAProxy

HAProxy 需要将证书和私钥合并到单个 PEM 文件中。

# 使用 HAProxy deploy hook
export DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy/certs
export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy"

# 先申请证书
acme.sh --issue --dns dns_cf -d example.com -d *.example.com

# 部署到 HAProxy
acme.sh --deploy -d example.com --deploy-hook haproxy

HAProxy 配置:

frontend https_frontend
    bind *:443 ssl crt /etc/haproxy/certs/
    mode http
    default_backend web_servers

7.5 通过 SSH 部署到远程服务器

acme.sh 的 SSH deploy hook 可以将证书自动推送到远程服务器,非常适合多服务器场景。

7.5.1 基础 SSH 部署

# 配置 SSH 部署参数
export DEPLOY_SSH_USER="root"
export DEPLOY_SSH_SERVER="remote-server.example.com"
export DEPLOY_SSH_PORT="22"
# 远程服务器证书存放路径
export DEPLOY_SSH_KEYFILE="/etc/nginx/ssl/example.com/privkey.pem"
export DEPLOY_SSH_FULLCHAIN="/etc/nginx/ssl/example.com/fullchain.pem"
# 证书推送后在远程服务器执行的命令
export DEPLOY_SSH_REMOTE_CMD="systemctl reload nginx"

# 执行部署
acme.sh --deploy -d example.com --deploy-hook ssh

7.5.2 使用特定 SSH 私钥

export DEPLOY_SSH_USER="deploy"
export DEPLOY_SSH_SERVER="192.168.1.100"
# 指定本地 SSH 私钥(默认使用 ~/.ssh/id_rsa)
export DEPLOY_SSH_IDENTITY="/root/.ssh/deploy_key"
export DEPLOY_SSH_KEYFILE="/etc/ssl/privkey.pem"
export DEPLOY_SSH_FULLCHAIN="/etc/ssl/fullchain.pem"
export DEPLOY_SSH_REMOTE_CMD="systemctl reload nginx"

acme.sh --deploy -d example.com --deploy-hook ssh

7.5.3 部署到多台服务器

通过 --reloadcmd 触发自定义脚本,在脚本中依次部署到多台服务器:

cat > /opt/deploy_cert.sh << 'SCRIPT'
#!/bin/bash
DOMAIN="example.com"
FULLCHAIN="/etc/ssl/example.com/fullchain.pem"
PRIVKEY="/etc/ssl/example.com/privkey.pem"

# 服务器列表
SERVERS=("server1.example.com" "server2.example.com" "server3.example.com")

for SERVER in "${SERVERS[@]}"; do
    echo "Deploying to $SERVER..."
    scp -i /root/.ssh/deploy_key $FULLCHAIN $PRIVKEY "root@$SERVER:/etc/nginx/ssl/"
    ssh -i /root/.ssh/deploy_key "root@$SERVER" "systemctl reload nginx"
    echo "Done: $SERVER"
done
SCRIPT

chmod +x /opt/deploy_cert.sh

# 安装证书并配置多服务器部署
acme.sh --install-cert -d example.com \
  --fullchain-file /etc/ssl/example.com/fullchain.pem \
  --key-file /etc/ssl/example.com/privkey.pem \
  --reloadcmd "/opt/deploy_cert.sh"

7.6 部署到 Synology NAS

Synology NAS(群晖)是国内外广泛使用的 NAS 设备,acme.sh 有专门的 deploy hook。

7.6.1 自动部署(推荐)

# 新版 DSM 7.x(推荐使用临时管理员)
export SYNO_USE_TEMP_ADMIN=1

acme.sh --deploy -d nas.example.com --deploy-hook synology_dsm

# 旧版或手动指定账户
export SYNO_USERNAME="admin"
export SYNO_PASSWORD="your_password"
export SYNO_HOSTNAME="192.168.1.100"
export SYNO_PORT="5001"
export SYNO_SCHEME="https"

acme.sh --deploy -d nas.example.com --deploy-hook synology_dsm

7.7 部署到 Proxmox VE

Proxmox VE 是流行的开源虚拟化平台:

# 在 Proxmox 节点上执行
acme.sh --issue --dns dns_cf -d pve.example.com

# 部署到 Proxmox(使用内置 deploy hook)
export DEPLOY_PROXMOXVE_API_TOKEN_KEY="your_api_token"
export DEPLOY_PROXMOXVE_API_TOKEN_NAME="your_token_name"
export DEPLOY_PROXMOXVE_SERVER_FQDN="pve.example.com"

acme.sh --deploy -d pve.example.com --deploy-hook proxmoxve

# 或者手动安装(不使用 deploy hook)
acme.sh --install-cert -d pve.example.com \
  --certpath /etc/pve/local/pveproxy-ssl.pem \
  --keypath  /etc/pve/local/pveproxy-ssl.key \
  --capath   /etc/pve/local/pveproxy-ssl.pem \
  --reloadcmd "systemctl restart pveproxy"

7.8 部署到 Docker 容器

7.8.1 宿主机申请,挂载到容器

最简单的方式:在宿主机申请证书,通过 Docker Volume 挂载到容器:

# 在宿主机申请并安装证书
acme.sh --install-cert -d example.com \
  --key-file       /docker/nginx/ssl/privkey.pem \
  --fullchain-file /docker/nginx/ssl/fullchain.pem \
  --reloadcmd      "docker exec nginx nginx -s reload"

Docker Compose 配置:

version: '3'
services:
  nginx:
    image: nginx:latest
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /docker/nginx/ssl:/etc/nginx/ssl:ro
      - /docker/nginx/conf.d:/etc/nginx/conf.d:ro

7.8.2 acme.sh 官方 Docker 镜像

# 使用官方 Docker 镜像
docker run --rm \
  -v /docker/acme.sh:/acme.sh \
  -e CF_Token="your_cf_token" \
  -e CF_Account_ID="your_account_id" \
  neilpang/acme.sh --issue \
    --dns dns_cf \
    -d example.com -d *.example.com

# 安装证书
docker run --rm \
  -v /docker/acme.sh:/acme.sh \
  -v /docker/nginx/ssl:/ssl \
  neilpang/acme.sh --install-cert \
    -d example.com \
    --key-file /ssl/privkey.pem \
    --fullchain-file /ssl/fullchain.pem

7.9 部署到 OpenWrt 路由器

OpenWrt 是常见的嵌入式 Linux 路由器系统,可以在路由器上运行 acme.sh 申请证书(用于路由器管理界面的 HTTPS):

# OpenWrt 安装(使用 opkg 或手动)
opkg update
opkg install curl

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

# 申请证书(使用 DNS API,因为路由器 80 端口通常不对外开放)
export CF_Token="your_cf_token"
export CF_Account_ID="your_account_id"
acme.sh --issue --dns dns_cf -d router.example.com

# 安装证书到 OpenWrt uhttpd
acme.sh --install-cert -d router.example.com \
  --key-file       /etc/uhttpd.key \
  --fullchain-file /etc/uhttpd.crt \
  --reloadcmd      "/etc/init.d/uhttpd restart"

7.10 部署到 HashiCorp Vault

对于需要集中管理证书的企业环境,可以将证书存储到 HashiCorp Vault:

# 配置 Vault 连接
export VAULT_PREFIX="secret/certs"
export VAULT_ADDR="https://vault.example.com:8200"
export VAULT_TOKEN="your_vault_token"

# 部署到 Vault
acme.sh --deploy -d example.com --deploy-hook vault

# 或使用 vault_cli 方式
acme.sh --deploy -d example.com --deploy-hook vault_cli

7.11 部署到 GitLab Pages

export GITLAB_TOKEN="your_gitlab_personal_access_token"
export GITLAB_PROJECT_ID="12345678"
export GITLAB_DOMAIN="pages.example.com"

acme.sh --deploy -d pages.example.com --deploy-hook gitlab

7.12 PKCS12(PFX)格式转换

Windows IIS、Java(Tomcat、Spring Boot)等环境通常需要 PKCS12(.pfx)格式的证书。

# 生成 PFX 文件(会在 ~/.acme.sh/example.com/ 下生成 example.com.pfx)
acme.sh --toPkcs -d example.com --password your_pfx_password

# ECC 证书转换
acme.sh --toPkcs -d example.com --ecc --password your_pfx_password

# 查看生成的 PFX 文件
ls ~/.acme.sh/example.com/*.pfx

重要:必须指定 --password,否则续期时 PFX 文件不会自动更新。

7.12.1 部署到 Tomcat

# 将 PFX 文件复制到 Tomcat 目录
cp ~/.acme.sh/example.com/example.com.pfx /opt/tomcat/conf/

# 在 Tomcat server.xml 中配置:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
           maxThreads="150" SSLEnabled="true">
    <SSLHostConfig>
        <Certificate certificateKeystoreFile="conf/example.com.pfx"
                     certificateKeystorePassword="your_pfx_password"
                     type="RSA" />
    </SSLHostConfig>
</Connector>

7.13 查看和管理已安装的证书

# 查看所有已颁发证书
acme.sh --list

# 查看特定域名的证书详情
acme.sh --info -d example.com

# 手动重新安装(如果需要更新 reloadcmd 等)
acme.sh --install-cert -d example.com \
  --key-file       /etc/nginx/ssl/privkey.pem \
  --fullchain-file /etc/nginx/ssl/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

# 吊销证书
acme.sh --revoke -d example.com

# 删除证书(从 acme.sh 管理中移除,不吊销)
acme.sh --remove -d example.com

7.14 小结

本章介绍了 acme.sh 的证书部署方式:

  • 核心原则:始终使用 --install-cert 将证书复制到目标路径,不直接使用内部存储路径
  • Nginx/Apache:配置 --reloadcmd 实现续期后自动重载
  • HAProxy:使用专用 deploy hook 合并证书和私钥
  • SSH 远程部署:支持推送到多台服务器
  • 特殊环境:Synology NAS、Proxmox VE、Docker、OpenWrt 等均有对应支持
  • 格式转换:通过 --toPkcs 生成 Windows/Java 环境所需的 PFX 文件

下一章介绍证书的自动续期和维护管理。