Redirect all HTTP requests to HTTPS with Nginx

All login credentials transferred over plain HTTP can easily be sniffed by an MITM attacker, but is is not enough to encrypt the login forms. If you are visiting plain HTTP pages while logged in, your session can be hijacked, and not even two-factor authentication will protect you. To protect all info sent between your visitors – which includes you – and your web server, we will redirect all requests that are coming over plain HTTP to the HTTPS equivalent.

It is not really necessary to use HTTPS for absolutely all requests, but it makes your life much easier to just handle one scheme and redirect all plain HTTP traffic to the equivalent HTTPS resource. So please make sure you setup HTTPS for the same hostname that you use for plain HTTP. Do NOT 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 will save you from a lot of headaches further down the road.

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

Now all traffic for http://example.com/foobar is redirected to https://example.com/foobar. Please note that while this works fine for GET requests, the postdata is not sent to the new URL for POST requests. This is usually not an issue if you’re using WordPress – at least not if your website is coded somewhat properly – as all your forms should use the URL WordPress is configured to use.

The redirect response is sent with the HTTP status code 301, which tells the browser (and search engines) that this a permanent redirect. This makes the browser remember the redirect, so that next time they visit, the browser will do the redirect internally. If you set the HSTS header – which you should – the browser will even do this for every single request to your domain.

Note that the above is a very general purpose Nginx config that will redirect all hostnames on the server. You are free to specify the specific hostnames you want to have redirected. Also: If you’re a little paranoid – which is not a bad thing in web security – you will note that it’s using the Nginx $host variable. This variable can be set by the HTTP Host header – provided by the client. It is most likely safe to use in this manner, but as a principle, it’s better to play it safe by using variables we set ourselves:

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

We do have to use the $request_uri variable – which we have very little control over. To remove malicious request URIs, you should look into getting a WAF (Web Application Firewall).

76 Comments

  1. Not working if the https is handled by a proxy like Cloudflare, since the server listens on port 80 and not 443 (with the flexible ssl)

    1. In that case, your proxy would normally handle the redirection from HTTP to HTTPS.

      BTW, if you’re using Cloudflare, you should use “Full SSL” instead of “Flexible SSL”. There’s no reason why you’d want the traffic between Cloudflare and you to be unencrypted. Use a certificate from Let’s Encrypt or a self-signed one.

      If you for some reason can’t handle the redirection in the proxy, or absolutely don’t want to setup HTTPS on your own server, your proxy should set a header indicating whether the request came over HTTP or HTTPS. Cloudflare sets X-Forwarded-Proto.

      1. Bjørn,
        thanks for your article, and for letting me know Let’s encrypt.. I needed a way to obtain free but valid certificates for the platform I’m developing, and didn’t know that project!
        You saved my day!

        Paolo

      2. Even better, CloudFlare generates their own certificates on your behalf, just for the connection between them and your server — no need to use Let’s Encrypt, since it’s CloudFlare’s certificate that will be presented to the visitor’s browsers anyway, not yours.

        1. You still need a cert on your server to use “Full SSL” or “Strict SSL” (which you should).

          1. Cloudflare has started generating origin certificates for this very purpose, and it’s available on the free plan. Security tab -> origin certs

      3. Thanks :) I keep forgetting that! I was scratching my head trying to figure out what I did wrong this time to get all those redirects… and of course, I had forgotten about the ‘Full’ setting on CloudFlare!

        1. … no it will not. 3 things can make a unique request. IP OR Port OR hostheader value.
          Http and https use different ports.

          1. https://$server_name:80
            You are redirecting to Port 80, but telling the browser to establish a TLS session. However the Nginx server is listening for unencrypted connections.
            This may not quite redirect to itself, but will not work.

  2. hi,
    set in your nginx as default ssl on port 443.

    You can combine this into one server block like so:

    server {
    listen 80;
    listen 443 default_server ssl;

    }

    1. Sure, but then you would have to check for the $scheme being used:

      if ($scheme=='http') {
        return 301 https://$server_name$request_uri;
      }

      I like to keep separate virtualhosts with different logic in different server blocks.

  3. Hey Bjørn! First off, thank you very much for writing this tutorial. I’m not quite sure if you noticed, but using the second method always results in a redirect to the first listed “server_name”, i.e.

    server_name example.com http://www.example.com;
    return 301 https://$server_name$request_uri;

    sends me to “https://example.com” even if “www.example.com” was entered. The same is true for other subdomains, i.e.

    server_name http://www.example.com test.example.com (…);
    return 301 https://$server_name$request_uri;

    always results in a redirect to “https://www.example.com”. I’ve searched around a bit and found the following comment on https://serverfault.com/questions/67316/in-nginx-how-can-i-rewrite-all-http-requests-to-https-while-maintaining-sub-dom

    “Note that you must use $host instead of $server_name if you’re using subdomains.”

    If that is really so, you might want to add a little note to your post. My solution (since I am more than a bit paranoid :p) was to enter each subdomain in its own server definition and to reverse the example.com / http://www.example.com order so that entering the URL in the browser leads to the expected result.

    1. If you’re paranoid, you don’t have to use a server definition per hostname, but you can use a catchall server block for unrecognized hostnames and use the $host variable in a server block where you specify your valid hostnames:

      server {
        server_name _;
        return 444;
      }
      server {
        server_name example.com example.net whatever.youlike.example.org;
        return 301 https://$host$request_uri;
      }
      
      1. Hello Bjørn,
        thank you very much for this simple paranoid setting. I set up my server and got flooded with very suspicious requests.
        I think it is good time to be paranoid.
        Thank you again for the whole article and the GIST lower! I use it as my go-to config now.

  4. Hi Bjørn, I’ve been struggling for hours to find a solution to have one nginx host file which handles domain.tld and http://www.domain.tld with and without https and redirects all of them to use https and www

    could you help me out there? No combination of above examples worked for me :-(

      1. Thanks Bjørn, that was a really fast reply :-)
        The solution with 3 server blocks looks great but I have a few questions:

        Why does the second server block not contain root /whatever/your/webroot/is; – is that an omission or on purpose?

        Here is my current vhost file according to your instructions except I’m not sure what all goes into the second server block: http://pastebin.com/fYq2CjLf

          1. Sure but what does go in here: # include your HTTPS config here ?
            A complete copy of everything in the 3rd block? I tried that and the result was a warning about a wrong certificate so I had to point it to a non-www certificate to get the redirect to kick in.

            Anyway, thanks for helping out so far, I have several open threads on different forums waiting for help so hopefully I can figure this out soon :-)

  5. Hmm,
    I think a have a big problem with understanding how to properly set up things combining different domains, ssl certificate and cloduflare.

    My setup:
    several tlds / domain variations
    letsencrypt
    cloudflare ssl full

    what I would like to accomplish:

    redirect all domain/tlds from non-www to (example) https://www.domain.de

    What would be the option for this?

  6. Hello, do you have a way to do this when the server only runs on port 80 and is using an AWS load balancer? In apache we would use
    “`X-Forwarded-For
    X-Forwarded-Proto
    X-Forwarded-Port“`

    example
    “`RewriteEngine on
    RewriteCond %{HTTP:X-Forwarded-Proto} ^http$
    RewriteRule .* https://%{HTTP_HOST}%{REQUEST_URI} R=301,L“`

  7. This is all good, but for one very important point: When setting up any web server, first use 302 (temporary) redirects. Make sure everything works over the course of a week, then *only* change them to 301 (permanent) if you are absolutely sure the change is permanent. Once 301 redirects get into the world of DNS, they are there to stay.

    I made this mistake on my web site. Luckily it was not a big deal, but I decided later that the ‘www’ was superfluous.

  8. I would like to redirect all https traffic to an app instance that will use port 8000. Could your code here be changed to accomplish that?

    1. Do you want to redirect the visitors to port 8000, or proxy/forward the traffic to port 8000?

      If you want to redirect them:
      return 301 https://example.com:8000$request_uri;

      If you want to proxy them (forward the traffic):

      location / {
          proxy_pass http://127.0.0.1:8000;
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header X-Forwarded-Proto $scheme;
      }
  9. How can this be done without sending a 301?
    Hi,

    My apache has something like :
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule (.*) https://%{SERVER_NAME}/$1 [R,L]

    The redirect may or mayn’t be permanent in my case. Is this possible in nginx?

    Thanks.

    1. Your Apache config will send a 302 redirect. So in Nginx you would use:
      return 302 https://$server_name$request_uri;

  10. Used the exact same code verbatim. Broke my website. Not a sysadmin so my knowledge is very limited. Just following awesome guides like yours. I also have redirect happening on .htaccess but that only works for my root domain. If I use http:mydomain.com/path it doesn’t redirect to https version of the same. Will appreciate you guiding me through it.

    1. If you’re using .htaccess, you’re using Apache, not Nginx.

  11. Hi Bjørn, first of all, thank you for your wonderful articles!

    Can you help me? I spent many hours and efforts to set up nginx configuration, but I still have an issue. Below is my simplified configuration.

    root /var/www/public;
    index index.php index.html index.htm;

    ssl_certificate /home/oleg/ssl/www.example.com.chained.crt;
    ssl_certificate_key /home/oleg/ssl/www.example.com.key;

    # disable ssl
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

    # optimizing the cipher suites
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    # redirect non-www to www version
    server {
    server_name example-en.com;
    rewrite ^(.*) https://www.example-en.com$1 permanent;
    }

    server {
    server_name example-es.com;
    rewrite ^(.*) https://www.example-es.com$1 permanent;
    }

    server {
    server_name example-pt.com;
    rewrite ^(.*) https://www.example-pt.com$1 permanent;
    }

    server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    listen 443 ssl;

    server_name http://www.example-en.com http://www.example-es.com http://www.example-pt.com;

    # add trailing slash
    if ($request_method = “GET” ) {
    rewrite ^([^.]*[^/])$ $1/ permanent;
    }

    location / {
    try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
    try_files $uri =404;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    fastcgi_param HTTPS on;
    }

    location ~* \.(?:jpg|jpeg|gif|png|ico|cur|gz|svg|svgz|mp4|ogg|ogv|webm|htc|svg|woff|woff2|ttf)\$ {
    expires 1M;
    access_log off;
    add_header Cache-Control “public”;
    }

    }

    http://example-en.com/ -> https://www.example-en.com/ -> success redirect
    https://example-en.com/ -> https://www.example-en.com/ -> success redirect
    http://www.casinobonusesnow.com/ -> http://www.casinobonusesnow.com/ -> fail

    How can I change my config to enable redirection from urls starting with http://www. to https version?

  12. I wanted to do proxy pass from https front end site to back end siete https . both application are in some other servers. but urls can be accessible from public.

    I have tried all above

    My exact requirement is….

    I have xyz.com site but my backend server abc.com, but user should always face xyz.com.

    Can you please help me out

  13. I need to configure following scenario in nginx — xyz.com site my front end url and my backend server abc.com, but user should always face xyz.com.

    xyz.com is public which enabled https
    abc.com is private site it is also enabled https.

    I need to expose only xyz.com globally and site hosted on abc.com

    1. Use proxy_pass, and set x-forwarded-for.
      On abc.com check for x-forwarded-for, and if it’s not there, do a 301.

  14. server {
    listen 80;
    server_name 123.xxxx.com;

    location / {

    proxy_redirect https://123.xxxx.com https://abc.xxxxx.com;

    proxy_pass https://123.xxxx.com;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    }

    configure d above but not working.
    it is saying upstream not found when i configure upstream getting following error nginx: [emerg] host not found in upstream https://123.xxxx.com.

    Can you please help me,

  15. hi, thanks for the awsome site here.
    a question about how to set up http not force to https but just available
    i have a wordpress set up with Nginx, those images will broken link if i turn http force to https, but just want to have https available.
    please adivse hot to do that . thanks a lot.

    1. Like I wrote before: If you’re using a proxy like CloudFlare, your proxy would normally handle the redirection from HTTP to HTTPS.

  16. Was struggling for ages trying to get the redirection to work and it just kept stumping me. I ended up putting the redirects in their own `server` block instead of in the default and that seems to have done the trick.

    As an aside, I have nothing but problems with Cloudflare. There are a few benefits to it but mostly just headaches.

    Thanks again Bjørn for this. Up until last night I knew nothing about nginx and now I have a load of new tricks up my sleeve! :D

  17. Hi, I’m struggling to find a solution to my problem and this thread seemed a nice place for it for some reason (even more than NGinx website or stackoverflow).

    What I want is simple. I have a NodeJS application calling APIs on a SSL protected website (let’s call it https://whatever.com).

    What I need is to proxy the connection made to whatever.com through Nginx, but I couldn’t find a way to do it. It seems like I need a domain name, so I can install a proper certificate, but that seems a huge overkill for something so simple.

    If it was port80->port80 it would be dead simple to do. But because the destination API server is SSL, it turns into a nightmare.

    Your solution isn’t enough because all requests are handled with 301, and I really need them to be processed by Nginx, so I can collect stuff such as request times, and so on.

    Any hints?

    1. Doesn’t sound like a redirect is what you want, but rather a proxy_pass statement in a separate location block.

      1. Tried all sorts of variations in config (location, upstream, proxy_pass included). Do you know if it is possible, because of the SSL and all?

  18. Hello guys.

    Can you please help a dummy?

    I’m using Webuzo to manage a mew new dedicated server.
    From it’s GUI I can add new .conf files for each domain I have.
    I have successfully added one .conf file with this content only:
    try_files $uri $uri/ /index.php?q=$uri&$args;
    so now I can use pretty permalinks in WordPress.
    But if I try to add a new .conf file with your code it will not work.

    Any suggestions?

    Thanks.

  19. Wonderful resourceful information. I like what i’ve
    actually
    acquired here. You make your site articles easy and enjoyable to grasp.
    a
    I can not wait to read more from you. Bookmarked!

  20. Pretty portion of content. I simply stumbled upon your web site
    and in accession capital to say that I get actually loved account your blog posts.

    Anyway I’ll be subscribing on your feeds or even I achievement you access consistently fast.

  21. Thank you so much for this post. I had trouble with using server_name _; and redirecting to server_name in my 301, which redirected to “_”.

  22. by the way: Your site locks out Tor users because of Cloudflare. You can disable the captcha for them, or set Tor users to be verified via the Cloudflare javascript check

  23. I would such a guide that doesn’t use custom image / script. I like to understand what I’m doing. Thanks a lot though, I analysed your script and adapted it to my needs.

  24. The problem i’m having is that when i type the IP address into the address bar it does not get forwarded to my port 443 and https:// Any thoughts?

  25. I will іmmedіately seize your rss as I cаn’t
    in findіng your email subscription hyperlink or e-newsletter service.
    Do you’ve any? Please let me recognise in order that I may
    just ѕubscribe. Thankѕ.

  26. I have a Mainframe application which is trying to call a HTTPS endpoint which is setup as an enterprise API using APIGEE gateway in my internal network. This request go through a corporate proxy(webproxy.org).

    Having said that, Mainframe CICS is not supporting SNI to the end-point and getting certificate errors, So plan is to make a call to in-between reverse proxy server(NGINX/Apache) which will just trigger a plain http request to the proxy server so that it will convert it to the HTTPS endpoint. So that the proxy will take care of handling the SSL certificates & keys. but the problem here is that it is not routing through the corporate proxy and hence i am not getting response on the same. I tried with return 307 but it didn’t work out as mainframes again can’t re-route it to https because of previous problem, any suggestions on how to proceed…. i mean where can i mention webproxy.org to call https://$servername$request_uri…any suggestions are always welcome.

  27. Hi i have a url in nginx
    mailsvr.parco.net.pk/mailsvr/mail
    i want to remove the trailing */mailsvr/* in the url, can someone help me out.

  28. Thank you for the information and technical details. Good information for your friends and mine. Wishing everyone benefits

  29. I have followed the similar changes to redirect but I have faced its taking too long to render the application! is there some enhancement I can do to make that work! I am pretty new on nginx!

Comments are closed.