239

I have a signup page on a subdomain like: https://signup.example.com

It should only be accessible via HTTPS but I'm worried people might somehow stumble upon it via HTTP and get a 404.

My html/server block in nginx looks like this:

html { server { listen 443; server_name signup.example.com; ssl on; ssl_certificate /path/to/my/cert; ssl_certificate_key /path/to/my/key; ssl_session_timeout 30m; location / { root /path/to/my/rails/app/public; index index.html; passenger_enabled on; } } } 

What can I add so that people who go to http://signup.example.com get redirected to https://signup.example.com ? (FYI I know there are Rails plugins that can force SSL but was hoping to avoid that)

1

7 Answers 7

148

According to nginx pitfalls, it's slightly better to omit the unnecessary capture, using $request_uri instead. In that case, append a question mark to prevent nginx from doubling any query args.

server { listen 80; server_name signup.mysite.com; rewrite ^ https://$server_name$request_uri? permanent; } 
7
  • 71
    Or, according to the site you linked, "BETTER": return 301 http://domain.com$request_uri; Commented Aug 14, 2012 at 4:56
  • 13
    one comment. the $server_name$ picks up the first server_name variable. So be aware of this if you have non FQN names in your configuration Commented Aug 15, 2012 at 15:48
  • 2
    @nh2 This is another case of the documentation being wrong since using return 301... causes a "too many redirects" error while the rewrite method actually works. Commented Jul 2, 2015 at 12:06
  • 4
    That's now documented as "also BAD". @MikeBethany return 301 does work, unless (I guess) you're triggering it also for correct URLs, by listening on both ports (example of config. triggering the problem: take serverfault.com/a/474345/29689's first answer and omit the if). Commented Aug 3, 2015 at 8:57
  • 1
    I wonder what has changed over the years and whether this other answer is better: serverfault.com/a/337893/119666 Commented Jul 14, 2018 at 0:59
279

The best way as it described in the official how-to is by using the return directive:

server { listen 80; server_name signup.mysite.com; return 301 https://$server_name$request_uri; } 
7
  • 6
    shortest answer and worked perfectly in my case Commented Sep 8, 2012 at 22:37
  • 1
    This is generally recommended because it returns a 301 Moved Permanently (your links have permanently moved) as well as re-writing Commented Dec 17, 2014 at 17:17
  • 2
    This doesn't work as it causes a "too many redirects" error even if you have set proxy_set_header X-Forwarded-Proto https; Commented Jul 2, 2015 at 12:03
  • 1
    @MikeBethany are you defining listen 443; in the same block? Commented Aug 12, 2015 at 18:06
  • 3
    This should be the accepted answer. Commented Sep 13, 2016 at 10:08
135

This is the correct and most efficient way if you want to keep it all in one server block:

server { listen 80; listen [::]:80; listen 443 default_server ssl; server_name www.example.com; ssl_certificate /path/to/my/cert; ssl_certificate_key /path/to/my/key; if ($scheme = http) { return 301 https://$server_name$request_uri; } } 

Everything else above, using "rewrite" or "if ssl_protocol" etc is slower and worse.

Here is the same, but even more efficient, by only running the rewrite on the http protocol it avoids having to check the $scheme variable on every request. But seriously, it's such a minor thing that you don't need to separate them.

server { listen 80; listen [::]:80; server_name www.example.com; return 301 https://$server_name$request_uri; } server { listen 443 default_server ssl; server_name www.example.com; ssl_certificate /path/to/my/cert; ssl_certificate_key /path/to/my/key; } 
6
  • 9
    Great, some coward voted this answer down without saying why, even though this answer is correct. Maybe another one of those "if is evil" cultists. If you bother to read the Nginx documentation about If, you will know that IfIsNOTEvil, just CERTAIN uses of it within a location{} context, none of which we do here. My answer is absolutely the correct way of doing things! Commented Feb 26, 2013 at 21:50
  • 2
    I didn't down vote this, but I would like to point out that default has been changed to 'default_server' in the most recent versions. Commented Oct 22, 2013 at 3:32
  • 1
    The first solution cannot be the most efficient, if the second one is even more efficient. And you even described, why you shouldn't use an if there: "it avoids having to check the $scheme variable on every request". The point of not using ifs is not only about performance, but also about being declarative, and not imperative. Commented Oct 24, 2016 at 11:12
  • 1
    +1 for if ($scheme = http) Commented Nov 27, 2016 at 21:34
  • Should use $host here, as mentioned in the other answers. Commented Jan 7, 2017 at 9:25
56

If you are using the new dual HTTP and HTTPS server definition, you can use the following:

server { listen 80; listen [::]:80; listen 443 default ssl; server_name www.example.com; ssl_certificate /path/to/my/cert; ssl_certificate_key /path/to/my/key; if ($ssl_protocol = "") { rewrite ^ https://$server_name$request_uri? permanent; } } 

This appears to work for me and doesn't cause redirect loops.

Edit:

Replaced:

rewrite ^/(.*) https://$server_name/$1 permanent; 

with Pratik's rewrite line.

6
  • 2
    @DavidPashley your solution worked like a charm for me. Thanks Commented May 17, 2012 at 12:23
  • 1
    If you are using the new dual HTTP and HTTPS server definition then you should separate it. Commented Feb 23, 2013 at 14:42
  • 2
    elegant and works perfect! Commented Sep 17, 2014 at 17:01
  • 2
    This was the only solution that worked for me with my Laravel/Homestead Nginx configuration. Commented Jan 5, 2015 at 17:04
  • 1
    Also the rewrite line ought to be return 301 https://$server_name$request_uri; as this is the preferred method. Commented Apr 20, 2015 at 15:17
31

Yet another variant, that preserves the Host: request header and follows the "GOOD" example on nginx pitfalls:

server { listen 10.0.0.134:80 default_server; server_name site1; server_name site2; server_name 10.0.0.134; return 301 https://$host$request_uri; } 

Here are the results. Note that using $server_name instead of $host would always redirect to https://site1.

# curl -Is http://site1/ | grep Location Location: https://site1/ # curl -Is http://site2/ | grep Location Location: https://site2/ # curl -Is http://site1/foo/bar | grep Location Location: https://site1/foo/bar # curl -Is http://site1/foo/bar?baz=qux | grep Location Location: https://site1/foo/bar?baz=qux 
6
  • 2
    $request_uri does not contain a host or domain name. In other words, it always starts with a "/" character. Commented Apr 27, 2014 at 11:17
  • 2
    Best answer by far. Commented Jul 3, 2015 at 14:42
  • 3
    I am not sure why this answer is so low in votes. It's the only one worth using. Commented Dec 8, 2015 at 18:02
  • 2
    Cant believe so many people use $server_name this is the correct way to do it Commented Jun 29, 2016 at 2:14
  • 1
    Both $host and $server_name are correct, in different environments. $host reuses the hostname used in the client URL; $server_name forces a redirect to the correct, canonical hostname of the site. For most uses I prefer server_name. Commented Oct 4, 2016 at 18:10
2

I think this is the most simple solution. Forces both non-HTTPS and non-WWW traffic to HTTPS and www only.

server { listen 80; listen 443 ssl; server_name domain.tld www.domain.tld; # global HTTP handler if ($scheme = http) { return 301 https://www.domain.tld$request_uri; } # global non-WWW HTTPS handler if ($http_host = domain.tld) { return 303 https://www.domain.tld$request_uri; } } 

EDIT - Apr 2018: Solution w/o IF's can be found in my post here: https://stackoverflow.com/a/36777526/6076984

4
  • 1
    Aren't IF conditions considered evil and inefficient in the nginx world? Commented Feb 14, 2017 at 20:09
  • Yes they are, in general. But for this simple checks I would guess not. I do have a proper configuration file which involves more code writing though, but avoids IF's completely. Commented Feb 22, 2017 at 19:29
  • Google advises to use 301 instead of 303. Source: support.google.com/webmasters/answer/6073543?hl=en Commented Apr 17, 2018 at 6:49
  • @DylanHunt - I left 303 only for testing, take a note that 1st handler was set to 301, only 2nd I forgot to change :) Also, solution w/o IF's: stackoverflow.com/a/36777526/6076984 Commented Apr 20, 2018 at 18:27
1
server { listen x.x.x.x:80; server_name domain.tld; server_name www.domian.tld; server_name ipv4.domain.tld; rewrite ^ https://$server_name$request_uri? permanent; } 

This works better i think. x.x.x.x refers to your server's IP. If you are working with Plesk 12, you can do that by changing the "nginx.conf" file in the directory "/var/www/vhosts/system/domain.tld/conf" for whichever domain you want. Do not forget to restart nginx service after you save the configuration.

1
  • rewrite ^ https://$host$request_uri? permanent; would be a better solution as you might have several server names on a vhost Commented Jan 20, 2016 at 15:39

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.