Remove "www" and redirect to "https" with nginx
The best way to accomplish this is using three server blocks: one to redirect http to https, one to redirect the https www-name to no-www, and one to actually handle requests. The reason for using extra server blocks instead of ifs is that server selection is performed using a hash table, and is very fast. Using a server-level if means the if is run for every request, which is wasteful. Also, capturing the requested uri in the rewrite is wasteful, as nginx already has this information in the $uri and $request_uri variables (without and with query string, respectively).
server {
server_name www.example.com example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /path/to/server.cert;
ssl_certificate_key /path/to/server.key;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
ssl_certificate /path/to/server.cert;
ssl_certificate_key /path/to/server.key;
server_name example.com;
<locations for processing requests>
}
This works for me:
server {
listen 80;
server_name www.yourdomain.com yourdomain.com;
return 301 https://yourdomain.com$request_uri;
}
server {
listen 443 ssl;
server_name www.yourdomain.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private/key.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
return 301 https://yourdomain.com$request_uri;
}
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /path/to/certificate.crt;
ssl_certificate_key /path/to/private/key.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
# do the proper handling of the request
}
Keep in mind that both yourdomain.com
and www.yourdomain.com
must be in your SSL certificate. This is possible with a wildcard certificate or with a Server Alternate Name as explained here. Check https://www.startssl.com for nice and free certificates that do this. (Edith: beginning with Chrome version 56, startssl certificates will not be trusted anymore. Try https://letsencrypt.org/ instead.)
After spending so much time with hundreds of similar cases, I've come up with the following snippet. It's short and can be easily tweaked to fit anything.
server {
listen 80;
listen 443 ssl;
server_name example.com www.example.com;
ssl_certificate /path/to/my/certs/example.com/fullchain.pem;
ssl_certificate_key /path/to/my/certs/example.com/privkey.pem;
# Redirect to the correct place, if needed
set $https_redirect 0;
if ($server_port = 80) { set $https_redirect 1; }
if ($host ~ '^www\.') { set $https_redirect 1; }
if ($https_redirect = 1) {
return 301 https://example.com$request_uri;
}
location / {
# ...
}
Oh but
if
is evil!
Yes it can be. But it exists for a reason, and should do no harm to those who know how to use it properly. ;)
I prefer to return with a response code so the browser knows you are redirecting it to another URL.
server {
listen 80;
server_name www.example.com;
return 301 https://example.com$request_uri;
}
then another server configurations block for the https
server {
listen 443 ssl;
server_name example.com;
...
}