How to force or redirect to SSL in nginx?
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
Related videos on Youtube
Callmeed
Updated on September 17, 2022Comments
-
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 tohttps://signup.example.com
? (FYI I know there are Rails plugins that can forceSSL
but was hoping to avoid that)-
Nasreddine about 8 yearsPossible duplicate of In Nginx, how can I rewrite all http requests to https while maintaining sub-domain?
-
-
Jayesh Gopalan almost 12 years@DavidPashley your solution worked like a charm for me. Thanks
-
nh2 over 11 yearsOr, according to the site you linked, "BETTER":
return 301 http://domain.com$request_uri;
-
engineerDave over 11 yearsone 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 over 11 yearsshortest answer and worked perfectly in my case
-
VBart about 11 years
If you are using the new dual HTTP and HTTPS server definition
then you should separate it. -
DELETEDACC about 11 yearsGreat, 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 over 10 yearsI 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 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 about 10 years
$request_uri
does not contain a host or domain name. In other words, it always starts with a "/" character. -
jacktrade over 9 yearselegant and works perfect!
-
sgb over 9 yearsThis is generally recommended because it returns a
301 Moved Permanently
(your links have permanently moved) as well as re-writing -
Jared Eitnier about 9 yearsAlso the rewrite line ought to be
return 301 https://$server_name$request_uri;
as this is the preferred method. -
Mike Bethany almost 9 yearsThis 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 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 almost 9 yearsBest answer by far.
-
user2968902 over 8 yearsThat'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 over 8 years@Blaisorblade Thanks, that's a much better way to do it.
-
Joe B over 8 years@MikeBethany are you defining
listen 443;
in the same block? -
zopieux over 8 yearsI am not sure why this answer is so low in votes. It's the only one worth using.
-
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 almost 8 yearsCant believe so many people use $server_name this is the correct way to do it
-
Harald over 7 yearsBoth $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 over 7 yearsThe 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 over 7 years+1 for if ($scheme = http)
-
Artem Russakovskii over 7 yearsShould use $host here, as mentioned in the other answers.
-
PKHunter about 7 yearsAren't IF conditions considered evil and inefficient in the nginx world?
-
stamster about 7 yearsYes 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 about 7 yearsFor completeness, one might add:
listen [::]:443 ssl;
-
sdeland about 6 yearsIt's probably wise to
listen [::]:80;
in addition so as to bind/redirect on IPv6 also. -
dylanh724 about 6 yearsGoogle advises to use 301 instead of 303. Source: support.google.com/webmasters/answer/6073543?hl=en
-
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 almost 6 yearsI wonder what has changed over the years and whether this other answer is better: serverfault.com/a/337893/119666
-
jwatkins over 5 yearsAdministrators 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 almost 5 years
nginx: [emerg] unknown directive "if($scheme" in /etc/nginx/sites-enabled/mysite.com:16