1 起因
最近突然发现我的网站在苹果手机上Safari浏览器上第一次会访问会非常慢,但只要第一次访问后,后续的访问速度均不受影响...
这就纳闷了,网站速度我都是优化过的,为什么会存在这种情况呢?
困扰我许久,因为只有手机访问才这样,在电脑上访问速度都很快,完全没有头绪...
2 问题研究
在多次尝试后,发现在Firefox和IE浏览器上能复现该问题,在ssl握手之前,Firefox会阻塞2s,IE浏览器会阻塞10s以上;
问题能复现就好解决了;
使用Charles进行抓包,发现使用IE打开网站的时候,会去请求ocsp.int-x3.letsencrypt.org
域名进行证书验证,该域名无法访问从而导致访问速度变慢;
所以问题就出在SSL证书上面,我使用的证书全部是Let's Encrypt证书,其特点是免费、支持泛域名、并且脚本一键部署,但是Let's Encrypt证书的OCSP验证域名被DNS污染,无法解析到正确的IP地址,导致无法进行证书有效性验证。
这时候有同学要问了:OCSP验证又是什么呢?
OCSP 称 Online Certificate Status Protocol,即在线证书状态协议。
说白了就是查询你的SSL证书是不是被提供商吊销了,正常访问一个网站时,浏览器要先去使用OCSP协议去查询你的证书是否有效,如果有效才进行接下来的操作;
可全球这么多网站,都去验证的话OCSP服务器也扛不住,所以查询就可能超时;
不同浏览器都会有不同的OCSP验证超时时间,超过那个时间就不进行验证了,先默认你是有效的继续进行访问。
但是有些"注重用户体验"的浏览器,比如Chrome就感觉这种方式很不好,于是Chrome浏览器就不用这个协议了,自己在浏览器内部做了个本地列表,通过每次浏览器更新来进行列表更新;
由于直接查本地的列表速度就非常快了,缺点是不是实时的;受Chromium项目的影响,国内的绝大部分换皮浏览器也使用的这个策略,所以大部分的浏览器压根就没有进行使用OCSP协议去验证SSL域名的有效性,所以大部分的浏览器访问速度就很快了;
但是有些浏览器,比如苹果的Safari和IE就强制开启了OCSP验证,Firefox的OCSP验证可以在选项中关闭;
由于Let's Encrypt证书的OCSP验证域名被DNS污染,无法进行验证,所以就出现了首次访问会很慢的原因,也就是OCSP验证超时了。
如何解决DNS污染呢?其实也简单,不使用国内公共DNS就好了,使用国外公共DNS,例如8.8.8.8等,就可以正常访问Let's Encrypt的OCSP验证域名了;但是在国内使用8.8.8.8速度不是很理想,你也不可能要求所有人去使用这个DNS吧,所以还是得从源头解决这个问题。
那么解决办法显而易见了~ 对!就是换个SSL证书!...
好了本文结束,感谢大家的阅读,完结撒花~~
哈哈,换SSL证书说着简单,但是买一个泛域名证书属实挺贵的,钱包不允许个人也没必要买;市面上免费证书也只有腾讯阿里等大厂提供的单域名证书,申请麻烦还得一个个去部署更麻烦...对我来说不太现实
所以还是得想办法解决DNS污染的问题,给每个人都换DNS服务器不现实,那直接给服务器换DNS服务器不就行了,OCSP验证放在网站服务器上并且缓存在本地,客户端直接查询服务器缓存的结果,也不用去等,多好
这时候就需要Nginx的OCSP装订功能了
3 Nginx OCSP stapling
由网站服务器去进行OCSP查询,缓存查询结果,然后在与浏览器进行TLS连接时返回给浏览器,这样浏览器就不需要再去查询了。
是不是很棒?
别急,开启之前我们是不是得先知道如何查询是否开启了OCSP装订,不然捣鼓半天都不知道开启成功了没有可还行..
4 检测OCSP
有两种方法:
1.在线检测:
亚洲诚信的SSL检测工具 https://myssl.com/
2.手动检测:
openssl s_client -connect ffis.me:443 -servername ffis.me -status -tlsextdebug < /dev/null 2>&1 | grep -i "OCSP response"
5 开启 OCSP stapling
开启OCSP装订需要在网站的nginx配置文件中添加如下配置:
# 开启 OCSP Stapling,开启后服务器在TLS握手时发送事先缓存的OCSP响应,用户只需验证该响应的有效性而不用再向数字证书认证机构(CA)发送请求
ssl_stapling on;
# 启用或禁用服务器对OCSP响应的验证
ssl_stapling_verify on;
# 证书的签发机构的ca证书,我的Let's Encrypt是acme.sh自动获取的证书,ca证书目录为:/root/.acme.sh/ffis.me/ca.cer
ssl_trusted_certificate /usr/local/nginx/conf/ssl/ffis.me_ca.cer;
# 添加resolver解析OSCP响应服务器的主机名,valid表示缓存。这里添加是为了解决DNS污染问题。
resolver 8.8.8.8 8.8.4.4 223.5.5.5 valid=60s;
# 网络超时时间
resolver_timeout 2s;
添加后,重启nginx服务器,再次使用命令验证,OCSP装订是不是就已经开启了?
使用myssl验证,也提示OCSP已装订
Firefox、IE、Safari浏览器的访问也正常了
再次使用Charles进行抓包,发现浏览器会跳过OCSP验证
6 弊端
此方式实现了OCSP查询在服务器端进行,避免了浏览器去进行OCSP验证从而影响访问速度;
但是OCSP响应的缓存并不是预加载的,而是异步加载的;
在Nginx启动后,只有当有客户端访问的时候,Nginx才开始去请求OCSP响应并缓存到本地,并且当OCSP响应缓存过期的时候并不会去主动更新,而是等待客户端访问异步触发的更新;
这样就会导致总会有几次访问并没有走OCSP响应缓存从而导致还是会有访问速度缓慢的情况发生。
7 优化
那如何解决呢?
最好的办法就是我们人工去请求OCSP响应并保存到本地,Nginx直接加载保存好的响应信息就行了;
这里我自己编写了Shell脚本去定时请求OCSP响应信息并保存到本地,利用 Nginx 的 ssl_stapling_file
指令直接读取本地响应信息,这样就不会去实时查询证书的OCSP服务器,在进行SSL握手的时候直接将本地的响应信息进行下发,极大的加快了访问速度。
开始优化
1.更改服务器DNS解析服务器
由于Let's Encrypt证书DNS解析被污染,在服务器上也是无法直接访问Let's Encrypt的OCSP验证域名的,解决访问有两个
一:我们更改服务器的DNS解析服务器为8.8.8.8等国外公共服务器
二:直接修改本地Hosts,指定OCSP域名ocsp.int-x3.letsencrypt.org
的IP地址
这里我选择第二种,直接修改本地Hosts文件
//编辑Hosts文件
vi /etc/hosts
//添加如下信息
175.45.42.218 ocsp.int-x3.letsencrypt.org
这里列举出来一些常用的ocsp.int-x3.letsencrypt.org
服务器IP地址
23.44.51.8 (美国)
23.44.51.27 (美国)
104.109.129.57 (英国)
104.109.129.11 (英国)
175.45.42.209 (香港)
175.45.42.218 (香港)
223.119.50.201 (香港)
223.119.50.203 (香港)
23.32.3.72 (东京)
2.编写Shell脚本去获取OCSP响应
直接在指定目录vim getOCSP.sh
,输入以下脚本信息(仅在CentOS7 x64系统下实测过没问题,其他系统请自行测试):
#!/bin/sh
SITE=$1
if [ ! $1 ]; then
echo "请输入查询域名!正确打开方式:sh getOCSP.sh ffis.me"
exit 0;
fi
# acme.sh 安装目录
LEDIR=/root/.acme.sh
# 证书目录
DIR=$LEDIR/$SITE
# Let's Encrype OCSP 服务器地址
HOST=ocsp.int-x3.letsencrypt.org
# OCSP 响应保存文件地址
OUTDIR=/xxx/xxx/xxx/xxx/xxx/live
# 日志文件地址
LOGFILE=$OUTDIR/log/get-ocsp_$(date +\%Y\%m\%d).log
echo $(date +"%Y-%m-%d %H:%M:%S") '-----域名:'$SITE '开始请求OCSP响应------' >> $LOGFILE
# 请求 ocsp 响应并保存到本地文件
openssl ocsp -no_nonce \
-respout $OUTDIR/$SITE.ocsp.resp.new \
-issuer $DIR/ca.cer \
-verify_other $DIR/ca.cer \
-cert $DIR/$SITE.cer \
-url http://$HOST/ \
-header "host="$HOST > $OUTDIR/$SITE.ocsp-reply.txt 2>&1
if grep -q ": good" $OUTDIR/$SITE.ocsp-reply.txt; then
if cmp -s $OUTDIR/$SITE.ocsp.resp.new $OUTDIR/$SITE.ocsp.resp; then
rm $OUTDIR/$SITE.ocsp.resp.new
else
mv $OUTDIR/$SITE.ocsp.resp.new $OUTDIR/$SITE.ocsp.resp
# nginx 配置重载
systemctl force-reload nginx.service > /dev/null
fi
else
cat $OUTDIR/$SITE.ocsp-reply.txt | echo "OCSP error for $SITE"
fi
# 输出到控制台
cat $OUTDIR/$SITE.ocsp-reply.txt
# 输出到日志文件
cat $OUTDIR/$SITE.ocsp-reply.txt >> $LOGFILE
# 记录最后一次OCSP请求结果
mv $OUTDIR/$SITE.ocsp-reply.txt $OUTDIR/$SITE.ocsp-reply-old.txt
echo $(date +"%Y-%m-%d %H:%M:%S") '------------OCSP响应请求完毕------------' >> $LOGFILE
我的这个脚本是针对 acme.sh 脚本而编写的,
如果你使用的也是acme.sh,直接修改脚本中的:
acme.sh安装目录:LEDIR
OCSP 响应保存文件地址:OUTDIR
日志文件地址:LOGFILE
并确保输入的目录已经创建好。
如果要获取指定证书文件的OCSP响应,则需要自己手动修改对应的证书目录和OCSP服务器地址等。
3.运行脚本
//添加可执行权限
chmod +x getOCSP.sh
//运行脚本
./getOCSP.sh ffis.me
//PS:后边的参数为需要获取OCSP响应的域名
运行后就会在指定的目录生成OCSP响应文件了
4.Nginx读取OCSP响应文件
这时的Nginx配置就比上边的方式要简单许多了
ssl_stapling on;
ssl_stapling_verify on;
//OCSP响应文件地址
ssl_stapling_file /xxx/xxx/xxx/xxx/xxx/live/ffis.me.ocsp.resp;
重启Nginx后,OCSP响应的预加载就完成了
这时再去检测下SSL证书状态
5.配置crontab定时任务,每日自动刷新OCSP缓存
虽然我们本地预加载了OCSP响应缓存,但是OCSP响应也是有生效时间了,超过有效时间就得重新获取,这里我们可以配置crontab定时任务去每日自动刷新OCSP响应缓存
//编辑crontab定时任务
crontab -e
//添加如下命令
#每日2:22分刷新OCSP缓存
22 2 * * * /root/getOCSP.sh ffis.me > /dev/null 2>&1
这样crontab定时任务就配置完成
至此我们的Nginx OCSP装订工作就到此完成~
IE已经不算浏览器了
最好的解决方案还是放弃Let's Encrypt,虽然说明1.13以后因为LE X3证书到期更换OCSP被污染的问题会解决,不过离开了DST Root新的根证书时间太短导致安卓7以下以及16年以前的一些设备没办法信任这个证书
最近看到环智中诚那个Encryption365提供泛域名,他家海外的ECMP AllinSSL也可以直接申请https://allinssl.com/zh/,和LE一样是90天的有效期,就都换过去了
补充下ZeroSSL的ACME方式也可以签发通配符,之前在网站上看到没有还以为不提供……
同样是Sectigo的根证书,还支持ACME自动化,基本上可以代替LE了
https://zerossl.com/features/acme/
OK,已经换上了,acme.sh直接更换CA可无缝切换使用,美滋滋
感谢推荐,这个确实可以考虑,但是我实在是懒得换,白嫖这么久都用出感情了 ,落后的设备就让它淘汰吧!积极拥抱新技术,一起走向新未来
谢大佬的分享,自动抓取脚本写的比我的好多了。不过发现有个问题,在 header 的 HOST 部分在 Ubuntu Server 20.04 下会出现 HOST 无效的错误:
Missing = in header key=value
我看了下似乎是和 openssl 版本有关,新版本参数更改为 header host=XXXX.com 了。
https://github.com/h2o/h2o/issues/1267
我很懒,直接不用变量,把参数写死到里面了,脚本就正常了。
手动缓存OCSP响应确实解决了大部分的浏览器访问问题,但是我发现Chromium系的谷歌浏览器和新版edge在IOS系统下就算开启了服务器OCSP装订还是会去验证OCSP,导致访问卡顿,不过好在超时时间比较短只有2s,估计是谷歌浏览器的bug,暂时没有解决办法 ,谷歌不是不验证OCSP吗,在IOS系统下怎么这么老实了
前一阵在 Twitter 上看到有人说 iOS 这个事情了。
好象是 iOS 在系统层面也会验证 OCSP,即使你用任何浏览器,系统都会去验证之类的。