1

This question has been asked in various ways a million times. Still I can not find an answer that is generic enough to cover all cases.

All I want is to remove www from any incoming URL and I want that applied to all my subdomains. Here is my nginx config that is otherwise working fine:

# redirect HTTP to HTTPS, no www
server {
    listen      80;
    listen [::]:80;

    server_name ~^(?<www>www\.)(?<subdomain>.+)\.example\.com$;

    # redirect
    return 301 https://${subdomain}.example.com$request_uri;
}

#redirect HTTPS www to non-www
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name ~^(?<www>www\.)(?<subdomain>.+)\.example\.com$;

    # ssl config...

    # redirect
    return 301 https://${subdomain}.example.com$request_uri;
}

#default server
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;

    server_name example.com *.example.com;

    # other calls below this point include
    # root dir
    # ssl config
    # security headers
    # various location blocks, specific to my project
    # error page
    # error/access logs...
}

All these mappings are handled fine by the config above:

  • http://example.com -> https://example.com
  • http://client1.example.com -> https://client1.example.com
  • http://dev.client1.example.com -> https://dev.client1.example.com
  • http://stage.client1.example.com -> https://stage.client1.example.com

But these are NOT:

  • http://www.example.com -> https://example.com
  • http://www.client1.example.com -> https://client1.example.com
  • http://www.dev.client1.example.com -> https://dev.client1.example.com

Can you help me understand what I need to change in my config or how to debug this?

Dimitris
  • 13,480
  • 17
  • 74
  • 94
  • Your middle block will never match according to [these rules](http://nginx.org/en/docs/http/server_names.html). Your regular expression does not match `www.example.com`, but would match `www..example.com`. Do you have HSTS headers set? – Richard Smith Jan 20 '19 at 18:13
  • Thanks @RichardSmith, why would my middle block not match in case someone visits `https://www.subdomain.example.com`? Where would that get caught instead? On the top block there is no `https` and on the bottom block there is no `www`. I will look into the regex again but according to regex testers it matches fine. – Dimitris Jan 20 '19 at 22:53
  • 1
    *.example.com matches www.sub.example.com and takes precedence over regular expressions. – Richard Smith Jan 20 '19 at 23:26
  • Thank you for the explanation, makes total sense! I do have HSTS headers setup. How do you recommend I achieve what I need? Maybe an explicit `www.*` server that redirects to non-www? If do that without a regex then how can I extract the subdomain as I do on the first block? – Dimitris Jan 21 '19 at 00:48

1 Answers1

0

For the http block:

Due to HSTS, only serves clients visiting for the first time. So you could use a simple redirect and perform www. removal in the https blocks.

For example:

server {
    listen      80;
    listen [::]:80;
    return 301 https://$host$request_uri;
}

If you want to remove the www. prefix at this stage, you need a regular expression that will always match.

For example:

server {
    listen      80;
    listen [::]:80;
    server_name   ~^(www\.)?(?<domain>.+)$;
    return 301 https://$domain$request_uri;
}

See this document for details.


For the https blocks:

You need a regular expression to capture that part of the domain name after the www.. You have two options:

Use two server blocks (as you have now) but don't put a wildcard server_name statement in the second block. You could use another regular expression (e.g. server_name ~example\.com$;) or just no server_name at all and make it the default.

For example:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name   ~^www\.(?<domain>.+)$;
    # ssl config...
    return 301 https://$domain$request_uri;
}
server {
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;
    # no server_name statement needed
    ...
}

Or use a single server block and test the $http_host variable using an if statement.

For example:

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name  .example.com;

    if ($http_host ~ ~^www\.(.+)$) { return 301 https://$1$request_uri; }
    ...
}

See this caution on the use of if.

Richard Smith
  • 45,711
  • 6
  • 82
  • 81