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


我在子域上有一个注册页面,如: 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 但希望避免这种情况)


210
2018-03-22 18:45




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


答案:


根据 nginx陷阱,使用省略不必要的捕获稍微好一些 $request_uri 代替。在这种情况下,附加一个问号以防止nginx加倍任何查询参数。

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

137
2018-03-22 19:22



或者,根据您链接的网站, “更好”: return 301 http://domain.com$request_uri; - nh2
一条评论。 $ server_name $获取第一个server_name变量。如果配置中包含非FQN名称,请注意这一点 - engineerDave
@ nh2这是自使用以来文档错误的另一种情况 return 301... 重写方法实际工作时导致“太多重定向”错误。 - Mike Bethany
现在记录为“也很糟糕”。 @MikeBethany return 301 确实有效,除非(我猜)你正在触发它 也 通过侦听两个端口来获取正确的URL(配置示例。触发问题:take serverfault.com/a/474345/29689's 第一个回答并省略if)。 - Blaisorblade
我想知道这些年来发生了什么变化,以及其他答案是否更好: serverfault.com/a/337893/119666 - Ryan


它描述的最佳方式 官方的操作方法 是通过使用 return 指示:

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

237
2017-09-03 23:50



最短的答案,并在我的情况下完美地工作 - mateusz.fiolka
通常建议这样做,因为它返回一个 301 Moved Permanently (您的链接已永久移动)以及重写 - sgb
这不起作用,因为它会导致“太多重定向”错误,即使您已设置 proxy_set_header X-Forwarded-Proto https; - Mike Bethany
@MikeBethany你定义 listen 443; 在同一个街区? - Joe B
这应该是公认的答案。 - sjas


如果要将所有内容保存在一个服务器块中,这是正确且最有效的方法:

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;
    }
}

上面的其他所有内容,使用“重写”或“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;
}

108
2018-01-31 20:43



太棒了,一些懦夫投了这个答案而没有说明为什么,即使这个答案是正确的。也许是另一个“如果是邪恶的”邪教徒。如果您懒得阅读有关If的Nginx文档,您将知道IfIsNOTEvil,只是在某个位置{}上下文中使用它,我们在此处都没有。我的回答绝对是正确的做事方式! - DELETEDACC
我没有对此进行投票,但我想指出在最新版本中默认值已更改为“default_server”。 - spuder
如果第二种解决方案更有效,则第一种解决方案不是最有效的。你甚至描述了为什么你不应该使用if:“它避免了必须在每个请求上检查$ scheme变量”。不使用ifs的关键不仅在于性能,还在于声明,而不是必要。 - pepkin88
+1的if($ scheme = http) - Fernando Kosh
应该在这里使用$ host,如其他答案所述。 - Artem Russakovskii


如果您使用新的双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的重写线。


56
2017-08-08 11:12



@DavidPashley你的解​​决方案对我来说就像一个魅力。谢谢 - Jayesh Gopalan
If you are using the new dual HTTP and HTTPS server definition 那么你应该分开它。 - VBart
优雅,完美! - jipipayo
这是我使用Laravel / Homestead Nginx配置的唯一解决方案。 - Jared Eitnier
重写线也应该是 return 301 https://$server_name$request_uri; 因为这是首选方法。 - Jared Eitnier


另一种变体,保留Host:request标头并遵循“GOOD”示例 nginx陷阱

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

27
2018-04-11 12:20



Note that using $server_name instead of $host would always redirect to https://site1 不是那样的 $request_uri 是为了? - Jürgen Paul
$request_uri 不包含主机或域名。换句话说,它始终以“/”字符开头。 - Peter
到目前为止最佳答案。 - Ashesh
我不确定为什么这个答案的票数如此之低。这是唯一值得使用的。 - zopieux
不能相信很多人使用$ server_name这是正确的方法 - Greg Ennis


确保在任何cookie上设置“安全”,否则它们将通过HTTP请求发送,并可以通过Firesheep等工具获取。


3
2018-03-23 00:40





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;
}

我觉得这样做效果更好。 x.x.x.x是指服务器的IP。如果您正在使用Plesk 12,则可以通过更改目录“/var/www/vhosts/system/domain.tld/conf”中的“nginx.conf”文件来执行此操作,无论您想要哪个域。保存配置后,不要忘记重新启动nginx服务。


1
2017-08-23 19:40



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


我认为这是最简单的解决方案。强制将非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


0
2018-04-21 18:30



在nginx世界中,IF条件是否被视为邪恶和低效? - PKHunter
是的,一般而言。但是对于这个简单的检查,我猜不会。我确实有一个正确的配置文件,但是需要更多代码编写,但完全避免使用IF。 - stamster
Google建议使用301而不是303.来源: support.google.com/webmasters/answer/6073543?hl=en - dylanh724
@DylanHunt - 我只留下303进行测试,记下第一个处理程序设置为301,只有第二个我忘了改变:)另外,解决方案没有IF: stackoverflow.com/a/36777526/6076984 - stamster