如何在nginx中强制或重定向到SSL?


222

我在一个子域上有一个注册页面,例如: https://signup.example.com

它只能通过HTTPS进行访问,但是我担心人们可能会通过HTTP偶然发现它并得到404。

我在nginx中的html / server块如下所示:

html {
  server {
    listen 443;
    server_name signup.example.com;

    ssl                        on;
    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    ssl_session_timeout 30m;

    location / {
      root /path/to/my/rails/app/public;
      index index.html;
        passenger_enabled on;
    }
  }
}

我可以添加些什么,以便那些要http://signup.example.com重定向到的人https://signup.example.com?(仅供参考,我知道有些Rails插件可以强制执行,SSL但希望避免这种情况)


Answers:


145

根据nginx陷阱,最好$request_uri改用不必要的捕获。在这种情况下,请添加问号以防止Nginx将任何查询参数加倍。

server {
    listen      80;
    server_name signup.mysite.com;
    rewrite     ^   https://$server_name$request_uri? permanent;
}

68
或者,根据您链接的网站,“更好”return 301 http://domain.com$request_uri;
nh2 2012年

13
一则评论。$ server_name $获取第一个server_name变量。因此,如果您的配置中没有非FQN名称,请注意这一点
EngineerDave

2
@ nh2这是文档错误的另一种情况,因为return 301...在重写方法实际有效的情况下,使用会导致“重定向过多”错误。
Mike Bethany

1
现在已记录为“也很糟糕”。@MikeBethany return 301确实可以工作,除非(我猜)您通过侦听两个端口触发了它以获取正确的URL(例如配置触发问题的示例:采用serverfault.com/a/474345/29689的第一个答案,并省略if )。
Blaisorblade

1
我想知道这些年来发生了什么变化以及其他答案是否更好:serverfault.com/a/337893/119666
Ryan

256

官方方法中描述的最佳方法是使用return指令:

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

5
最短的答案,并且在我的情况下效果很好
mateusz.fiolka 2012年

1
通常建议这样做,因为它会返回一个301 Moved Permanently(您的链接已永久移动)并进行重写
sgb 2014年

1
即使您进行了设置,这也不起作用,因为它会导致“重定向过多”错误proxy_set_header X-Forwarded-Proto https;
Mike Bethany 2015年

1
@MikeBethany您是否listen 443;在同一块中定义?
乔B

2
这应该是公认的答案。
sjas 16/09/13

119

如果要将其全部保留在一个服务器块中,这是正确且最有效的方法:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($scheme = http) {
        return 301 https://$server_name$request_uri;
    }
}

上面的所有其他内容,使用“ rewrite”或“ if ssl_protocol”等将变得越来越慢。

这是相同的,但是效率更高,通过仅在http协议上运行重写,它避免了必须在每个请求中检查$ scheme变量。但是说真的,这是一件很小的事情,您不需要将它们分开。

server {
    listen   80;
    listen   [::]:80;

    server_name www.example.com;

    return 301 https://$server_name$request_uri;
}
server {
    listen   443 default_server ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;
}

8
太好了,即使这个答案是正确的,一些胆小鬼还是拒绝了这个答案,却没有说出原因。也许那些“如果是邪恶的”信徒中的另一个。如果您不愿阅读有关If的Nginx文档,就会知道IfIsNOTEvil,只是CERTAIN在location {}上下文中使用了它,我们在这里都不会做。我的回答绝对是正确的做事方式!
DELETEDACC 2013年

2
我没有对此表示拒绝,但我想指出的是,在最新版本中,默认值已更改为“ default_server”。
13年

如果第二种解决方案效率更高,则第一种解决方案可能不是最有效的。您甚至描述了为什么不应该在其中使用if:“它避免了每次请求都必须检查$ scheme变量”。不使用ifs的意义不仅在于性能,还在于声明性而非强制性。
pepkin88

if +1($ scheme = http)
Fernando Kosh

如其他答案中所述,应在此处使用$ host。
Artem Russakovskii

56

如果使用新的双重HTTP和HTTPS服务器定义,则可以使用以下内容:

server {
    listen   80;
    listen   [::]:80;
    listen   443 default ssl;

    server_name www.example.com;

    ssl_certificate        /path/to/my/cert;
    ssl_certificate_key  /path/to/my/key;

    if ($ssl_protocol = "") {
       rewrite ^   https://$server_name$request_uri? permanent;
    }
}

这似乎对我有用,并且不会引起重定向循环。

编辑:

替换为:

rewrite ^/(.*) https://$server_name/$1 permanent;

与Pratik的重写行。


2
@DavidPashley,您的解决方案对我而言就像一个魅力。谢谢
Jayesh Gopalan

1
If you are using the new dual HTTP and HTTPS server definition那么您应该将其分开。
VBart 2013年

2
优雅而完美的作品!
jacktrade

2
这是唯一适用于我的Laravel / Homestead Nginx配置的解决方案。
贾里德Eitnier

1
重写行也应该是这样,return 301 https://$server_name$request_uri;因为这是首选方法。
Jared Eitnier 2015年

27

另一个变体,它保留Host:请求标头,并在nginx陷阱上遵循“ GOOD”示例:

server {
    listen   10.0.0.134:80 default_server;

    server_name  site1;
    server_name  site2;
    server_name  10.0.0.134;

    return 301 https://$host$request_uri;
}

这是结果。请注意,使用$server_name代替$host总是会重定向到https://site1

# curl -Is http://site1/ | grep Location
Location: https://site1/

# curl -Is http://site2/ | grep Location
Location: https://site2/


# curl -Is http://site1/foo/bar | grep Location
Location: https://site1/foo/bar

# curl -Is http://site1/foo/bar?baz=qux | grep Location
Location: https://site1/foo/bar?baz=qux

Note that using $server_name instead of $host would always redirect to https://site1那不是为了什么$request_uri吗?
尔根·保罗(

2
$request_uri不包含主机名或域名。换句话说,它始终以“ /”字符开头。
彼得

2
迄今为止最好的答案。
Ashesh

3
我不确定为什么这个答案投票率太低。这是唯一值得使用的工具。
zopieux

2
不能相信这么多人使用$ server_name这是正确的方法
Greg Ennis

3

确保在任何cookie上设置“安全”,否则它们将在HTTP请求上发送,并可能被Firesheep之类的工具捕获。


1
server {
    listen x.x.x.x:80;

    server_name domain.tld;
    server_name www.domian.tld;
    server_name ipv4.domain.tld;

    rewrite     ^   https://$server_name$request_uri? permanent;
}

我认为这更好。xxxx是服务器的IP。如果您使用的是Plesk 12,则可以通过更改所需域的目录“ /var/www/vhosts/system/domain.tld/conf”中的“ nginx.conf”文件来实现。保存配置后,请不要忘记重启nginx服务。


rewrite ^ https://$host$request_uri? permanent; 可能是更好的解决方案,因为您可能在虚拟主机上有多个服务器名称

0

我认为这是最简单的解决方案。仅将非HTTPS和非WWW流量都强制为HTTPS和www。

server {
    listen 80;
    listen 443 ssl;

    server_name domain.tld www.domain.tld;

    # global HTTP handler
    if ($scheme = http) {
        return 301 https://www.domain.tld$request_uri;
    }

    # global non-WWW HTTPS handler
    if ($http_host = domain.tld) {
        return 303 https://www.domain.tld$request_uri;
    }
}

编辑-2018年4月:可以在我的文章中找到没有IF的解决方案:https : //stackoverflow.com/a/36777526/6076984


1
在Nginx世界中,IF条件是否被认为是邪恶的和低效的?
PKHunter

是的,一般而言。但是对于这种简单的检查,我猜不会。我确实有一个适当的配置文件,尽管其中包含更多的代码编写,但完全避免了IF。
stamster

Google建议使用301而不是303。来源:support.google.com/webmasters/answer/6073543?
hl = zh

@DylanHunt-我只剩下303进行测试,请注意第一个处理程序设置为301,只有第二个我忘记更改了:)另外,没有IF的解决方案:stackoverflow.com/a/36777526/6076984
stamster
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.