1

I'm trying to redirect http://www.example.com to in Nginx. Only non-www version has a certificate installed. I have two 301 redirection rules in that order:

1) From http://example.com to

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

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

2) From http://WWW.example.com to http://example.com

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

When I request www.example.com via curl from a terminal I see:

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

Which is what I expect to happen. However, when I do the same from a browser it redirects me to and throws a certificate error.

I fixed it by adding the certificate to www version but I wonder why it didn't work without it? And why curl gives me a different result?

Just in case the SSL params I use:

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";
  • Please, post full curl command you executed - it's not clear what you're waiting for – Victor Perov Nov 24 '16 at 12:59
  • All HTTPS connections perform their handshakes before doing anything else. If you make any config that includes ssl, you must include in the certificate and ssl configs or the browsers will throw up the warnings. – Paul Nov 24 '16 at 23:32
2

For cleared discussion I propose to you to divide your all nginx logic to main node (example.conf config) and "satellites" (www_example.conf, etc). Satellites should not has a huge logic.

If in your main node you are redirect all HTTP traffic to HTTPS, - I propose to do it in satellites too - it is a bit faster:

www_example.conf

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

I've changed $scheme to https.

Then, if I correctly understand you - you have two problems.

1. CURL does not follow your redirects. By default he does not do this, you should pass -L:

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

2. Another problem lies in how nginx parse SSL connections. SSL is not checking on "server config" layer. It is checking a much before it, when server receives connection data packets and starts handshake operation - so, your configs with server_name are not using during this checks.

When your server receives incoming connection for SSL-handshake, he retrieves first config that are listening on current ip:port and tries to make handshake with ssl key, chain & certificate.

Summaries:

If you want to pass "https://www.example.com" and retrieve answer from "https://example.com" - you should have both SSL-configured servers.

If you want to pass "http://www.example.com" and retrieve answer from "https://example.com" you should be sure, that handshaking working only on example.com, not before entering this domain. How your server process "resolving steps" you can check via wget output - it is a simplest way to do this.

Here you can find my config, I used for testing your problem:

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

I've added location section with return 200 "OK" for debug purpose.

Then in 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]

In last output you can find that "resolving steps" works correctly.

In browser all works correctly too.

  • How does nginx handle multiple certificates considering server_name is ignored during the handshake? Will it go through all certificates until finds the correct one? – csandanov Nov 28 '16 at 8:22
  • Nope, just first virtual host that meets ip & port criteria – Victor Perov Nov 28 '16 at 10:55
  • Then how does nginx support multiple certificates for different hosts? – csandanov Nov 29 '16 at 5:56
  • Each vhost with unique SSL key should bind to unique IP:port – Victor Perov Nov 29 '16 at 9:34
  • I have working nginx config where I use multiple domains with different SSL certificates, all of them use 443 port and it works fine though I use it as a reverse proxy. – csandanov Nov 29 '16 at 10:39
1

If the www.example.com site has been previously served with a Strict-Transport-Security header (or the includeSubdomains options on example.com) - the browser will remember (for a long time - e.g. 31536000 seconds) and will always promote http connections to https before even accessing www.example.com.

  • Yes, I have HSTS enabled but without includeSubdomains option. Thanks for noticing though. – csandanov Nov 28 '16 at 8:23
0

Another solution using map module.

In this case, only one server directive for http is needed (though, this can be achieved with hard-coded host in return clause). Furthermore, this allows to reuse obtained $root_domain variable from map module in any virtual host and automatization tools. And also, no if directive is needed as well.

So, for getting 'clean' host name:

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

In nginx.conf

And then for HTTP:

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

and HTTPS:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.