Nginx 从 http www 重定向到 https 非 www

Nginx 从 http www 重定向到 https 非 www

我正在尝试重定向http://www.example.com在 Nginx 中。只有非 www 版本安装了证书。我有两个 301 重定向规则,顺序如下:

1)来自http://example.com

server {
  listen 80;
  listen 443 ssl;
  server_name example.com;

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

2)来自http://WWW.example.comhttp://example.com

server {
  listen 80;
  server_name www.example.com;
  return 301 $scheme://example.com$request_uri;
}

当我从终端通过 curl 请求 www.example.com 时,我看到:

HTTP/1.1 301 Moved Permanently
Location: http://example.com/

这正是我期望发生的事情。但是,当我从浏览器执行相同操作时,它会将我重定向到并引发证书错误。

我通过将证书添加到 www 版本来修复此问题,但我想知道为什么没有它就不起作用?为什么 curl 会给我不同的结果?

以防万一我使用的 SSL 参数:

ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;
ssl_dhparam /etc/nginx/pki/dhparam.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
resolver 8.8.8.8;
ssl_stapling on;
add_header Strict-Transport-Security "max-age=31536000";

答案1

为了便于讨论,我建议你将所有 nginx 逻辑划分为主节点(example.conf 配置)和“卫星节点”(www_example.conf 等)。卫星节点不应包含庞大的逻辑。

如果在您的主节点中将所有 HTTP 流量重定向到 HTTPS,- 我建议也在卫星中这样做 - 这样会更快一些:

www_示例.conf

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

我已$scheme改为https

那么,如果我正确理解了你的话——你有两个问题。

1.CURL 不遵循您的重定向。默认情况下,它不会这样做,您应该传递-L

curl "http://www.example.com" -L

2.另一个问题在于 nginx 如何解析 SSL 连接。SSL 不会检查“服务器配置”层。它会在服务器接收连接数据包并开始握手操作之前进行大量检查 - 因此,在此检查期间不会使用带有 server_name 的配置。

当您的服务器接收到 SSL 握手的传入连接时,它会检索当前正在监听的第一个配置ip:port,并尝试使用 SSL 密钥、链和证书进行握手。

摘要:

如果你想通过“https://www.example.com“并从中检索答案”https://example.com“——您应该拥有两台 SSL 配置的服务器。

如果你想通过“http://www.example.com“并从中检索答案”https://example.com“您应该确保握手仅在 example.com 上有效,在进入此域之前无效。您可以通过wget输出检查服务器如何处理“解析步骤” - 这是最简单的方法。

您可以在这里找到我用来测试您的问题的配置:

server {
    listen 80;
    listen 443 ssl;
    server_name example.com;

    ssl on;
    ssl_certificate /path/to.crt;
    ssl_certificate_key /path/to.key;

    ssl_protocols        TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers          ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4;
    ssl_prefer_server_ciphers on;


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

    location / {
        add_header Content-type text/plain;
        return 200 "OK";
    }
}

server {
    listen 80;
    server_name www.example.com;

    return 301 https://example.com$request_uri;
}

我添加了用于调试目的的location部分。return 200 "OK"

然后在 CMD 中:

$ curl "http://www.example.com" -L
OK

$ wget "http://www.example.com" -O /dev/null
--2016-11-24 15:54:33--  http://www.example.com/
Resolving www.example.com (www.example.com)... 127.0.0.1
Connecting to www.example.com (www.example.com)|127.0.0.1|:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://example.com/ [following]
--2016-11-24 15:54:33--  https://example.com/
Resolving example.com (example.com)... 127.0.0.1
Connecting to example.com (example.com)|127.0.0.1|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2 [application/octet-stream]

在最后的输出中,您可以发现“解决步骤”正常工作。

在浏览器中一切也都能正常工作。

答案2

如果该www.example.com网站之前已经提供过Strict-Transport-Security标头(或includeSubdomains上的选项example.com),那么浏览器将会记住(很长时间 - 例如 31536000 秒)并且始终在访问 之前提升http到 的连接。httpswww.example.com

答案3

另一种解决方案是使用地图模块。

在这种情况下,只需要一个 http 服务器指令(尽管这可以通过硬编码 host inreturn子句来实现)。此外,这允许在任何虚拟主机和自动化工具中重用从 map 模块获取的 $root_domain 变量。而且,也不需要 if 指令。

因此,为了获得“干净”的主机名:

map $host $root_domain {
  default none;
  ~*^www\.(.*)$ $1;
  ~*(.*) $1;
}

在 nginx.conf 中

然后对于 HTTP:

server {
  listen 80;
  server_name .example.org;
  return 301 https://$root_domain$request_uri;
}

和 HTTPS:

server {
  listen 443 ssl http2;
  ssl_certificate ...;
  ssl_certificate_key  ...;
  server_name www.example.org;
  return 301 https://$domain_root$request_uri;
}

相关内容