4

I would like to force HTTPS and the apex domain (e.g. https://example.com) in my application through nginx configuration using location blocks. I currently have the following nginx_app.conf file (which works with both the apex and the www subdomain, and both http and https):

location / {
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    rewrite ^(.*)$ /app.php/$1 last;
}

location ~ ^/(app|config)\.php(/|$) {
    # fastcgi_pass directives go here...
}

To force the apex domain and https, I tried using if-statements as follows, checking for the $scheme and $host variables, but I get an error that the page is not redirecting properly. I also added an HSTS directive.

location / {
    if ($scheme = http) {
        rewrite ^/(.*) https://$host/$1 permanent;
    }
    if ($host = www.example.com) {
        rewrite ^/(.*) https://example.com/$1 permanent;
    }
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    rewrite ^(.*)$ /app.php/$1 last;
}

location ~ ^/(app|config)\.php(/|$) {
    # fastcgi_pass directives go here...
    add_header Strict-Transport-Security "max-age=86400";
}

What is the proper way to force http and the apex domain with nginx configuration? As an aside, I'm using heroku (with DNSimple) to deploy my app so I would like both the following domains to work: https://example.herokuapp.com and https://example.com.

UPDATE: I tried moving the if-statements outside the location block into the default server block (click here), and change the rewrites for returns as follows, but it still does not work. I still get "The page isn't redirecting properly" when requesting http, and "Unable to connect error" when requesting the www subdomain.

if ($scheme = http) {
    return 301 https://$host$request_uri;
}
if ($host = www.example.com) {
    return 301 https://example.com$request_uri;
}

location / {
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    rewrite ^(.*)$ /app.php/$1 last;
}

location ~ ^/(app|config)\.php(/|$) {
    # fastcgi_pass directives go here...
    add_header Strict-Transport-Security "max-age=86400";
}
3
  • 1
    Stop using if blocks. There's nothing you can't do without if blocks in your current configuration. Use locations with server_name and the right listen directive for each HTTP scheme. Oct 16, 2014 at 8:57
  • 1
    @XavierLucas The problem is that I don't think I can use the listen directive in the location block. In the official documentation for listen it indicates that it is used under the context of server. I'm using Heroku and I don't think I can modify the server block, that's why I'm asking for a solution using location blocks...
    – RayOnAir
    Oct 16, 2014 at 13:39
  • @RayOnAir Amen. Server block approaches and "If is evil" in this context is not helpful.
    – MrYellow
    Jun 21, 2018 at 23:31

2 Answers 2

9

1) The problem here is probably the Heroku load balancer. When the request of a visit comes in your app the request is HTTP again. It is just the internal routing. You can not test against $scheme. But Heroku set a $http_x_forwarded_proto header on those requests.

if ($http_x_forwarded_proto != "https") {
  return 301 https://$host$request_uri;
}

Source: https://discussion.heroku.com/t/force-ssl-and-no-www-with-nginx-configuration/856


2a) For directing to no-www you can use this:

server {
  listen <%= ENV["PORT"] %>;
  server_name "~^www\.(.*)$";
  return 301 https://$1$request_uri;
}

For testing you should use 302 instead of 301 because the browser will cache 301 redirects.

This one will redirect you to https as well. But only from the www subdomain, so you have to keep the above $http_x_forwarded_proto redirect.

2b) Another option is to use a server block with the www subdomain and redirect it to the non-www domain, like this:

server {
  listen <%= ENV["PORT"] %>;
  server_name www.example.com;

  location / {
    return 301 https://example.com$request_uri;
  }
}

server {
  listen <%= ENV["PORT"] %>;
  server_name example.com;

  location / {
    try_files $uri $uri/ /index.html =404;
  }
}

The <%= ENV["PORT"] %> code comes from a buildpack. On Heroku you can't listen to port 80.

3
  • 1
    Yes! that solves the part for forcing redirect to https; however, I still have not found a way to force no-www...
    – RayOnAir
    Nov 15, 2014 at 17:17
  • @RayOnAir any luck with finding final solution?
    – Dimitry K
    Apr 19, 2017 at 17:36
  • The if statement should be put inside the server clause.
    – alayor
    Oct 22, 2019 at 17:32
2

The best way is to do a 301 redirect.

server {
    listen         80;
    return 301 https://$host$request_uri;
}
3
  • Thanks, I tried using the return 301 https://$host$request_uri; within the if-statements in my location block, but it does not work. I don't think I can modify the server block in heroku (although I'm not 100% sure about that), which is why my question asks for a solution using location blocks...
    – RayOnAir
    Oct 16, 2014 at 2:01
  • Hmm. Are you using the SSL heroku addon? If so, you should have an HTTPS endpoint. You can check running: heroku certs The end point looks like this: example-2121.herokussl.com
    – Contarc
    Oct 16, 2014 at 2:28
  • I'm using the SSL heroku add-on, and all pages (with/without SSL, with/without www) were working with my initial nginx configuration shown in the Question. However, I started having problems when I tried to force redirects to SSL and no-www with the if-statements.
    – RayOnAir
    Oct 16, 2014 at 13:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .