2

I have a website running with a Python/Django/uWSGI/Nginx setup. I also use Certbot to enable https on my site. My redirects from non-www to www (e.g. "example.com" to "www.example.com") result in a "Bad Request (400)" message even though I couldn't spot any deviations from the Nginx/Certbot documentation. Here is the relevant part of my sites-available Nginx code:

server { listen 80; server_name example.com www.example.com; location = /favicon.ico { access_log off; log_not_found off; } location /static/ { root /home/myname/example; } location / { include uwsgi_params; uwsgi_pass unix:/run/uwsgi/activities.sock; } listen 443 ssl; # managed by Certbot ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; #managed by Certbot ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; #managed by Certbot include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot if ($scheme != "https") { return 301 https://$host$request_uri; } # managed by Certbot } 

I found a similar StackOverflow answer (Nginx: redirect non-www to www on https) but none of the solutions worked for me. I have SSL certificates for both example.com and www.example.com. I also tried creating a separate 443 ssl server block for example.com based on the comments in that answer but it didn't work either. My sites-available and sites-enabled code is the same.

What am I doing wrong?

2
  • What do you mean "not working"? Commented Jul 3, 2018 at 16:32
  • I get a "Bad Request (400)" error. I'll edit the original comment to include that. Commented Jul 3, 2018 at 16:44

3 Answers 3

2

It seems that server_name when translated to the $host variable selects the first in the list of server_name. Let me know if that works. I can't quite test this currently.

Try swapping server_name to server_name www.example.com example.com; as well as changing return 301 https://$host$request_uri; to return 301 https://$server_name$request_uri;

server { server_name www.example.com example.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl; # SSL CERT STUFF. server_name example.com; return 301 https://www.$server_name$request_uri; } server { listen 443 ssl; # SSL CERT STUFF. server_name www.example.com; # LOCATION STUFF } 
Sign up to request clarification or add additional context in comments.

6 Comments

That was a copy and paste error on my part. The actual server code has a closing bracket. Editing the question now...
I tested the changes above and it seems to work. As a matter of fact this will most likely not translate https:// example.com to https:// www.example.com. This should get you started possibly. Always remember to try in a clear cache browser.
Your solution solved half of the problem! http://example.com now redirects properly. However, https://example.com still returns the Bad Request (400) error. Do you have any recommendations as to how I can get the https non-www to www redirect working?
Okay I've updated again. The only possibly not working step might be the second return 301 https://www.$server_name$request_uri; might have to change to www.example.com
It works! I went with https://www.$server_name$request_uri. Thank you!
|
1

This is not an efficient configuration for Nginx request processing. It's messy, your if condition gets evaluated on every request and I don't see where your non www to www is even meant to happen.

I'd split http and https:

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

Thats all non https traffic taken care of in a single redirect. Now for the https:

server { listen 443 default_server ssl; server_name www.example.com; root # should be outside location blocks ideally ...... } 

The default server directive means this server will handle any requests which do not have a matching server configuration. If you don't want that then add example.com after www.example.com, not before it. Any requests ending up here will display the first entry in the client browser bar.

Based on your comments you might need to add a separate block for the other domain to avoid an SSL certificate mismatch. Try this:

server { listen 443 ssl; server_name example.com; ssl_certificate .....; ssl_certificate_key .....; return https://www.example.com$request_uri; } 

5 Comments

Thanks for the input! Your code is giving the same result as Mitchell Walls' solution: http://example.com now redirects properly but https://example.com still returns the Bad Request (400) error. Any ideas as to why this is happening?
Do you have a single SSL certificate with both example.com and www.example.com on, or 2 certificates?
I think I have a single SSL certificate with both domains on it.
I edited my answer with another server block for you to try adding, if you visit the site in chrome, click the padlock in address bar, click certificate, details then choose Subject Alternative Name if will list all domains the certificate covers
Thanks for the tips! I checked the SSL using Chrome and it's one certificate covering both. I wound up using Mitchell Walls' edited solution, which was very similar to yours. I created a separate block for the other domain like you said, but when I used default_server for the first 2 blocks the same error still occurred. When I left it out and went with Mitchell Walls' code it worked perfectly.
0

Although the OP has accept one of the answers as the solution, I just want to point out that it may not be the best practice.

The correct way is to use $host instead of $server_name (as per Mitchell Walls' example) or hardcoded www.exmple.com (as per miknik's example). Both results an extra 443 server directive that is not necessary and messy.

server { listen 80 default_server; server_name www.example.com example.com; root /var/www/html; # define your root directory here return 301 https://$host$request_uri; } server { listen 443 ssl; # SSL CERT STUFF. #server_name www.example.com; you don't need to specify again here # LOCATION STUFF } 

There is a difference between $host and $server_name:

  • $host contains "in this order of precedence: host name from the request line, or host name from the 'Host' request header field, or the server name matching a request".
  • $server_name contains the server_name of the virtual host which processed the request, as it was defined in the nginx configuration. If a server contains multiple server_names, only the first one will be present in this variable.

1 Comment

Awesome about the host thing. Just curious about your example how do you define the ssl cert for both example.com and www.example.com. The OP has two different certs for each. So wouldn't your example run into an error when going directly to example.com with https?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.