14 Feb 2019

Redirect All HTTP Requests to HTTPS with Nginx

All requests transferred over plain HTTP can easily be sniffed by a MITM attacker, and it’s not enough to just encrypt forms. If you visit plain HTTP pages while logged in, your session can be hijacked — not even two-factor authentication protects you. To protect all information sent between your visitors and your server, redirect all requests coming over plain HTTP to their HTTPS equivalent.

It’s not strictly necessary to use HTTPS for every request, but it’s much simpler to handle one scheme and redirect all plain HTTP traffic to the HTTPS equivalent. Make sure you set up HTTPS for the same hostname you use for plain HTTP — don’t use secure.example.com if your regular hostname is example.com or www.example.com. The only difference should be the scheme, nothing else. This saves a lot of headaches later.

Steps

  1. Set up HTTPS on Nginx.
  2. Optimize HTTPS on Nginx and get an A+ score on the SSL Labs test.
  3. Optionally, set up HTTP Public Key Pinning (HPKP).
  4. Redirect all HTTP traffic to HTTPS in your Nginx config.
server {
     listen 80;
     listen [::]:80;
     server_name _;
     return 301 https://$host$request_uri;
}

Now all traffic for http://example.com/foobar redirects to https://example.com/foobar. Note that while this works fine for GET requests, POST data isn’t carried over to the new URL for POST requests.

The redirect is sent with HTTP status code 301, telling the browser (and search engines) it’s a permanent redirect — the browser remembers it, so on the next visit it performs the redirect internally. With the HSTS header set (which you should do), the browser will do this for every request to your domain.

The config above is general-purpose and redirects all hostnames on the server — you can scope it to specific hostnames. Also worth noting: it uses Nginx’s $host variable, which can be set by the client-provided HTTP Host header. It’s most likely safe to use this way, but as a principle it’s better to use variables you set yourself:

server {
      listen 80;
      listen [::]:80;
      server_name example.com www.example.com;
      return 301 https://$server_name$request_uri;
 }

You do still have to use $request_uri, which you have very little control over — to guard against malicious request URIs, look into a Web Application Firewall (WAF).