How to force or redirect to SSL in nginx?

321,083

Solution 1

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;
}

Solution 2

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;
}

Solution 3

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;
}

Solution 4

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.

Solution 5

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
Share:
321,083

Related videos on Youtube

Callmeed
Author by

Callmeed

Updated on September 17, 2022

Comments

  • Callmeed
    Callmeed over 1 year

    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)

  • Jayesh Gopalan
    Jayesh Gopalan almost 12 years
    @DavidPashley your solution worked like a charm for me. Thanks
  • nh2
    nh2 over 11 years
    Or, according to the site you linked, "BETTER": return 301 http://domain.com$request_uri;
  • engineerDave
    engineerDave over 11 years
    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
  • mateusz.fiolka
    mateusz.fiolka over 11 years
    shortest answer and worked perfectly in my case
  • VBart
    VBart about 11 years
    If you are using the new dual HTTP and HTTPS server definition then you should separate it.
  • DELETEDACC
    DELETEDACC about 11 years
    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!
  • spuder
    spuder over 10 years
    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.
  • Jürgen Paul
    Jürgen Paul about 10 years
    Note that using $server_name instead of $host would always redirect to https://site1 isn't that what $request_uri is for?
  • Peter
    Peter about 10 years
    $request_uri does not contain a host or domain name. In other words, it always starts with a "/" character.
  • jacktrade
    jacktrade over 9 years
    elegant and works perfect!
  • sgb
    sgb over 9 years
    This is generally recommended because it returns a 301 Moved Permanently (your links have permanently moved) as well as re-writing
  • Jared Eitnier
    Jared Eitnier about 9 years
    Also the rewrite line ought to be return 301 https://$server_name$request_uri; as this is the preferred method.
  • Mike Bethany
    Mike Bethany almost 9 years
    This doesn't work as it causes a "too many redirects" error even if you have set proxy_set_header X-Forwarded-Proto https;
  • Mike Bethany
    Mike Bethany almost 9 years
    @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.
  • Ashesh
    Ashesh almost 9 years
    Best answer by far.
  • user2968902
    user2968902 over 8 years
    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).
  • Mike Bethany
    Mike Bethany over 8 years
    @Blaisorblade Thanks, that's a much better way to do it.
  • Joe B
    Joe B over 8 years
    @MikeBethany are you defining listen 443; in the same block?
  • zopieux
    zopieux over 8 years
    I am not sure why this answer is so low in votes. It's the only one worth using.
  • Onion
    Onion over 8 years
    rewrite ^ https://$host$request_uri? permanent; would be a better solution as you might have several server names on a vhost
  • Greg Ennis
    Greg Ennis almost 8 years
    Cant believe so many people use $server_name this is the correct way to do it
  • Harald
    Harald over 7 years
    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.
  • pepkin88
    pepkin88 over 7 years
    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.
  • Fernando Kosh
    Fernando Kosh over 7 years
    +1 for if ($scheme = http)
  • Artem Russakovskii
    Artem Russakovskii over 7 years
    Should use $host here, as mentioned in the other answers.
  • PKHunter
    PKHunter about 7 years
    Aren't IF conditions considered evil and inefficient in the nginx world?
  • stamster
    stamster about 7 years
    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.
  • tajh
    tajh about 7 years
    For completeness, one might add: listen [::]:443 ssl;
  • sdeland
    sdeland about 6 years
    It's probably wise to listen [::]:80; in addition so as to bind/redirect on IPv6 also.
  • dylanh724
    dylanh724 about 6 years
    Google advises to use 301 instead of 303. Source: support.google.com/webmasters/answer/6073543?hl=en
  • stamster
    stamster about 6 years
    @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
  • Ryan
    Ryan almost 6 years
    I wonder what has changed over the years and whether this other answer is better: serverfault.com/a/337893/119666
  • jwatkins
    jwatkins over 5 years
    Administrators that are commit to run HTTPS only servers should also consider enable HTST header so that modern clients knows that all future requests targeted at that domain must be performed over HTTPS. In nginx, this can be done by adding the line add_header Strict-Transport-Security "max-age=15768000" always; to the corresponding HTTPS block.
  • Zee
    Zee almost 5 years
    nginx: [emerg] unknown directive "if($scheme" in /etc/nginx/sites-enabled/mysite.com:16