I want to rewrite all http requests on my web server to be https requests, I started with the following:

server {
    listen      80;

    location / {
      rewrite     ^(.*)   https://mysite.com$1 permanent;
    }
...


One Problem is that this strips away any subdomain information (e.g., node1.mysite.com/folder), how could I rewrite the above to reroute everything to https and maintain the sub-domain?

10 Answers 10

up vote 738 down vote accepted

Correct way in new versions of nginx

Turn out my first answer to this question was correct at certain time, but it turned into another pitfall - to stay up to date please check Taxing rewrite pitfalls

I have been corrected by many SE users, so the credit goes to them, but more importantly, here is the correct code:

server {
       listen         80;
       server_name    my.domain.com;
       return         301 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000" always; 

       [....]
}
  • 3
    You would have to do this on a domain by domain basis however - no? What if you wanted to apply it to every domain on your server? – JM4 Aug 15 '12 at 23:57
  • 27
    @JM4: if you use $host$ in the rewrite instead of server_name and add default_server to the listen directive it will work for every domain on your server. – Klaas van Schelven Jan 5 '13 at 20:06
  • 5
    It's important to mention that the 301 is stored in your local cache without expiry date. Not very useful when config changes – Trefex Apr 10 '15 at 13:58
  • 6
    @everyone Use a 307 redirect to preserve POST content. – Mahmoud Al-Qudsi May 14 '15 at 16:46
  • 8
    Note that you must use $host instead of $server_name if you're using subdomains. – Catfish Jan 19 '16 at 14:27

NOTE: The best way to do this was provided by https://serverfault.com/a/401632/3641 - but is repeated here:

server {
    listen         80;
    return 301 https://$host$request_uri;
}

In the simplest case your host will be fixed to be your service you want to send them to - this will do a 301 redirect to the browser and the browser URL will update accordingly.

Below is the previous answer, which is inefficient due to regex, a simple 301 is great as shown by @kmindi

I have been using nginx 0.8.39 and above, and used the following:

 server {
       listen 80;
       rewrite ^(.*) https://$host$1 permanent;
 }

Sends a permanent redirect to the client.

  • 15
    I think it should be 80 - as this is listening for http and then telling the client to come back as https (443). – Michael Neale Dec 8 '11 at 22:30
  • 3
    This should be the top answer! – Nathan Aug 31 '12 at 22:52
  • 3
    This is the most taxing answer. – Case Nov 21 '12 at 7:36
  • 1
    this is the easiest one, but the least secure - this way you allow your server to redirect a user to any page, without checking if it's even allowed to be used on your server. If your server serves mydomain.co, malicious users could still use your server to redirect users to other domains like mydomain.co, such as google.com. – friedkiwi May 30 '13 at 15:32
  • 10
    @cab0lt there is no security issue here. Serving a redirect doesn't present a security risk. If there's access control requirements, those should be checked at the point where the browser requests the new URL. The browser won't gain access simply on the basis of the redirect, nor do they need the redirect to request the new URL. – mc0e Oct 16 '13 at 3:38

I think the best and only way should be using a HTTP 301 Moved Permanently redirect like this:

server {
    listen         [::]:80;
    return 301 https://$host$request_uri;
}

The HTTP 301 Moved Permanently redirect is also the most efficient because there is no regex to be evaluated, according to already mentioned pitfails.


The new HTTP 308 Moved Permanently preserves the Request method and is supported by major browsers. For example, using 308 prevents browsers from changing the request method from POST to GET for the redirect request.


If you want to preserve the hostname and subdomain this is the way.

This does still work if you have no DNS, as I am also using it locally. I am requesting for example with http://192.168.0.100/index.php and will get redirected to exactly https://192.168.0.100/index.php.

I use listen [::]:80 on my host because i have bindv6only set to false, so it also binds to ipv4 socket. change it to listen 80 if you don't want IPv6 or want to bind elsewhere.

The solution from Saif Bechan uses the server_name which in my case is localhost but that is not reachable over a network.

The solution from Michael Neale is good, but according to the pitfails, there is a better solution with redirect 301 ;)

  • Nice you try to quote it, but 301 does not work on HTTPS. – Case Nov 21 '12 at 7:34
  • 5
    what does not work? the stated server section is for non-encrypted http (without s) traffic to be permanently redirected to encrypted server (that section that listens on 443 (https) is not listed) – kmindi Nov 21 '12 at 13:05
  • I checked this works great with https and everything - @kmindi I updated my answer with reference to yours - as I think it is the right way and this keeps popping up! Nice work. – Michael Neale Jun 4 '13 at 0:03
  • When using a domain (non-ip) request, does not work unless I change '[::]:80' to '80'. – Joseph Lust Nov 17 '13 at 19:58
  • that could be the expected behaviour: trac.nginx.org/nginx/ticket/345. I updated the answer to describe the listen option. – kmindi Nov 17 '13 at 20:35

The above didn't work for with with new subdomains being created all the time. e.g. AAA.example.com BBB.example.com for about 30 subdomains.

Finally got a config working with the following:

server {
  listen 80;
  server_name _;
  rewrite ^ https://$host$request_uri? permanent;
}
server {
  listen  443;
  server_name example.com;
  ssl on;
  ssl_certificate /etc/ssl/certs/myssl.crt;
  ssl_certificate_key /etc/ssl/private/myssl.key;
  ssl_prefer_server_ciphers       on;
# ...
# rest of config here
# ...
}
  • thank you! nginx would either return 301 https://*/ or cancel the request prematurely in the other answers here. server_name _; with $host was the answer that did the trick. +1 – zamnuts Mar 13 '13 at 0:53
  • 1
    This one is optimal! However, I recommend for some to replace _ with the actual domain, e.g. .domain.com I had two servers, and nginx was accidentally directing one of my servers to the default server. – zzz Jul 1 '13 at 21:16
  • 1
    This is the only answer that worked for me, thanks! – Snowman Jul 21 '16 at 0:28
  • Thanks a lot buddy.. I have tried many of the solutions but didnt worked out. This solutions is awesome and it worked for me. server_name _; what is this meant for.. I didnt understand. Please explain me this. – Pavan Kumar Jan 11 '17 at 14:51

Within the server block you can also do the following:

# Force HTTPS connection. This rules is domain agnostic
if ($scheme != "https") {
    rewrite ^ https://$host$uri permanent;
}
  • 2
    This configuration caused my server to produce a redirect loop – Corkscreewe Oct 16 '15 at 13:58
  • Maybe cause there's another redirect in place or https is not enable in your site/app – Oriol Oct 31 '15 at 3:59
  • 1
    None of the others seemed to work except this one. Using nginx version: nginx/1.10.0 (Ubuntu) – ThatGuy343 Jul 2 '16 at 1:22
  • upvoted for https://$host$uri – AMB Nov 27 '16 at 17:12
  • 4
    This is the way to go if you are behind a loadbalancer ! – Antwan Mar 28 '17 at 19:50

I posted a comment on the correct answer a long, long time ago with a very important correction, but I feel it is necessary to highlight this correction in its own answer. None of the previous answers are safe to use if at any point you had unsecure HTTP set up and expect user content, have forms, host an API, or have configured any website, tool, application, or utility to speak to your site.

The problem occurs when a POST request is made to your server. If the server response with a plain 30x redirect the POST content will be lost. What happens is that the browser/client will upgrade the request to SSL but downgrade the POST to a GET request. The POST parameters will be lost and incorrect request will be made to your server.

The solution is simple. You need to use a HTTP 1.1 307 redirect. This is detailed in RFC 7231 S6.4.7:

  Note: This status code is similar to 302 (Found), except that it
  does not allow changing the request method from POST to GET.  This
  specification defines no equivalent counterpart for 301 (Moved
  Permanently) ([RFC7238], however, defines the status code 308
  (Permanent Redirect) for this purpose).

The solution, adapted from the accepted solution, is to use 307 in your redirect code:

server {
       listen         80;
       server_name    my.domain.com;
       return         307 https://$server_name$request_uri;
}

server {
       listen         443 ssl;
       server_name    my.domain.com;
       # add Strict-Transport-Security to prevent man in the middle attacks
       add_header Strict-Transport-Security "max-age=31536000"; 

       [....]
}
  • 1
    very, very very handfull command. thanks! – Denis Matafonov Mar 22 '17 at 18:26

I managed to do it like this:

server {
listen 80;
listen 443 ssl;

server_name domain.tld www.domain.tld;

# global HTTP handler
if ($scheme = http) {
        return 301 https://www.domain.tld$request_uri;
}

# global non-WWW HTTPS handler
if ($http_host = domain.tld){
        return 303 https://www.domain.tld$request_uri;
}
}

https://stackoverflow.com/a/36777526/6076984

I am running ngnix behind an AWS ELB. The ELB is talking to ngnix over http. Since the ELB has no way to send out redirects to clients, I check for the X-Forwarded-Proto header and redirect:

if ($http_x_forwarded_proto != 'https') {
    return 301 "https://www.exampl.com";
}

If you return 301 https://$host$request_uri; as the default response on port 80, then your server may sooner or later get on a list of open proxies[1] and start being abused to send traffic elsewhere on the Internet. If your logs fill up with messages like this one, then you know it's happened to you:

42.232.104.114 - - [25/Mar/2018:04:50:49 +0000] "GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1" 301 185 "http://www.ioffer.com/" "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Hotbar 4.1.8.0; RogueCleaner; Alexa Toolbar)"

The problem is that $host will echo back whatever the browser sends in the Host header or even the hostname from HTTP's opening line, like this one:

GET http://www.ioffer.com/i/new-fashion-fine-gold-bracelet-versaec-bracelet-641175733 HTTP/1.1

Because of that problem, some other answers here recommend using $server_name instead of $host. $server_name always evaluates to what you put in the server_name declaration. But if you have multiple subdomains there or use a wildcard, that won't work, because $server_name only uses the first entry after the server_name declaration, and more importantly will just echo back a wildcard (not expand it).

So how to support multiple domains while maintaining security? On my own systems I've dealt with this dilemma by first listing a default_server block that doesn't use $host, and then listing a wildcard block that does:

server {
  listen 80 default_server;
  server_name example.com;
  return 301 https://example.com$request_uri;
}
server {
  listen 80;
  server_name *.example.com;
  return 301 https://$host$request_uri;
}

(You could also list more than one domain in the second block.)

With that combination, unmatched domains will get redirected somewhere hardcoded (always example.com), and domains that match your own will go to the right place. Your server won't be useful as an open proxy, so you won't be attracting trouble.

If you are feeling ornery, I suppose you could also make the default_server block match none of your legitimate domains and serve something offensive. . . .

[1] Technically "proxy" is the wrong word, because your server isn't going out and fulfilling requests for the clients, just sending a redirect, but I'm not sure what the right word would be. I'm also not sure what the goal is, but it fills up your logs with noise and consumes your CPU and bandwidth, so you might as well put a stop to it.

rewrite ^!https https://$host$request_uri permanent;

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.