3

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?

Adrian Smith
  • 276
  • 4
  • 10
  • Have you tried `absolute_redirect off;`? – Richard Smith Nov 24 '17 at 16:51
  • @RichardSmith - That command failed with `nginx: [emerg] unknown directive "absolute_redirect"` I googled for it in case it was misspelt but I couldn't find anything. Did you mean another command? – Adrian Smith Nov 27 '17 at 09:08
  • That directive is available from `nginx` version 1.11.8 onwards. It would mitigate the problem by removing the scheme and domain name from the HTTP Location header. – Richard Smith Nov 27 '17 at 14:41
  • @RichardSmith - Please write this as an answer, then we can discuss it there. – Adrian Smith Nov 28 '17 at 09:07
  • I have tried now with nginx/1.13.7 with that command but it makes no difference. I've also tried writing `server_name_in_redirect foo` and indeed it delivers an error, leading me to think the command is not being ignored. I still get `Location: http://example.com/admin/` (i.e. http not https) in the 301 response. – Adrian Smith Nov 28 '17 at 09:08
  • @RobinSalih - yes, that's what I'm doing, I think that's why nginx believes the client is requesting the resource through HTTP, and thus redirecting to HTTP. – Adrian Smith Nov 28 '17 at 09:54

4 Answers4

6

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.

Richard Smith
  • 12,834
  • 2
  • 21
  • 29
  • 1
    jeez, absolute_redirect off is my lifesaver, thanks a lot – user3527 Jan 18 '18 at 00:58
  • With Chrome 86 (October 2020) an unnoticed https to http redirect can now trigger an "insecure form" page. Having a redirect to https on your loadbalancer does not save you too, because https to http to https will still trigger the warning page on the first redirect. This answer is very helpful to prevent that from happening for nginx servers behind a reverse proxy. `absolute_redirect off` is a saver. – Haluk Oct 11 '20 at 21:27
  • `absolute_redirect off` did the job for me, thanks! – Peter Evans Feb 02 '21 at 10:15
1

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.

Tim
  • 31,888
  • 7
  • 52
  • 78
  • I have updated my question to include the `nginx.conf`, and removed the `basic.conf` from my config and my question as it didn't make any difference. I added `curl` information to my question. I tried your suggestion, alas when ELB requested "/" it showed `rewrite or internal redirection cycle while internally redirecting to "////////////"`. Thanks in advance for your help. – Adrian Smith Nov 27 '17 at 09:22
0

Not tested on AWS, however I solved with this line in each location block:

proxy_redirect ~^http:(.*)$ https:$1;
Pino
  • 101
  • 1
0

You can try to use a "evil if" sentence:

server {
    ...
    if ($http_x_forwarded_proto = 'http' ) {
        rewrite ^ https://$host$request_uri? permanent;
    }
    ...
}
MAL
  • 3
  • 1
  • 3