Netcraft 当时对其监测的 SSL/TLS 网站发表了一项研究,发现其中只有 5% 正确实现了 HTTP 严格传输安全(HSTS)。本文介绍如何配置 NGINX 和 NGINX Plus 来实施 HSTS 策略。
什么是 HSTS?
HTTPS(使用 SSL 或 TLS 加密的 HTTP)是保障网站流量安全的重要措施,使攻击者很难截获、修改或伪造用户与网站之间的流量。
当用户手动输入域名,未加 http:// 或 https:// 前缀,或点击普通 http:// 链接时,发往网站的第一个请求会以未加密的普通 HTTP 发送。多数安全网站会立即返回重定向,将连接升级为 HTTPS。但处于合适位置的攻击者可以发动中间人(MITM)攻击,截获最初的 HTTP 请求,继而控制用户会话。
HSTS 通过告知浏览器“该域名只能使用 HTTPS 访问”来应对这种潜在漏洞。即使用户输入或点击普通 HTTP 链接,浏览器也会强制将连接升级为 HTTPS:

HSTS 如何工作?
安全的 HTTPS 网站通过发送以下 HTTP 响应头发布 HSTS 策略:
Strict-Transport-Security: max-age=31536000
浏览器在 HTTPS 网站收到这个响应头后,就会“记住”该域名只能使用 HTTPS(SSL 或 TLS)访问,并将这条信息缓存 max-age 指定的时长,通常是 31,536,000 秒,约等于一年。
可选参数 includeSubDomains 告知浏览器,策略也适用于当前域名的所有子域:
Strict-Transport-Security: max-age=31536000; includeSubDomains
例如,https://www.example.com 的 HTML 响应可以包含对 https://example.com 上某个资源的请求,以确保为 example.com 的所有子域设置 HSTS。
在 NGINX 中配置 HSTS
在 NGINX 中设置 Strict Transport Security(STS)响应头相当直接:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
always 参数确保所有响应都设置该响应头,包括内部生成的错误响应。旧版 NGINX(1.7.5 之前)或 NGINX Plus R5 之前的版本不支持 always,因此不会在内部生成的错误响应中设置该头。
add_header 指令的继承规则
NGINX 配置块会从外层配置块继承 add_header 指令,所以只需在顶层 server 块中放置该指令。有一个重要例外:如果某个块自身包含 add_header 指令,它就不会继承外层配置块的响应头,需要重新声明所有 add_header 指令:
server {
listen 443 ssl;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# This 'location' block inherits the STS header
location / {
root /usr/share/nginx/html;
}
# Because this 'location' block contains another 'add_header' directive,
# we must redeclare the STS header
location /servlet {
add_header X-Served-By "My Servlet Handler";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
proxy_pass http://localhost:8080;
}
}
谨慎测试 HTTP 严格传输安全
客户端收到 HSTS 策略后,会在 max-age 指定的时间内缓存它。在此期间,浏览器拒绝通过未加密的 HTTP 访问该服务,也拒绝允许证书错误的例外(前提是网站此前提供过有效、可信的证书)。如果策略包含 includeSubDomains,这些限制也适用于所有子域。
撤销 HSTS 策略、进而移除网站或服务的 HTTPS 版本非常困难。测试 HSTS 时,应使用很短的 max-age,并确认你能接受其影响,以及持续维护 HTTPS 网站的责任。首次上线时也应保持较短的 max-age,确信配置正确后再增加。
每个 HTTPS 响应都需要 STS 头吗?
目标是在用户开始 HTTPS 会话时,尽早向其提供 HSTS 策略。如果会话期间用户没有收到该策略,之后仍可能遭遇 HTTP 劫持攻击。
浏览器只需看到一次 STS 头,因此严格来说,并不需要把它加入每个 location 块和每个响应。但只在首页或登录页加入,通常不够;如果仅在可缓存的响应中加入,客户端可能看不到它。应在合理范围内覆盖尽可能多的 URL,尤其关注动态、不可缓存的内容。
同时提供网站的 HTTP 与 HTTPS 版本
一些网站在同一个 NGINX 或 NGINX Plus 服务中同时提供 HTTP 和 HTTPS,让用户可以通过任一协议访问内容:
server {
listen 80;
listen 443 ssl;
# ...
}
启用 HSTS 时,这样做并不合适,因为不应让用户通过 HTTP 访问内容。相反,应将所有 HTTP 访问重定向到 HTTPS:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Discourage deep links by using a permanent redirect to home page of HTTPS site
return 301 https://$host;
# Alternatively, redirect all HTTP links to the matching HTTPS page
# return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name www.example.com;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
}
加强 HSTS
客户端在指定的 max-age 期间内看到过相关域名的 STS 头后,就能获得防止 HTTP 截获的保护。
但是,HSTS 并不能完美解决 HTTP 会话劫持。如果用户通过 HTTP 访问受 HSTS 保护的网站,并且属于下列情况,仍可能遭遇攻击:
- 从未访问过该网站。
- 最近重新安装了操作系统。
- 最近重新安装了浏览器。
- 换用了新的浏览器。
- 换用了新的设备,例如手机。
- 清除了浏览器缓存。
- 很久没有访问网站,
max-age已经过期。
来源:Netcraft。
为解决这一问题,Google 维护一份“HSTS 预加载列表”,记录使用 HSTS 并向 hstspreload.appspot.com 提交域名的网站及子域。该列表随主要浏览器分发,并硬编码到浏览器中。客户端访问列表里的域名时,会自动使用 HTTPS,拒绝通过 HTTP 访问。
原文随后提醒:一旦设置 STS 头或将域名提交到 HSTS 预加载列表,就无法移除;它将域名改为通过 HTTPS 提供服务形容为单向决定。
延伸阅读
有关 HSTS 的详细信息,参阅:
- RFC 6797:HTTP Strict Transport Security(HSTS)。
- 维基百科的 HTTP Strict Transport Security。
- 浏览器对 HSTS 的支持情况。
如果正在考虑为 NGINX 配置添加 STS 头,也可以一并考虑原文提到的其他安全相关 HTTP 响应头,例如 X-Frame-Options 和 X-XSS-Protection。这是原文当年的建议,具体选用应结合当前浏览器和安全规范。
来源与版权
原文:HTTP Strict Transport Security (HSTS) and NGINX;作者 Owen Garrett;发布日期 2016 年 3 月 23 日。© F5, Inc.,保留所有权利。本中文版本依据转载授权翻译;未执行文中配置。











暂无评论内容