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";
}
RayOnAir
  • 141
  • 1
  • 5
  • 1
    Stop using [if blocks](http://wiki.nginx.org/IfIsEvil). 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. – Xavier Lucas Oct 16 '14 at 08: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](http://nginx.org/en/docs/http/ngx_http_core_module.html#listen) 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 '14 at 13:39
  • @RayOnAir Amen. Server block approaches and "If is evil" in this context is not helpful. – MrYellow Jun 21 '18 at 23:31

2 Answers2

8

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.

adriaan
  • 181
  • 4
2

The best way is to do a 301 redirect.

server {
    listen         80;
    return 301 https://$host$request_uri;
}
Contarc
  • 21
  • 1
  • 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 '14 at 02: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 '14 at 02: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 '14 at 13:45