SSL证书配置踩坑记一次搞定HTTPS部署难题
线上HTTPS部署踩坑记
昨天帮客户部署一个新项目到生产环境,本以为很简单的事情,结果SSL证书这里折腾了我整整一天。本来想用Let’s Encrypt免费证书,结果各种问题层出不穷。
一开始我按照平时的做法,直接用acme.sh申请证书,命令都挺熟悉了:
curl https://get.acme.sh | sh
~/.acme.sh/acme.sh --issue -d example.com --nginx
结果这里就踩第一个坑了。客户服务器的nginx配置比较特殊,用了多个location块,acme.sh自动检测的时候找不到正确的webroot路径,一直报错。折腾了半天发现原来是要手动指定webroot目录。
后来试了下手动DNS验证的方式:
~/.acme.sh/acme.sh --issue --dns -d example.com --yes-I-know-dns-manual-mode-enough-go-ahead-please
执行完命令会返回一个TXT记录值,需要去域名管理后台添加。这里我犯了个低级错误,复制粘贴的时候把前缀的下划线给漏掉了,导致验证失败。DNS记录更新一般需要几分钟生效,等了大概10分钟才开始验证。
证书安装过程的意外
证书申请下来之后,配置nginx的时候又遇到了问题。客户的服务器nginx版本比较老,不支持http2,配置文件里加上ssl_protocols那几行就会报语法错误。折腾半天才发现原来是nginx编译的时候没加–with-http_v2_module模块。
最后我的nginx配置是这样的:
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private.key;
# SSL安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# HSTS头设置
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://backend;
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;
}
}
这里有几个需要注意的地方,SSL证书路径一定要确保权限正确,私钥文件应该只有root用户能读取。另外HSTS头设置是个好习惯,但要注意max-age设得太大会影响调试。
中间件配置的坑
部署完之后测试发现一个问题,某些接口返回的响应头里有Mixed Content警告。检查了一下原来是应用代码里的某些重定向还是用了HTTP协议。这种情况在前后端分离的项目里特别容易出现,特别是API请求的baseURL配置。
我在前端代码里加了一个全局的axios拦截器来处理这个问题:
import axios from 'axios';
// 创建axios实例
const apiClient = axios.create({
baseURL: process.env.NODE_ENV === 'production'
? 'https://api.example.com'
: 'http://localhost:3000',
timeout: 10000,
});
// 请求拦截器
apiClient.interceptors.request.use(
config => {
// 确保生产环境下都是HTTPS
if (process.env.NODE_ENV === 'production') {
if (config.url && config.url.startsWith('http://')) {
config.url = config.url.replace('http://', 'https://');
}
}
return config;
},
error => {
return Promise.reject(error);
}
);
export default apiClient;
还有一个细节,WebSocket连接也要改成wss协议,不然浏览器一样会报Mixed Content错误:
const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
const socket = new WebSocket(${wsProtocol}//${window.location.host}/ws);
证书续期自动化
Let’s Encrypt证书只有90天有效期,所以必须设置自动续期。acme.sh自带续期功能,但为了保险起见我还是写了个crontab任务:
# 每天凌晨2点检查证书是否需要续期
0 2 * * * ~/.acme.sh/acme.sh --cron --home ~/.acme.sh > /var/log/acme_renew.log 2>&1
# 每次续期成功后重启nginx
0 2 * * * ~/.acme.sh/acme.sh --renew-all --ecc && nginx -s reload
这里有个坑需要注意,续期完成后必须reload nginx配置,否则新证书不会生效。我开始漏掉了nginx reload步骤,结果每次续期后还要手动重启nginx。
为了确保续期脚本正常工作,我还写了个监控脚本定期检查证书剩余有效期:
#!/bin/bash
CERT_FILE="/path/to/certificate.crt"
DAYS_LEFT=$(openssl x509 -in $CERT_FILE -noout -days_until_expiry)
if [ $DAYS_LEFT -lt 30 ]; then
echo "Warning: Certificate expires in $DAYS_LEFT days" | mail -s "SSL Certificate Alert" admin@example.com
fi
客户端兼容性问题
部署完成后的第二天,有用户反映在某些老旧设备上打不开网站。查了半天发现问题出在SSL协议版本上。虽然TLS 1.0和TLS 1.1已经不安全了,但有些旧的Android设备和Windows系统还需要支持。
为了兼顾安全性又能照顾到老设备,我把配置稍微调整了一下:
ssl_protocols TLSv1.2 TLSv1.3; # 移除了对老协议的支持
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
# 对于需要支持老设备的情况,可能需要添加更多cipher
ssl_ciphers HIGH:!aNULL:!MD5:!kEDH:!CAMELLIA;
不过我还是坚持移除了对TLS 1.0和1.1的支持,毕竟安全更重要。让那些还在用Win XP和Android 4.4以下系统的用户升级一下系统吧,这年代还用这些老古董确实有点危险。
监控告警不能少
这次折腾让我意识到SSL证书监控的重要性。之前都是靠用户反馈才发现证书过期的问题,太被动了。现在我在Prometheus里加了证书过期监控:
groups:
- name: ssl_certificates
rules:
- alert: SSLCertificateExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 7 # 7天内过期
for: 5m
labels:
severity: warning
annotations:
summary: "SSL certificate expiring soon"
description: "{{ $labels.instance }} SSL certificate expires in less than 7 days"
配合AlertManager可以实现邮件和短信通知,这样证书快过期的时候就能提前收到告警了。
整个过程下来,虽然踩了不少坑,但也算是积累了更多实战经验。下次再处理类似问题应该会更快一些。其实大部分问题都是因为对SSL/TLS协议理解不够深入,以后得多花点时间研究底层原理了。
以上是我这次SSL证书部署的踩坑记录,如果你也遇到过类似的问题或者有更好的解决方案,欢迎在评论区交流。

暂无评论