在Nginx中将HTTP重定向到HTTPS:最佳实践
使用专用的80端口块在Nginx中设置可靠的HTTP到HTTPS重定向,避免重定向循环,选择合适的重定向代码,并在准备就绪时添加HSTS。
在Nginx中将HTTP重定向到HTTPS:最佳实践
在Nginx中将HTTP重定向到HTTPS可确保访问者使用您网站的加密版本,即使他们输入了旧的http:// URL或点击了过时的链接。一个干净的重定向设置能提高安全性,避免重复URL,并为用户提供一致的入口点。
最佳方法通常很简单:仅保留80端口足够长时间以重定向流量,然后在443端口上使用有效的TLS证书提供真实网站。
细节很重要,因为重定向很容易做得几乎正确。一个丢失路径的重定向会破坏书签。一个保留错误主机名的重定向可能会创建重复的规范URL。如果Nginx不了解TLS终止的位置,负载均衡器后面的重定向可能会无限循环。
将重定向视为公共API的一部分。人们将链接粘贴到聊天中,搜索引擎爬取它们,监控系统访问它们,旧邮件多年后仍在使用它们。如果重定向稳定,没人会注意到。如果它不完善,用户会在您的应用程序收到请求之前看到证书警告、路径错误或过多重定向错误。
为什么HTTPS重定向很重要
HTTPS通过使用TLS加密保护浏览器和服务器之间的流量。没有它,用户和网站之间的网络可以检查或修改数据。这对登录、表单、Cookie、管理区域、API甚至普通浏览都很重要。
重定向也有助于一致性。搜索引擎和用户不应将http://example.com/page和https://example.com/page视为两个独立的目标。永久重定向告诉客户端HTTPS是首选版本。
标准的Nginx模式是专用的80端口服务器块:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}
然后您的HTTPS服务器块处理实际网站:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html;
}
return 301指令高效且清晰。它告诉Nginx发送永久重定向,无需额外处理位置。$request_uri保留路径和查询字符串,因此/docs?page=2变为https://example.com/docs?page=2。
有关完整的TLS设置,请参阅逐步使用HTTPS保护Nginx。
对于大多数网站,避免在80端口块中放置应用程序逻辑。它不应提供静态文件、代理到应用程序或包含大量位置。保持简单:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
这个小块更容易审计,并且不太可能偏离HTTPS配置。
选择301还是302重定向
在生产网站上使用301进行永久HTTP到HTTPS重定向。浏览器和搜索引擎理解HTTPS URL应替换HTTP URL。
仅在重定向是临时时使用302或307。例如,在证书和主机名尚未最终确定时,您可能使用临时重定向进行测试。一旦HTTPS网站准备就绪,切换到永久重定向。
在早期设置时要小心。浏览器可能会积极缓存301重定向。如果您意外重定向到错误的主机名,即使您修复了Nginx,浏览器也可能继续使用错误的重定向。尽可能使用curl、私有浏览器窗口和非生产主机名进行测试。
一个实用的部署流程如下:
- 确认HTTPS服务器块直接工作。
- 确认证书匹配每个主机名。
- 添加HTTP重定向服务器块。
- 测试多个路径和查询字符串。
- 仅在行为正确时将临时重定向更改为永久重定向。
您还应决定规范主机名。如果example.com和www.example.com都有效,选择一个作为首选公共主机名。否则,用户可能会在主机名之间跳转,或搜索引擎可能会索引两者。
例如,将所有HTTP流量重定向到非www的HTTPS主机名:
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
这与https://$host$request_uri不同,后者保留用户请求的主机。
如果您希望行为明确,也可以将主机名重定向与方案重定向分开:
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 80;
server_name example.com;
return 301 https://example.com$request_uri;
}
这更冗长,但使最终目标显而易见。在小型网站上,两种风格都可以。在具有许多别名的较大网站上,显式服务器块可以减少后续更改时的混淆。
避免重定向循环和证书问题
当Nginx、负载均衡器或应用程序不断将请求发送回触发另一个重定向的URL时,就会发生重定向循环。这在TLS在Nginx之前终止时很常见,例如在云负载均衡器或CDN处。
在简单的单服务器设置中,Nginx直接接收HTTPS,因此重定向很简单。在代理链中,Nginx可能从负载均衡器接收纯HTTP,即使用户通过HTTPS连接。如果您的应用程序随后基于本地连接方案强制使用HTTPS,可能会导致循环。
修复取决于您的架构。通常,负载均衡器应传递诸如X-Forwarded-Proto之类的标头,并且应用程序或Nginx配置应仅信任来自已知代理地址的标头。
例如,如果Nginx位于受信任的负载均衡器后面并且仅接收内部HTTP,您可能不希望Nginx重定向每个本地HTTP请求。相反,负载均衡器可以处理公共HTTP到HTTPS重定向,而Nginx从私有网络提供流量。如果Nginx必须做出决定,它需要来自其前面的代理的可靠转发协议信息。不要信任来自任意互联网客户端的X-Forwarded-Proto。
还要确保证书覆盖每个重定向的主机名。如果用户访问http://www.example.com并且您重定向到https://www.example.com,则证书必须对www.example.com有效。如果您将所有内容重定向到https://example.com,则最终网站的证书必须覆盖example.com。
使用以下命令测试:
curl -I http://example.com/some/path?x=1
查找:
HTTP/1.1 301 Moved Permanently
Location: https://example.com/some/path?x=1
然后测试HTTPS URL:
curl -I https://example.com/some/path?x=1
HTTPS响应应返回页面的实际状态,而不是另一个重定向回HTTP。
如果两个主机名都存在,也要测试它们:
curl -I http://www.example.com/
curl -I https://www.example.com/
curl -I http://example.com/
curl -I https://example.com/
您正在寻找一个简短、可预测的链。从HTTP到规范HTTPS URL的一个重定向是好的。多次跳转,例如HTTP非www到HTTPS非www到HTTPS www再返回,是Nginx、应用程序、CDN规则或DNS级别转发相互冲突的迹象。
您可以使用以下命令检查整个链:
curl -IL http://www.example.com/some/path
-L标志跟随重定向。在干净的设置中,输出应显示初始HTTP响应,然后是最终HTTPS响应。如果您看到三个或四个Location标头,请简化规则,直到有一条清晰的路径到达规范URL。
HSTS和其他最佳实践
在您的HTTPS设置稳定后,您可以考虑HTTP严格传输安全,通常称为HSTS。HSTS告诉浏览器将来访问时自动使用HTTPS。
一个常见的标头如下所示:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
不要随意添加此标头。如果您包含子域,每个子域都必须支持HTTPS。如果您后来破坏了HTTPS,看到HSTS标头的浏览器可能会拒绝访问HTTP版本。在测试期间从较短的max-age开始,然后在您有信心时增加它。
其他最佳实践:
- 保持80端口块简单。
- 保留路径和查询字符串,除非您有理由不这样做。
- 选择一个规范主机名。
- 使用
curl测试重定向,而不仅仅是浏览器。 - 自动续订证书并监控续订失败。
- 尽可能将重定向逻辑保留在Nginx中,而不是在应用程序中重复。
简单的重定向规则更容易推理,并且在以后的网站更改中不太可能中断。
常见错误
最常见的错误是使用rewrite进行简单重定向:
rewrite ^ https://example.com$request_uri permanent;
这可以工作,但return 301 ...更清晰,并避免额外的重写处理。当您真正需要模式匹配时使用rewrite,而不是用于基本方案重定向。
另一个错误是重定向到$server_name而不理解其内容。$host来自请求主机标头,而$server_name基于匹配的Nginx服务器名称。对于规范重定向,文字主机名通常是最不令人惊讶的选择:
return 301 https://example.com$request_uri;
如果您的证书工具需要在80端口上使用ACME HTTP挑战路径,您还应避免重定向它们。许多Let's Encrypt设置会自动处理此问题,但自定义配置可能需要例外:
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://example.com$request_uri;
}
仅当您的证书客户端使用此例外时才添加它。如果您的证书通过DNS验证或工具管理的临时服务器续订,请保持重定向块简单。
安全推出模式
对于生产网站,分阶段进行更改:
- 确认HTTPS服务器块正确提供网站。
- 确认证书续订工作或受到监控。
- 如果您仍在测试主机名,则在80端口上添加临时重定向。
- 使用
curl -I和curl -IL测试常见URL。 - 一旦重定向目标最终确定,切换到
301。 - 在启用长期HSTS之前等待。
这个等待期很有用。它让您有时间捕获被遗忘的子域、旧的webhook URL、硬编码的http://链接或从您的第一个测试机器看不到的CDN规则。
也要考虑监控。如果您的正常运行时间检查仍然指向http://example.com,决定它是否应期望301或跟随重定向并检查最终HTTPS页面。两者都可以有效,但监控应与您实际想要的行为匹配。
示例:静态网站和反向代理
对于静态网站,HTTPS块可能只是一个文档根:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com;
index index.html;
}
对于Nginx后面的应用程序,重定向块保持不变,但HTTPS块代理流量:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
重要的一点是分离。80端口决定浏览器应去哪里。443端口提供网站。混合这些任务会使重定向行为更难推理,尤其是当稍后添加另一个代理或CDN时。
编辑后,始终运行:
sudo nginx -t
sudo systemctl reload nginx
如果测试失败,不要重新加载。首先修复语法错误,然后再次测试。
一个最后的检查值得从服务器外部进行,而不仅仅是通过SSH在主机上进行。防火墙、CDN或负载均衡器可以改变真实用户看到的内容。从您的笔记本电脑、监控位置或临时云实例运行相同的curl -I检查。如果外部结果与本地主机不同,则重定向问题可能出在Nginx前面的网络层,而不是服务器块本身。在重写工作配置之前检查这一点。
何时寻求帮助
如果您的网站位于CDN、云负载均衡器、Kubernetes Ingress或多个反向代理后面,请向DevOps工程师寻求帮助。分层基础设施中的HTTPS重定向取决于TLS终止的位置以及信任哪些标头。
在跨多个子域启用长期HSTS之前,您也应寻求帮助。错误的设置可能会将用户锁定在尚未准备好使用HTTPS的服务之外。
在Nginx中将HTTP重定向到HTTPS是一个小的配置更改,具有很大的安全影响。使用专用的80端口重定向块,保留请求URI,验证证书,并在完成之前测试循环。