How to prevent nginx redirecting from HTTPS to HTTP on AWS?

I have a website served by nginx behind an AWS ELB Load Balancer. Only HTTPS is enabled on the load balancer.

Requesting individual files, or directories with a trailing slash, work fine. However, requesting directories without a trailing slash don't work.

The reason is, when I request the directory without the trailing slash, nginx does a redirect to the path with the trailing slash (that's OK), but it also changes from HTTPS to HTTP. The load balancer is configured to only allow HTTPS, so that doesn't work (timeout).

On the nginx logfile, I can see that the request reaches nginx, and that it's nginx that responds with the 301 Permanent Redirect (so it's not e.g. a problem with the load balancer setup).

10.100.10.15 - - [24/Nov/2017:15:41:08 +0000] "GET /admin HTTP/1.1" 301 178 "-" "Wget/1.18 (darwin16.0.0)"

When I request the URL via curl I see the redirect:

$ curl -v https://example.com/admin
*   Trying 1.2.3.4...
* TCP_NODELAY set
* Connected to example.com (1.2.3.4) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: example.com
* Server certificate: Amazon
* Server certificate: Amazon Root CA 1
* Server certificate: Starfield Services Root Certificate Authority - G2
> GET /admin HTTP/1.1
> Host: example.com
> User-Agent: curl/7.54.0
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Date: Mon, 27 Nov 2017 09:19:05 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Server: nginx
< Location: http://example.com/admin/
< X-UA-Compatible: IE=Edge
< 
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host example.com left intact

My nginx config file is just

server {
    root /var/www;
}

The /etc/nginx/nginx.conf is here.

I have tried server_name_in_redirect off but it did not make any difference.

I would like to avoid putting the hostname in the config file, because this is packed into a Docker image which is then deployed on different hosts (QA, Prod, etc.).

I would like nginx to do this redirect, but stay on HTTPS. What can I do?


The best place to fix the problem is where the SSL connection is terminated. If it was running nginx, you would use a proxy_redirect statement to map http to https in the Location header. I don't know AWS ELB so cannot comment on how to fix it there.

Certain circumstances causes nginx to respond with a redirect, and it assumes that the scheme is the same as the scheme used to connect to it (i.e. from AWS ELB). AFAIK there are three ways to mitigate the problem in the back-end nginx server.


1) From version 1.11.8 onwards, the absolute_redirect off; statement will cause the Location header to use a relative URL, which means that the scheme and domain name are missing.

server {
    absolute_redirect off;
    ...
}

See this document for more.


2) Inhibit the behaviour of appending a trailing / to directories by using a try_files statement:

server {
    root /path/to/files;

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

See this document for more.


3) Fix the problem with an explicit return statement.

server {
    root /path/to/files;

    location ~ [^/]$ {
        if (-d $request_filename) {
            return 302 https://$host$uri/$is_args$args;
        }
    }

    location / {
    }
    ...
}

See this document for more, and this caution on the use of if.


Try this

server {
  root /var/www;
  include /etc/nginx/basic.conf;
  try_files $uri $uri/;
}

If it doesn't work please edit your question to include whatever's in basic.conf and nginx.conf. Also do a curl showing the problem, including the response headers and the single corresponding Nginx access log file.