0

I need my Nginx setup to redirect users to www.example.com if they type example.com in the browser. The reason is that our SEO consultant said there should be only one preferred domain, otherwise Google sees it as content duplication. Anyway . . .

So the point is, I also have SSL from Letsencrypt set up on the server, but I'm not able to achieve the redirect from example.com to www.example.com (the server accepts both the versions). Here's the configuration I'm using:

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

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

    server_name example.com www.example.com;
    root /home/my_site;
    index index.php index.html index.htm;

    # for letsencrypt
    location ~ /.well-known {
        allow all;
    }   

    location / { 
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }   

    error_page 404 /404.html;

    error_page 500 502 503 504 /50x.html;

    location = /50x.html {
        root /usr/share/nginx/html;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

==== Update ====

I now changed my configuration to as advised by Tim (and I always to nginx -t and restart) in one of the answers to the following:

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

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name example.com;
    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;
    return 301 https://www.example.com$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name www.example.com;
    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

    root /home/ankush/wp_ankushthakur;
    index index.php index.html index.htm;

    # for letsencrypt
    location ~ /.well-known {
        allow all;
    }   

    location / {
        try_files $uri $uri/ /index.php?q=$uri&$args;
    }

    error_page 404 /404.html;

    error_page 500 502 503 504 /50x.html;

    location = /50x.html {
        root /usr/share/nginx/html;
    }

    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }
}

Here's the output of curl -k and access logs for all of the variations (I didn't build Nginx from source because I'm hoping for a simpler solution and don't want to mess up the server):

curl -k http://example.com
Curl output: 301 - Moved permanently
Access logs: "GET / HTTP/1.1" 301 194 "-" "curl/7.47.0"

curl -k http://www.example.com
Curl output: 301 - Moved permanently
Access logs: "GET / HTTP/1.1" 301 194 "-" "curl/7.47.0"

curl -k https://example.com
Curl output: 301 - Moved permanently
Access logs: "GET / HTTP/1.1" 301 194 "-" "curl/7.47.0"

curl -k https://www.example.com
Curl output: <Blank>
Access logs: "GET / HTTP/1.1" 301 5 "-" "curl/7.47.0"

Notice the last section, where the CURL output is blank and the access logs still give a permanent redirect.

Funnily enough, if I comment out the second server block and then restart Nginx, I end up with the opposite effect of what I wanted: www redirects to non-www! I'm surprised that's happening, because the HTTPS version of www.example.com isn't mentioned anywhere in this (third) version of the config.

3
  • 1
    What happens when "you are not able to achieve the redirect"? That doesn't help to understand what is wrong. Jan 22, 2017 at 8:05
  • @TeroKilkanen Thanks for pointing out. What's happening is that the server accepts both example.com and example.com.
    – ankush981
    Jan 22, 2017 at 11:55
  • The way it's set up it forwards all http requests to https, but the main https server block is accepting www and non-www. You need an https non-www block, as I've said in my answer below.
    – Tim
    Jan 22, 2017 at 18:08

3 Answers 3

0

It's probably because you are redirecting only on HTTP, but not on HTTPS. You should add a HTTPS to your redirecting vhost, and leave there only example.com name.

Furthermore, what you're doing is the opposite to what people actually do nowdays - the common approach is to bury the legacy www prefixes from the old era and use only simple domain names, without the www.

6
  • An example would've been really helpful, but I'll try doing it on my own. Yes, it's weird, but it's not a decision I make. :/ So, is it perfectly fine for a website today to not have a www subdomain?
    – ankush981
    Jan 22, 2017 at 11:56
  • Here's what I tried pastebin.com/5uSk4ZFt but now there's a circular redirect: example.com redirects to example.com and then example.com redirects to example.com. Not sure why. :|
    – ankush981
    Jan 22, 2017 at 12:07
  • Your configuration looks ok to me. Have you restarted Nginx? Post access logs, preferably edit your question. Server Fault prefers that all material stays on the site, that way people looking at it in future can see all the details, and other websites can disappear any time. I've updated my answer below and expanded it.
    – Tim
    Jan 22, 2017 at 18:54
  • @Tim Sure, I'll do that, though it will change the original question.
    – ankush981
    Jan 23, 2017 at 3:26
  • Your config seems to be fine indeed. Not sure about these includes you have. Plus, the redirection can also be performed by the code you are running. From your config it doesn't seems like you have a cyclic redirection.
    – drookie
    Jan 23, 2017 at 5:56
0

The key here is that you need to deal with four URLS - http and https versions of the www and non-www domains. Your problem is you're forwarding the http version of the www and non-www domain to the https://www domain, but your main server block is listening for both https://example.com and https://www.example.com

All you need to do is create a separate server block to forward the https://example.com to the https://www.example.com server. You need to include the https setup in this.

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  include snippets/ssl-example.com.conf;
  include snippets/ssl-params.conf;

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

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  include snippets/ssl-example.com.conf;
  include snippets/ssl-params.conf;

  server_name  www.example.com;

  # Main server block for main server continues
}

Standard Example

I have a tutorial with downloadable config files. A standard example is below.

If this doesn't work for you curl each variation (http and https, www and non-www) with the -k option to show headers and edit your question to include them.

# Main server
server {
  server_name www.example.com;
  listen 443 ssl http2;
  # etc, add all locations, call PHP or servers, etc
}


# Forward http requests to https www server
server {
  listen       80;
  server_name  example.com www.example.com;
  return       301 https://www.example.com$request_uri;
}

# Forward https non-www requests to the https www server
# Requires https setup for this server
server {
  listen 443 ssl http2;
  server_name example.com;

  ssl_certificate /var/lib/acme/certs/***CERT_DIRECTORY/fullchain;
  ssl_certificate_key /var/lib/acme/certs/***CERT_DIRECTORY/privkey;

  # Set up preferred protocols and ciphers. TLS1.2 is required for HTTP/2
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

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

Problem Solving The best way to diagnose problems are:

  • use "curl -k" (show headers) on each of the domain variants, in conjuction with the access log. The http status codes returned tell you what's going on. 200 is the page, 301 is permanent redirect, 302 is temporary redirect
  • Ensure Nginx has the headers_more module, which you can do by building Nginx from source, which is quite easy. This allows you to add https headers to the response. This is a great diagnostic tool. You can use statements like this to work out what blocks are being executed

add_header Z_DEBUG "location_name_or_message";

4
  • Thank you, but I'm a noob and feel overwhelmed at the complexity of the config files on your website! :P Would it be possible for you to point out what's wrong/missing in my configuration? I don't need caching and all that -- just a simple redirect. Specifically, I don't understand why we need two SSL server sections.
    – ankush981
    Jan 22, 2017 at 5:47
  • Your forwarding from http to https looks fine. You haven't set up forwarding from example.com to example.com. The second SSL section I provided is to forward from example.com to example.com, you have to set up SSL properly. You're serving the same content on both www and non-www. You need to edit your question to clearly describe and demonstrate the problem - "curl -k" of each of the four variations - http and https, example.com and www.example.com. Include applicable access and error logs.
    – Tim
    Jan 22, 2017 at 6:49
  • Makes sense, but I find it odd in your first SSL block, both www.example.com and example.com are mentioned. Shouldn't it be just www.example.com? When I tried to edit my Nginx conf file like this (see pastebin.com/zeMgN7ws) I got the error: conflicting server name "example.com" on 0.0.0.0:443, ignored. Please help me understand this and then I'll do the curl output for sure!
    – ankush981
    Jan 22, 2017 at 12:23
  • 1
    Well spotted, that was a typo that's also present in my tutorial. I'll fix it up.
    – Tim
    Jan 22, 2017 at 18:07
0

I was finally able to convince our SEO person to consider the non-www domain as primary. The configuration that worked to redirect www to non-www was as below. Although my attempt for achieving the reverse had a similar configuration, I'm not sure what was preventing it.

server {
    listen 80; 
    listen [::]:80;

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

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

    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

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

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

    include snippets/ssl-example.com.conf;
    include snippets/ssl-params.conf;

    server_name example.com;

    root /home/mysite;
    index index.php;

    location ~ /.well-known {
        allow all;
    }

    location / {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        try_files $uri $uri/ /index.php?$query_string;
        set $path_info $fastcgi_path_info;
        fastcgi_param PATH_INFO $path_info;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root/$fastcgi_script_name;
    }
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .