How do I redirect HTTPS to HTTP on NGINX?
location / {
if ($scheme = https) {
rewrite ^(.*)? http://$http_host$1 permanent;
}
}
Why is something like that useful? At first look I wasn't sure if it could be done. But it presented an interesting question.
You might try putting a redirect statement in your config file and restarting your server. Two possibilities might happen:
- The server will issue the redirect - what you seem to want.
- The server will first do the https exchange, and THEN issue the redirect, in which case, what's the point?
Will add more if I come up with something more concrete.
UPDATE: (couple of hours later) You could try this. You need to put this in your nginx.conf file -
server {
listen 443;
server_name _ *;
rewrite ^(.*) http://$host$1 permanent;
}
Sends a permanent redirect to the client. I am assuming you are using port 443 (default) for https.
server {
listen 80;
server_name _ *;
...
}
Add this so that your normal http requests on port 80 are undisturbed.
UPDATE: 18th Dec 2016
- server_name _
should be used instead of server_name _ *
in nginx versions > 0.6.25 (thanks to @Luca Steeb)
rewrite
and if
should be avoided with Nginx. The famous line is, "Nginx is not Apache": in other words, Nginx has better ways to handle URLs than rewriting. return
is still technically part of the rewrite module, but it doesn't carry the overhead of rewrite
, and isn't as caveat-ridden as if
.
Nginx has an entire page on why if
is "evil". It also provides a constructive page explaining why rewrite
and if
are bad, and how you can work around it. Here's what the page has to say regarding rewrite
and if
:
This is a wrong, cumbersome, and ineffective way.
You can solve this problem properly using return
:
server {
listen 443 ssl;
# You will need a wildcard certificate if you want to specify multiple
# hostnames here.
server_name domain.example www.domain.example;
# If you have a certificate that is shared among several servers, you
# can move these outside the `server` block.
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/cert.key;
# 301 indicates a permanent redirect. If your redirect is
# temporary, you can change it to 302 or omit the number
# altogether.
# $http_host is the hostname and, if applicable, port--unlike $host,
# which will break on non-standard ports
# $request_uri is the raw URI requested by the client, including any
# querystring
return 301 http://$http_host$request_uri;
}
If you expect a lot of bots that don't send a Host
header, you can use $host
instead of $http_host
as long as you stick to ports 80 and 443. Otherwise, you'll need to dynamically populate an $http_host
substitute. This code is efficient and safe as long as it appears in the root of server
(rather than in a location
block), despite using if
. However, you'd need to be using a default server for this to be applicable, which should be avoided with https.
set $request_host $server_name:$server_port;
if ($http_host) {
set $request_host $http_host;
}
If you want to enforce SSL/TLS for specific paths, but forbid it otherwise:
server {
listen 443 ssl;
server_name domain.example;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/cert.key;
location / {
return 301 http://$host$request_uri;
}
location /secure/ {
try_files $uri =404;
}
}
server {
listen 80;
server_name domain.example;
location / {
try_files $uri =404;
}
location /secure/ {
return 301 https://$http_host$request_uri;
}
}
If your server isn't in direct communication with the client--for example, if you're using CloudFlare--things get a bit more complicated. You'll need to ensure that any server in direct communication with the client adds an appropriate X-Forwarded-Proto
header to the request.
Using this is a messy proposition; for a full explanation, see IfIsEvil. In order for this to be useful, the if
block cannot be inside a location
block, for a variety of complex reasons. This forces the use of rewrite
for URI testing. In short, if you have to use this on a production server... don't. Think of it this way: if you've outgrown Apache, you've outgrown this solution.
/secure, /secure/, and anything in /secure/ will enforce https, while all other URIs will enforce http. The (?! )
PCRE construct is a negative lookahead assertion. (?: )
is a non-capturing group.
server {
# If you're using https between servers, you'll need to modify the listen
# block and ensure that proper ssl_* statements are either present or
# inherited.
listen 80;
server_name domain.example;
if ($http_x_forwarded_proto = https) {
rewrite ^(?!/secure)/ http://$http_host$request_uri? permanent;
}
if ($http_x_forwarded_proto != https) {
rewrite ^/secure(?:/|$) https://$http_host$request_uri? permanent;
}
}