搜索全站
安装部署 17 分钟阅读

SSL 证书申请与部署完整教程:免费证书、自动续期与强制 HTTPS

从零给 WordPress 站点配 HTTPS:讲清 DV/OV/EV 证书区别、acme.sh 与 certbot 申请 Let's Encrypt 证书的两种方式、DNS-01 与 HTTP-01 验证的选择、自动续期配置与强制跳转,附排错命令。

U
UFO_PF 2017-02-09 · 阅读 17 分钟
SS 安装部署
阅读提示
本文约 6,502 字,预计阅读 17 分钟。文中命令与配置均经过实测,可直接参考执行。

引言:不装证书,浏览器直接给你标红

现在的浏览器对 HTTP 站点的态度已经非常明确:Chrome 在地址栏显示”不安全”,表单输入框会提示”此连接不安全”;百度和 Google 都把 HTTPS 作为排名信号之一;更实际的问题是,如果你的站点要做微信分享、接入支付、使用 Service Worker 或者 getUserMedia,全部要求 HTTPS 环境。

好消息是证书本身已经免费——Let’s Encrypt 提供 90 天有效期的免费 DV 证书,配合 acme.sh 可以全自动续期,一次配置永久免维护。麻烦的部分在细节:验证方式选错导致签发失败、续期时 Nginx 没重载导致证书过期、强制 HTTPS 后后台重定向循环、混合内容(Mixed Content)导致小绿锁不显示。

原理分析:证书验证的是”域名控制权”而非”身份”

证书类型。 按验证强度分三类:

类型 全称 验证内容 签发时间 适用场景 价格
DV Domain Validation 只验证域名控制权 几分钟 博客、个人站、绝大多数企业站 免费
OV Organization Validation 验证组织真实性 3–7 工作日 企业官网、需要展示主体信息 数百至数千元/年
EV Extended Validation 严格验证组织与法律实体 1–2 周 金融、电商、支付 数千元/年

对绝大多数 WordPress 站点,免费 DV 证书完全够用。 DV 和 OV/EV 的加密强度是一样的,区别只在于证书里是否包含企业信息以及审核严格程度。

两种验证方式的区别。

  • HTTP-01 验证: ACME 服务器要求你在 http://域名/.well-known/acme-challenge/ 下放一个特定文件,能访问到就证明你控制该域名。优点是全自动;缺点是必须开放 80 端口,且泛域名证书不支持这种方式。
  • DNS-01 验证: ACME 服务器要求你在 DNS 里添加一条 _acme-challenge 的 TXT 记录。优点是支持泛域名证书(*.mimangfei.com)、不需要开放任何端口、服务器在内网也行;缺点是需要 DNS 服务商提供 API 密钥来自动添加记录。

为什么必须自动续期。 Let’s Encrypt 证书有效期 90 天,到期后浏览器会显示”您的连接不是私密连接”,访客基本会直接关掉。人工续期三年必然忘一次,所以自动续期不是可选项而是必选项。

证书链与中间证书。 部署时需要配置的是 fullchain.pem(站点证书 + 中间证书)而不是 cert.pem(仅站点证书)。只配 cert.pem 会导致部分浏览器(尤其是 Android 和旧版 Java 客户端)报 NET::ERR_CERT_AUTHORITY_INVALID,而桌面 Chrome 却正常——因为 Chrome 会用 AIA 扩展自动补全中间证书,其他客户端不一定会。

保姆级实操:用 acme.sh 申请并自动续期

步骤 1:安装 acme.sh 并设置默认 CA。

# 安装(邮箱用于到期提醒)
curl https://get.acme.sh | sh -s email=admin@mimangfei.com
source ~/.bashrc

# 切换默认 CA 为 Let's Encrypt(acme.sh 默认已改为 ZeroSSL,也可保持默认)
acme.sh --set-default-ca --server letsencrypt

步骤 2A:HTTP-01 方式签发(有 80 端口、单域名)。

# 用 Nginx 模式,acme.sh 会自动改配置再还原
acme.sh --issue -d mimangfei.com -d www.mimangfei.com --nginx

# 或者用 standalone 模式(需临时停掉占用 80 端口的服务)
acme.sh --issue -d mimangfei.com --standalone

步骤 2B:DNS-01 方式签发(推荐,支持泛域名)。 以 DNSPod(腾讯云)为例,先在控制台创建 API 密钥:

export DP_Id="你的API_ID"
export DP_Key="你的API_Token"

# 签发泛域名证书,包含根域与二级通配
acme.sh --issue --dns dns_dp -d mimangfei.com -d '*.mimangfei.com'

# 阿里云用 dns_ali,Cloudflare 用 dns_cf,完整列表见 acme.sh 的 dnsapi 目录

API 密钥会被 acme.sh 保存在 ~/.acme.sh/account.conf 里,续期时自动读取,不要把这个文件提交到代码仓库。

步骤 3:安装证书到指定目录并配置自动重载。 不要直接引用 ~/.acme.sh/ 下的文件(该目录结构可能变动),用 --install-cert 安装到固定路径:

mkdir -p /etc/nginx/ssl/mimangfei.com

acme.sh --install-cert -d mimangfei.com \
  --key-file       /etc/nginx/ssl/mimangfei.com/key.pem \
  --fullchain-file /etc/nginx/ssl/mimangfei.com/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

--reloadcmd 是关键:acme.sh 会自动创建 crontab 任务,每天检查到期时间,续期成功后执行这条命令让 Nginx 加载新证书。

步骤 4:配置 Nginx 的 HTTPS 与强制跳转。

# HTTP 全部 301 跳转到 HTTPS(保留 ACME 验证路径不走跳转)
server {
    listen 80;
    server_name mimangfei.com www.mimangfei.com;

    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }

    location / {
        return 301 https://www.mimangfei.com$request_uri;
    }
}

server {
    listen 443 ssl;
    http2 on;
    server_name mimangfei.com;
    ssl_certificate     /etc/nginx/ssl/mimangfei.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/mimangfei.com/key.pem;
    return 301 https://www.mimangfei.com$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name www.mimangfei.com;
    root /var/www/html;
    index index.php index.html;

    ssl_certificate     /etc/nginx/ssl/mimangfei.com/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/mimangfei.com/key.pem;

    # 会话复用,减少 TLS 握手开销
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;

    # HSTS:告诉浏览器以后只走 HTTPS(确认长期用 HTTPS 再开启)
    add_header Strict-Transport-Security "max-age=31536000" always;

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

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        # 关键:让 WordPress 知道前端是 HTTPS,否则会出现重定向循环
        fastcgi_param HTTPS on;
    }
}

步骤 5:修改 WordPress 站点地址。 后台「设置 → 常规」把 WordPress 地址(URL)和站点地址(URL)改成 https:// 开头。如果后台进不去,用 wp-config.php 强制:

// wp-config.php:在 "/* 好了!请停止编辑 */" 之前加入
define( 'WP_HOME',    'https://www.mimangfei.com' );
define( 'WP_SITEURL', 'https://www.mimangfei.com' );

// 反向代理/CDN 场景下必须加,否则后台重定向循环
if ( isset( $_SERVER['HTTP_X_FORWARDED_PROTO'] ) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https' ) {
    $_SERVER['HTTPS'] = 'on';
}

步骤 6:批量修复历史内容里的 HTTP 链接(混合内容)。 地址栏不显示锁图标,通常是因为页面里还引用了 http:// 的图片或脚本。用 WP-CLI 全站替换:

# 先干跑预览,确认影响范围
wp search-replace 'http://www.mimangfei.com' 'https://www.mimangfei.com' --dry-run --precise

# 确认无误后执行(--precise 使用 PHP 序列化安全替换,避免破坏插件设置)
wp search-replace 'http://www.mimangfei.com' 'https://www.mimangfei.com' --precise

# 只替换文章内容里的链接,不动设置项
wp search-replace 'http://' 'https://' wp_posts --precise

步骤 7:验证配置并做在线检测。

# 检查证书有效期与证书链
echo | openssl s_client -connect www.mimangfei.com:443 -servername www.mimangfei.com 2>/dev/null \
  | openssl x509 -noout -dates -subject -issuer

# 确认自动续期任务存在
crontab -l | grep acme

# 手动测试续期流程(加 --force 强制,确认无误后再靠自动任务)
acme.sh --renew -d mimangfei.com --force

# 检查 301 跳转是否正确
curl -I http://www.mimangfei.com

最后用 SSL Labs 的在线检测(ssllabs.com/ssltest)确认评级达到 A,重点看证书链是否完整(”Chain issues: None”)。

常见踩坑

  1. 只配了 cert.pem 没配 fullchain.pem。 桌面浏览器正常、部分手机报证书不可信,排查方向就是证书链不完整。
  2. CDN 后面配置 HTTPS 导致重定向循环。 WordPress 收到的是 HTTP 请求,又跳回 HTTPS,无限循环。解决办法是上面的 $_SERVER['HTTPS'] = 'on' 片段,或者让 CDN 回源也走 HTTPS。
  3. 80 端口被防火墙挡住导致 HTTP-01 验证失败。 报错 urn:ietf:params:acme:error:connection。要么临时放行,要么改用 DNS-01。
  4. 没加 --reloadcmd,证书续期了但 Nginx 没加载。 表现为”证书文件是新的,浏览器还是报过期”。systemctl reload nginx 必须加上。
  5. 开了 HSTS 又想回退到 HTTP。 HSTS 的 max-age 有效期内浏览器强制走 HTTPS,无法绕过。没想清楚之前不要开 HSTS。
  6. Let’s Encrypt 的速率限制。 同一域名每周最多签发 50 张证书,同一注册域名每周 5 张(Renewal Exempt)。调试阶段狂点会触发 too many certificates already issued,此时要用 --staging 测试环境。
  7. 案例:一次证书过期导致全站不可访问。 客户的证书是半年前用 certbot 手动申请的,配置的 cron 任务写的是 certbot renew --quiet,但因为安装时用的是 snap,cron 里的 PATH 找不到 certbot 可执行文件,任务静默失败。证书过期当天,全站访问显示 NET::ERR_CERT_DATE_INVALID,流量当天掉了 90%。处理方式是紧急用 certbot certonly --nginx 重签,然后改用 systemctl list-timers | grep certbot 确认 systemd timer 处于 active 状态。教训:配完自动续期必须验证一次——用 certbot renew --dry-run 或 acme.sh --renew --force 跑一遍,并且配置到期前 20 天的邮件/短信提醒作为第二道防线。

总结

SSL 配置的正确顺序是:选验证方式(有 80 端口用 HTTP-01,要泛域名用 DNS-01)→ 签发 → 安装到固定目录并配 reloadcmd → 配置 Nginx 443 与 301 跳转 → 改 WordPress 站点地址 → WP-CLI 批量替换历史链接 → 验证证书链与自动任务。判断标准:SSL Labs 评级 A、证书链完整、crontab 里有 acme 任务、所有页面无混合内容警告。证书配好之后,下一步是 网站监控与告警搭建,把证书到期也纳入告警项;安全方面还可以配合 网站上线安全基线配置 中的 HSTS 与安全头一起配置。

U
UFO_PF作者

专注 WordPress 生态的实战作者,内容以「可复制、可验证」为第一原则。

留下你的想法

邮箱地址不会被公开。带 * 的为必填项。