我正在尝试重定向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.com到http://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
到 的连接。https
www.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;
}