在Nginx中,如何在维护子域的同时将所有http请求重写为https?


508

我想将Web服务器上的所有http请求重写为https请求,我从以下内容开始:

服务器{
    听80;

    位置 / {
      重写^(。*)https://mysite.com$1永久;
    }
...


一个问题是,这剥夺了任何子域信息(例如,node1.mysite.com / folder),我该如何重写以上内容以将所有内容重新路由到https并维护该子域?


2
请考虑将“接受的答案”移至serverfault.com/a/171238/90758。那是正确的。
olafure

只需使用$ server_name代替硬编码的mysite.com
Fedir RYKHTIK 2014年

Answers:


748

Nginx新版本中的正确方法

事实证明,我对这个问题的第一个答案在某些时候是正确的,但它又变成了另一个陷阱-要保持最新状态,请检查对改写陷阱进行纳税

许多SE用户已对我进行了更正,因此功劳很大,但更重要的是,这里是正确的代码:

server {
       listen         80;
       server_name    my.domain.com;
       return         301 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000" always; 

       [....]
}

3
但是,您将必须逐域进行此操作-不?如果您想将其应用于服务器上的每个域怎么办?
JM4

30
@ JM4:如果您在重写中使用$ host $而不是server_name并将default_server添加到listen指令,则它将对您服务器上的每个域有效。
2013年

5
重要的是要提到301存储在本地缓存中,没有到期日期。配置更改时不是很有用
Trefex

9
@everyone使用307重定向保留POST内容。
Mahmoud Al-Qudsi

10
请注意,如果您正在使用子域$host$server_name则必须使用代替。
fish鱼

276

注意:https://serverfault.com/a/401632/3641提供了执行此操作的最佳方法-但在此重复:

server {
    listen         80;
    return 301 https://$host$request_uri;
}

在最简单的情况下,您的主机将被固定为您要将其发送到的服务-这将执行301重定向到浏览器,并且浏览器URL将相应更新。

下面是先前的答案,由于正则表达式效率低下,简单的301非常好,如@kmindi所示

我一直在使用nginx 0.8.39及更高版本,并使用了以下内容:

 server {
       listen 80;
       rewrite ^(.*) https://$host$1 permanent;
 }

向客户端发送永久重定向。


15
我认为应该是80,因为它正在监听http,然后告诉客户端以https(443)的身份返回。
Michael Neale

3
这应该是最佳答案!
内森

3
这是最费力的答案。
案例

1
这是最简单的方法,但是最不安全-使用这种方法,您可以允许服务器将用户重定向到任何页面,而无需检查服务器是否允许使用该页面。如果您的服务器提供mydomain.co服务,恶意用户仍然可以使用您的服务器将用户重定向到mydomain.co之类的其他域,例如google.com。
Friedkiwi 2013年

10
@ cab0lt此处没有安全问题。提供重定向不会带来安全风险。如果有访问控制要求,则应在浏览器请求新URL的位置进行检查。浏览器不会仅基于重定向获得访问权限,也不需要重定向来请求新的URL。
mc0e 13-10-16,3

125

我认为最好和唯一的方法应该是使用HTTP 301移动永久重定向,如下所示:

server {
    listen         [::]:80;
    return 301 https://$host$request_uri;
}

已移动永久HTTP 301重定向也是最有效的,因为没有正则表达式来进行评估,根据已经提到pitfails


新的“ HTTP 308永久移动”保留了Request方法,并且受到主要浏览器的支持。例如,使用308可以防止浏览器将重定向方法的请求方法从POST更改GET为。


如果要保留主机名和子域,可以采用这种方法。

还可以工作,如果你没有DNS,因为我也使用本地。我要求提供,例如,http://192.168.0.100/index.php并将其重定向到https://192.168.0.100/index.php

listen [::]:80在主机上使用,因为我已将bindv6only设置为false,因此它也绑定到ipv4套接字。listen 80如果不想使用IPv6或将其绑定到其他位置,请将其更改为。

Saif Bechan的解决方案server_name在我的情况下使用localhost,但无法通过网络访问。

来自Michael Neale的解决方案很好,但是根据pitfails的说法,重定向301是一个更好的解决方案;)


不错,您尝试引用它,但是301在HTTPS上不起作用。
案例

5
什么不起作用?声明的服务器部分用于将未加密的HTTP(无s)流量永久重定向到加密的服务器(未列出侦听443(https)的部分)
kmindi 2012年

我检查了一下https和其他所有东西都可以很好地工作-@kmindi我参考了您的回答更新了我的答案-因为我认为这是正确的方法,并且这种方法不断出现!辛苦了
Michael Neale 2013年

使用域(非IP)请求时,除非我将'[::]:80'更改为'80',否则它不起作用。
约瑟夫·卢斯特

可能是预期的行为:trac.nginx.org/nginx/ticket/345。我更新了答案以描述监听选项。
kmindi 2013年

20

在服务器块内,您还可以执行以下操作:

# Force HTTPS connection. This rules is domain agnostic
if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
}

2
这种配置导致我的服务器产生了一个重定向循环
Corkscreewe

可能是因为存在其他重定向,或者您的网站/应用未启用https
Oriol 2015年

1
除此一项外,其他任何一项似乎均无用。使用nginx版本:nginx / 1.10.0(Ubuntu)
ThatGuy343 '16

为https:// $ host $ uri投票
AMB

4
如果您在负载均衡器后面,这就是方法!
安特旺(Antwan),2017年

17

上面的方法不适用于始终创建新的子域的情况。例如AAA.example.com BBB.example.com约有30个子域。

最终得到了一个可以使用以下内容的配置:

server {
  listen 80;
  server_name _;
  rewrite ^ https://$host$request_uri? permanent;
}
server {
  listen  443;
  server_name example.com;
  ssl on;
  ssl_certificate /etc/ssl/certs/myssl.crt;
  ssl_certificate_key /etc/ssl/private/myssl.key;
  ssl_prefer_server_ciphers       on;
# ...
# rest of config here
# ...
}

谢谢!301 https://*/在这里的其他答案中,nginx会过早返回或取消请求。server_name _;$host是奏效了答案。+1
Zamnuts 2013年

1
这是最优的!但是,我建议将其替换_为实际的域,例如,.domain.com我有两台服务器,而nginx意外将我的一台服务器定向到默认服务器。
zzz

1
这是唯一对我有用的答案,谢谢!
雪人

非常感谢您..我尝试了许多解决方案,但没有解决。这个解决方案很棒,并且对我有用。服务器名称 _; 这是什么意思。。我不明白。请给我解释一下。
Pavan Kumar”,

6

很久很久以前,我就正确答案发表了评论,但有一个非常重要的更正,但是我觉得有必要在自己的答案中突出此更正。如果您在任何时候都设置了不安全的HTTP并期望用户内容,具有表单,托管API或配置了任何网站,工具,应用程序或实用程序来与您的站点对话,则上述任何答案都不能安全使用。

POST对您的服务器进行请求时,会发生此问题。如果服务器使用纯30x重定向响应,则POST内容将丢失。发生的情况是浏览器/客户端将请求升级到SSL,但降级POSTGET请求。该POST参数将丢失和不正确的请求将您的服务器进行。

解决方案很简单。您需要使用HTTP 1.1 307重定向。RFC 7231 S6.4.7中对此进行了详细说明:

  Note: This status code is similar to 302 (Found), except that it
  does not allow changing the request method from POST to GET.  This
  specification defines no equivalent counterpart for 301 (Moved
  Permanently) ([RFC7238], however, defines the status code 308
  (Permanent Redirect) for this purpose).

该解决方案(从接受的解决方案改编而来)将307在您的重定向代码中使用:

server {
       listen         80;
       server_name    my.domain.com;
       return         307 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000"; 

       [....]
}


4

我在AWS ELB后面运行ngnix。ELB正在通过http与ngnix交谈。由于ELB无法将重定向发送到客户端,因此我检查X-Forwarded-Proto标头并重定向:

if ($http_x_forwarded_proto != 'https') {
    return 301 "https://www.exampl.com";
}

1

如果return 301 https://$host$request_uri;作为端口80上的默认响应,则您的服务器可能迟早会进入开放代理列表[1],并开始被滥用以将流量发送到Internet上的其他位置。如果您的日志中充满了诸如此类的消息,那么您就知道发生了这种情况:

42.232.104.114 - - [25/Mar/2018:04:50:49 +0000] "GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1" 301 185 "http://www.ioffer.com/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Hotbar 4.1.8.0; RogueCleaner; Alexa Toolbar)"

问题是,$host无论浏览器在HostHTTP的开头行中发送标题还是主机名,它都会回显任何内容,例如:

GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1

由于该问题,此处建议使用其他一些答案$server_name代替$host$server_name始终会评估server_name声明中的内容。但是,如果您在那里有多个子域或使用通配符,则将不起作用,因为它$server_name仅使用声明后的第一个条目server_name,更重要的是只会回显通配符(而不扩展它)。

那么如何在保持安全性的同时支持多个域呢?在我自己的系统上,我首先通过列出default_server不使用的块,$host然后列出了一个使用通配符的块来解决这个难题:

server {
  listen 80 default_server;
  server_name example.com;
  return 301 https://example.com$request_uri;
}
server {
  listen 80;
  server_name *.example.com;
  return 301 https://$host$request_uri;
}

(您也可以在第二个块中列出多个域。)

通过这种组合,不匹配的域将被重定向到硬编码的位置(始终为example.com),并且与您自己的域匹配的域将被定位到正确的位置。您的服务器不能用作开放代理,因此不会惹麻烦。

如果您感到讨厌,我想您还可以使该default_server区块与您的合法域名都不匹配,并提供令人反感的服务。。。。

[1]从技术上讲,“ proxy”是一个错误的词,因为您的服务器不会关闭并满足客户的请求,只是发送重定向,但我不确定哪个词是正确的。我也不确定目标是什么,但是它充满了噪音,消耗了CPU和带宽,因此您最好停止它。


0

似乎没人真的100%正确地做到了。要使端口80的请求到达整个Web服务器的443个等效端口,您需要使用listen指令,而不是server_name指令来指定全部捕获名称。另请参阅https://nginx.org/en/docs/http/request_processing.html

服务器{
    默认听80;
    听默认[::]:80;
      返回307 https:// $ host $ request_uri;
}
  • $ host捕获子域名。
  • 307和308包括POST和GET请求URI。
  • 307是临时的,经过全面测试后更改为永久308:

并确保检查/etc/nginx/conf.d/中已经存在的内容,因为我经常会遇到default.conf返回一些现有vhost的问题。我处理nginx问题的顺序始终是从移出默认文件开始,然后将其逐行注释掉,以查看出错的地方。


-1
rewrite ^!https https://$host$request_uri permanent;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.