How do I redirect HTTPS to HTTP on NGINX?

SslNginxRedirectHttps

Ssl Problem Overview


Is there a way to redirect HTTPS requests to HTTP by adding a rule in the domain's vhost file?

Ssl Solutions


Solution 1 - Ssl

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:

  1. The server will issue the redirect - what you seem to want.
  2. 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)

Solution 2 - Ssl

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

Solution 3 - Ssl

location / {
	if ($scheme = https) {
		rewrite ^(.*)? http://$http_host$1 permanent;
	}
}

Solution 4 - Ssl

this question would have been better suited to the serverfault.com site.

A better way to do the redirect to http:

server {
   listen 443;
   return 301 http://$host$request_uri;
}

This avoid both the 'if' clause and the regex in the rewrite that are features of the other solutions to date. Both have performance implications, though in practice you'd have to have quite a lot of traffic before it would matter.

Depending on your setup, you are likely to also want to specify an ip in the listen clause, and perhaps a servername clause in the above. As is, it will apply to all port 443 requests for all domain names. You generally want an IP per domain with https, so mostly tying the above to an IP is more to the point than tying it to a domain name, but there's variations on that, eg where all domains are subdomains of one domain.

EDIT: TLS is close to universal now, and with it Server Name Identification (SNI) which allows for HTTPS sites on multiple domains sharing a single IP. There's a good write-up here

Solution 5 - Ssl

This helped me:

server {
    listen 443;
    server_name server.org www.server.org;
    rewrite ^ http://$server_name$request_uri? permanent;
}

Solution 6 - Ssl

Not a proper solution, but I was able to solve my use case by using Cloudflare, which handled the SSL for me transparently.

Solution 7 - Ssl

The only simple rule is already explained on the post above me:

server {
    listen ip:443;
    server_name www.example.com;
    rewrite ^(.*) http://$host$1 permanent;
}

Solution 8 - Ssl

server{
  listen 80;
  listen [::]:80;

  server_name mywebsite.com ;
  return 301 https://$host$request_uri;
}

After inserting this code, all traffic for the HTTP default server redirects to HTTPS.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondebView Question on Stackoverflow
Solution 1 - SslSrikar AppalarajuView Answer on Stackoverflow
Solution 2 - SslZenexerView Answer on Stackoverflow
Solution 3 - SslansiartView Answer on Stackoverflow
Solution 4 - Sslmc0eView Answer on Stackoverflow
Solution 5 - SslwarfishView Answer on Stackoverflow
Solution 6 - SslfafrdView Answer on Stackoverflow
Solution 7 - SslMaartenView Answer on Stackoverflow
Solution 8 - SslSpandan JoshiView Answer on Stackoverflow