May 07 2021

Nginx Behind Reverse Proxy 301 https to http Redirect When URL has no Trailing Slash

Published by at 7:56 am under Nginx

An Nginx web server hosted on Kubernetes was sending back to me permanent 301 http redirects although I was sending https requests. The web server was behind a reverse proxy that was also running Nginx but it would be the same story with haproxy or another. Worst, the reverse proxy was redirecting http requests to https (this is normal behaviour) but without a trailing slash, creating a loop!

Nginx http redirect behind proxy


Whenever I tried to connect to https://mydomain/app, I got a redirect to http://mydomain/app/. And the other way around… A simple curl confirmed the redirect:

$ curl -v https://mydomain/app
[...]
 > 
 * Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
 < HTTP/2 301 
 < date: Thu, 06 May 2021 08:56:48 GMT
 < content-type: text/html
 < content-length: 170
 < location: http://mydomain/app/
 [...]
 <html>
 <head><title>301 Moved Permanently</title></head>
 <body>
 <center><h1>301 Moved Permanently</h1></center>
 <hr><center>nginx/1.19.10</center>
 </body>
 </html>


Nginx adds a trailing slash because that’s its default behaviour as describe on the Nginx documentation:

“In response to a request with URI equal to this string, but without the trailing slash, a permanent redirect with the code 301 will be returned to the requested URI with the slash appended.” If this is not desired, an exact match of the URI and location could be defined with 2 blocks:

location /app/ {
    [...]
}

location = /app {
    [...]
}


Right… but this cannot be done on the root directory and you don’t want to do this for every single directory. Don’t expect developers to give you a ring every time they create a new directory!


Accept URIs with and without a Trailing Slash

Nginx provides another way to accept URLs with or without a trailing slash, other than duplicating location blocks.
try_files lets you do that by checking the existence of files in the specified order and uses the first found file to process the request.
It is even possible to check a directory’s existence by appending a slash to $uri which gives the following configuration lines:

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


Sounds perfect but there’s a catch. Once I’ve reloaded Nginx, the web browser could fetch no more CSS and Javascript links.
Checking the code returned to the browser, CSS links have been changed from /css/my_file.css to /my_file.css.

The problem is that try_files will source the file $uri/index.html but leaves the URI as /app so any relative URI will be relative to / (or the parent directory) and not /app/.

OK, that works but I need to change all the links to absolute path. From a developer point of view, I would understand this is not something they want.


Relative Redirects

We want something cleaner but we can’t prevent Nginx’s default behaviour from appending a trailing slash.
Let’s focus on the https to http redirect. This is where the main problem lies and most browsers deny that kind of redirection… This is why the problem might be visible on curl but most browsers.

Nginx redirects to the full (or absolute) URL and a trailing slash. The definite solution is to do a relative redirect:

absolute_redirect off;


Reload nginx and run another test with curl:

$ curl -v https://mydomain/app
[...]
 > 
 * Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
 < HTTP/2 301 
 < date: Thu, 06 May 2021 08:56:48 GMT
 < content-type: text/html
 < content-length: 170
 < location: /app/
 [...]
 <html>
 <head><title>301 Moved Permanently</title></head>
 <body>
 <center><h1>301 Moved Permanently</h1></center>
 <hr><center>nginx/1.19.10</center>
 </body>
 </html>


The client will now just reconnect with the protocol he used in the first request.


One response so far

One Response to “Nginx Behind Reverse Proxy 301 https to http Redirect When URL has no Trailing Slash”

  1. daveon 31 May 2021 at 10:59 am

    As for Kubernetes redirection dropping the trailing slash, there is a new optional “reserve-trailing-slash” annotation to be set to “true” in order to keep trailing slashes in the Location header returned by a TLS redirect:

    https://github.com/kubernetes/ingress-nginx/pull/7144

Trackback URI | Comments RSS

Leave a Reply