如何在 NGINX 上将 HTTPS 重定向到 HTTP?

Posted

技术标签:

【中文标题】如何在 NGINX 上将 HTTPS 重定向到 HTTP?【英文标题】:How do I redirect HTTPS to HTTP on NGINX? 【发布时间】:2011-04-23 01:43:07 【问题描述】:

有没有办法通过在域的 vhost 文件中添加规则来将 HTTPS 请求重定向到 HTTP?

【问题讨论】:

【参考方案1】:

为什么这样的东西有用?乍一看,我不确定它是否可以完成。但它提出了一个有趣的问题。

您可以尝试在配置文件中添加重定向语句并重新启动服务器。可能会发生两种可能性:

    服务器将发出重定向 - 您似乎想要的。 服务器会先做https交换,然后再发出重定向,这样的话,有什么意义呢?

如果我想出更具体的内容,会添加更多内容。

更新: (几个小时后) 你可以试试这个。你需要把它放在你的 nginx.conf 文件中 -

server 
       listen 443;
       server_name _ *;
       rewrite ^(.*) http://$host$1 permanent;
 

向客户端发送永久重定向。我假设您使用端口 443(默认)作为 https。

server 
    listen      80;
    server_name _ *;
    ...

添加此项,以便您在端口 80 上的正常 http 请求不受干扰。

更新: 2016 年 12 月 18 日 - 在 nginx 版本 > 0.6.25 中应使用 server_name _ 而不是 server_name _ *(感谢 @Luca Steeb)

【讨论】:

旧答案,但只想说它很有用,因为现在有“不安全的内容”规则。例如,使用 iframe 时。 它有用的另一个原因:我有一个托管多个域的服务器。其中一个具有 SSL,但其他域没有 SSL 证书。当有人通过 HTTPS 访问其他域时,它需要重定向到 HTTP 这对我不起作用,我在访问我的服务器时仍然遇到问题:OpenSSL: error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol Unable to establish SSL connection. 我认为解决方案不对。在没有ssl on 的情况下将 443 重定向到 80,与 https 不匹配,它不应该工作。但是如果添加ssl on,则需要ssl_certificates,并且必须提供正确的证书。所以我认为它不能在没有证书的情况下将 https 重定向到 http。相关问题:***.com/questions/3470290/… server_name _ 应该在 nginx 版本 > 0.6.25 中使用而不是 server_name _ *【参考方案2】:

rewriteif 应避免使用 Nginx。著名的一句话是,“Nginx 不是 Apache”:换句话说,Nginx 处理 URL 的方法比重写更好。 return 在技术上仍然是重写模块的一部分,但它没有 rewrite 的开销,并且不像 if 那样需要注意。

Nginx 在why if is "evil" 上有一个完整的页面。它还提供了一个建设性的页面来解释why rewrite and if are bad,以及如何解决它。以下是该页面关于rewriteif 的内容:

这是一种错误、繁琐且无效的方式。

您可以使用return 正确解决此问题:

server 
    listen 443 ssl;

    # You will need a wildcard certificate if you want to specify multiple
    # hostnames here.
    server_name domain.example www.domain.example;

    # If you have a certificate that is shared among several servers, you
    # can move these outside the `server` block.
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/cert.key;

    # 301          indicates a permanent redirect.  If your redirect is
    #              temporary, you can change it to 302 or omit the number
    #              altogether.
    # $http_host   is the hostname and, if applicable, port--unlike $host,
    #              which will break on non-standard ports
    # $request_uri is the raw URI requested by the client, including any
    #              querystring
    return 301 http://$http_host$request_uri;

如果您预计很多机器人不会发送 Host 标头,则可以使用 $host 而不是 $http_host,只要您坚持使用端口 80 和 443。否则,您需要动态填充$http_host 替代品。尽管使用了if,只要它出现在server 的根(而不是location 块)中,这段代码就是高效且安全的。但是,您需要使用默认服务器才能适用,这应该避免使用 https。

set $request_host $server_name:$server_port;
if ($http_host) 
    set $request_host $http_host;

如果您想对特定路径强制执行 SSL/TLS,但否则禁止:

server 
    listen 443 ssl;
    server_name domain.example;

    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/cert.key;

    location / 
        return 301 http://$host$request_uri;
    

    location /secure/ 
        try_files $uri =404;
    


server 
    listen 80;
    server_name domain.example;

    location / 
        try_files $uri =404;
    

    location /secure/ 
        return 301 https://$http_host$request_uri;
    

如果您的服务器没有与客户端直接通信(例如,如果您使用 CloudFlare),事情会变得有点复杂。您需要确保与客户端直接通信的任何服务器都在请求中添加适当的 X-Forwarded-Proto 标头。

使用这是一个混乱的提议;有关完整说明,请参阅IfIsEvil。为了使其有用,if 块不能位于 location 块内,原因有多种。这会强制使用rewrite 进行URI 测试。 简而言之,如果您必须在生产服务器上使用它...不要。可以这样想:如果您已经超越了 Apache,那么您就已经超越了这个解决方案。

/secure、/secure/ 和 /secure/ 中的任何内容都将强制执行 https,而所有其他 URI 将强制执行 http。 (?! ) PCRE 构造是negative lookahead assertion。 (?: ) 是 non-capturing group。

server 
    # If you're using https between servers, you'll need to modify the listen
    # block and ensure that proper ssl_* statements are either present or
    # inherited.
    listen 80;
    server_name domain.example;

    if ($http_x_forwarded_proto = https) 
        rewrite ^(?!/secure)/ http://$http_host$request_uri? permanent;
    
    if ($http_x_forwarded_proto != https) 
        rewrite ^/secure(?:/|$) https://$http_host$request_uri? permanent;
    

【讨论】:

好的,所以如果我理解正确的话,如果没有先建立 https 连接,就无法通过 http 向请求 https 的客户端提供服务。所以如果你想把人们从 https 转移到 http,你需要证书吗? @Freek 对,尽管您应该始终支持 HTTPS;这是一个非常基本但有效的安全功能,任何关心密码的用户都会想要它。您可以从Let's Encrypt 获得免费证书。即使您的网站不包含任何敏感信息,它也可能被用作攻击媒介。 如果您使用“删除 s”(https -> http)的弹性负载均衡器,则唯一可行的解​​决方案是使用 if ($http_x_forwarded_proto = https)... @jmcollin92 理想情况下,您应该在 ELB 和 Nginx 之间使用 HTTPS。这就是我倾向于做的事情。它不太方便,但更安全。 @jmcollin92 如果安全性是最重要的,那么麻烦是值得的。这使得可以访问您的 VPC 中的服务器的黑客更难以获取有用的信息。黑客通常会瞄准被遗忘的旧服务器,然后通过网络进行攻击。【参考方案3】:
location / 
    if ($scheme = https) 
        rewrite ^(.*)? http://$http_host$1 permanent;
    

【讨论】:

如果您想确保所有内容都匹配,可能需要添加一个比 / 更具包容性的位置标志。但老实说,服务器的答案可能是最好的。你是如何处理虚拟主机的?【参考方案4】:

这个问题更适合 serverfault.com 网站。

重定向到 http 的更好方法:

server 
   listen 443;
   return 301 http://$host$request_uri;

这避免了重写中的“if”子句和正则表达式,它们是迄今为止其他解决方案的特征。两者都对性能有影响,但在实践中,您必须拥有相当多的流量才有意义。

根据您的设置,您可能还想在 listen 子句中指定一个 ip,或许还需要在上面的 servername 子句中指定一个。照原样,它将适用于所有域名的所有端口 443 请求。您通常希望每个域都有一个带有 https 的 IP,因此大多数情况下将上述内容与 IP 绑定比将其与域名绑定更重要,但也有一些变化,例如所有域都是一个域的子域。

编辑:TLS 现在已接近通用,并且使用它的服务器名称识别 (SNI),它允许多个域上的 HTTPS 站点共享一个 IP。有一篇很好的文章here

【讨论】:

【参考方案5】:

这对我有帮助:

server 
    listen 443;
    server_name server.org www.server.org;
    rewrite ^ http://$server_name$request_uri? permanent;

【讨论】:

【参考方案6】:

不是一个合适的解决方案,但我能够通过使用 Cloudflare 解决我的用例,它透明地为我处理 SSL。

【讨论】:

【参考方案7】:

我上面的帖子已经解释了唯一简单的规则:

server 
    listen ip:443;
    server_name www.example.com;
    rewrite ^(.*) http://$host$1 permanent;

【讨论】:

【参考方案8】:
server
  listen 80;
  listen [::]:80;

  server_name mywebsite.com ;
  return 301 https://$host$request_uri;

插入此代码后,HTTP 默认服务器的所有流量都会重定向到 HTTPS。

【讨论】:

以上是关于如何在 NGINX 上将 HTTPS 重定向到 HTTP?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Node JS 上将 http 重定向到 https

如何在 WebSphere Application Server v9 上将 URL http 重定向到 https

如何在 ELB 应用程序负载均衡器上将 HTTPS 重定向到 HTTP

在 Flask+Heroku 上将 HTTP 重定向到 HTTPS

使用 IIS 在 ELB 上将 http 重定向到 https 的最佳方法

在没有 ServerName 的默认虚拟主机上将 HTTP 重定向到 HTTPS