2

I'm trying to get nginx to redirect every request it receives from any domain by either adding or removing a www. prefix.

This is for a little redirect service. If you point www.any.domain to it, it should redirect www.any.domain/* to any.domain/*. AND if you point any.domain to it, it should redirect any.domain/* to www.any.domain/*.

This won't cause a redirect loop in practise because only one of the two domains (any.domain or www.any.domain) would be pointing to this service. The other to your website on another server.

This should work with any domain. Thousands of different ones, so it has to have a regex wildcard match. The fact that anything on the internet could be pointed to it isn't nginx's concern because that will get handled at a layer above/before it. Every request with an HTTP_HOST header will either have a www or not and in both cases it should be redirected, so the server should redirect all requests it receives.

The case where a domain doesn't start with www. is easy enough, but I can't figure out how to capture the cases where it DOES start with www. and then only use the rest of the domain.

I've tried searching, but a) just about no one redirects both ways at the same time, b) most people only care about one domain, so they hardcode it and c) once you get past that people tend to chant "IF IS EVIL". So that doesn't help much.

This is what I have at the moment:

http {
  server {
    listen 80 default_server;
    server_name _;

    # add www. to URLs that don't start with it.
    if ($host !~* ^www\.) {
      rewrite ^(.*)$ http://www.$host$1 permanent;
    }

    # but what about URLs that DO start with www.? How do I remove it?
  }
}

This is for use with setting up DNS for your site. If you want your site served at mydomain.com, you point mydomain.com to your site and www.mydomain.com to this redirector. If you want your site served at www.mydomain.com, you point www.mydomain.com to your site and mydomain.com to this redirector.

  • I don't get how to determine whether domain must have www or not? – Alexey Ten Nov 17 '14 at 14:08
  • If it has www., then it shouldn't have it. If it doesn't, then it should. Everything sent to the server's ip address is supposed to go to the other domain. (So obviously I wouldn't point both www.domain.com and domain.com to the same server at the same time. This relies on only one of them pointing to it otherwise you get a redirect loop.) – Le Roux Bodenstein Nov 17 '14 at 18:34
2

Use two server blocks

That's actually in the docs:

A named regular expression capture can be used later as a variable:

server {
    server_name   ~^(www\.)?(?<domain>.+)$;

    location / {
        root   /sites/$domain;
    }
}

Applied to the use case in the question, the server block required is therefore:

server {
    server_name   ~^(www\.)(?<domain>.+)$;
    return 301 $scheme://$domain$request_uri;
}

Putting it all together

The complete server configs required for the question would be:

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

server {
    listen 80 default_server;
    return 301 $scheme://www.$host$request_uri;
}

In this way the config is very simple, and there's no usage of if (which generally speaking should be avoided).

  • Thanks. This seems like a cleaner solution than what I came up with. By the way: Will only the first server block that matches be used or is it the return that causes it to not fall through to the second one? I always find it tricky in nginx configuration to know in advance if it will stop or fall through and continue executing. – Le Roux Bodenstein Nov 17 '14 at 13:34
  • nginx will only use one server block - if there is no server_name match it'll use the default server (which if it's not explicitly defined will be "the first one") - here's an example of what to expect. – AD7six Nov 17 '14 at 13:41
  • Now, fix the case where someone visits the server by IP address. :) – Michael Hampton Nov 18 '14 at 8:59
  • @MichaelHampton That's pretty easy, since in that case the request can be handled using the IP address as the server name i.e. server { server_name 10.10.10.10; ...}. But it's not in the question =). – AD7six Nov 18 '14 at 12:37
  • @MichaelHampton Then it should do whatever it can to make the requesting client believe it is running some ancient unpatched version of PhPBB with the default admin login credentials ;) – Le Roux Bodenstein Nov 18 '14 at 13:33
1

Similar, and not tested but something along these lines :

http {
  server {
    listen 80 default_server;
    server_name _;

    # redirect www.foo.com -> foo.com    
    if ($host ~* ^www\.) {
      rewrite ^www\.(.*)$ http://$1 permanent;
    }
  }
}
  • ^www\.(.*)$ will match the request path, not the host. – Le Roux Bodenstein Nov 17 '14 at 12:55
0

Looks like I found a solution at https://blog.engineyard.com/2011/useful-rewrites-for-nginx

http {
    server {
        listen 80 default_server;
        server_name _;

        if ($host !~* ^www\.) {
            rewrite ^(.*)$ $scheme://www.$host$1 permanent;
            break;
        }

        if ($host ~* ^www\.(.*)) {
            set $host_without_www $1;
            rewrite ^(.*)$ $scheme://$host_without_www$1 permanent;
            break;
        }
    }
}

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.