Web 开发

把一台裸服务器变成能跑 HTTPS 的站点,配置本身并不复杂,真正容易出问题的是那些「看起来没问题」的细节。这篇记录完整的操作顺序,以及我在安卓端踩到的证书链坑。

一、准备证书

域名解析生效后,直接用 certbot 申请即可。它会同时改好 Nginx 配置并设置自动续期:

apt install -y certbot python3-certbot-nginx
certbot --nginx -d it365.wang -d www.it365.wang

# 验证自动续期是否就绪(不会真的续期)
certbot renew --dry-run

要注意 certbot 写入的配置会覆盖你手工加的 location 块,所以顺序上建议 先让它改完、再手工调整

二、反向代理配置

后端跑在 127.0.0.1:8080,前端由 Nginx 接管:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name it365.wang;

    ssl_certificate     /etc/letsencrypt/live/it365.wang/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/it365.wang/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host              $host;
        proxy_set_header X-Real-IP         $remote_addr;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

几个容易漏掉的响应头,建议一并补上:

响应头作用
Strict-Transport-Security让浏览器记住只用 HTTPS 访问
X-Content-Type-Options阻止浏览器猜测 MIME 类型
X-Frame-Options避免站点被嵌入 iframe 钓鱼

2.1 强制跳转 HTTPS

单独监听 80 端口并做 301:

server {
    listen 80;
    server_name it365.wang www.it365.wang;
    return 301 https://it365.wang$request_uri;
}
不要用 rewrite ... permanent 来做这件事。它在某些 location 组合下会丢掉查询字符串,而 return 301 不会。

三、踩到的坑

  • 证书链不全:只部署 cert.pem 时桌面浏览器正常,但安卓与部分 App 内嵌 WebView 会直接握手失败。一定要用 fullchain.pem
  • 漏掉 X-Forwarded-Proto:后端以为自己在跑 HTTP,把绝对地址拼成 http://,于是出现混合内容告警。
  • 只监听 IPv4:双栈机器上不写 listen [::]:443 ssl; 就不会响应 IPv6 请求。

排查握手问题最快的办法是直接问 openssl:

openssl s_client -connect it365.wang:443 -servername it365.wang \
  </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer

如果输出里只有一个证书、没有中间证书,基本就能确定是链不全。

四、小结

记住三点:先让工具改配置、再手工优化;证书用 fullchain;跳转用 return 而不是 rewrite。剩下的交给 certbot renew --dry-run 定期体检即可。

参与讨论