1

My basic goal is to serve the following clean URLs with nginx:

  • / serves /index.html
  • /abc/ serves /abc/index.html
  • /abc/def serves /abc/def.html
  • /abc redirects to /abc/

In order to have canonical names for each resource, I also want to normalize any URL with superfluous file names or extensions:

  • /index.html redirects to /
  • /abc/index.html redirects to /abc/
  • /abc/def.html redirects to /abc/def

The directives I thought would accomplish this:

index index.html;
try_files $uri.html $uri $uri/ =404;

# Redirect */index and */index.html to *.
rewrite ^(.*)/index(\.html)?$ $1 permanent;
# Redirect *.html to *.
rewrite ^(.+)\.html$          $1 permanent;

However, the result of this is different than I expected:

  • /, /index and /index.html all redirect to / (loop).
  • /abc and /abc/ both redirect to /abc/ (loop).

(It works as designed for /abc/def.html and /abc/def; only the directory URLs don't work.)

I'm not sure what is happening here; maybe I'm misunderstanding how the rewrite works?

(I already tried using location blocks instead, but this also results in loops as try_files performs an internal redirect to the location block that sends the HTTP 301.)

Edit: Fundamentally, I need something like a location block that only matches the original request URI, but is ignored for the purpose of internal redirects, so it doesn't create a loop in combination with the try_files directive.

2 Answers 2

1

You might be looking for a solution like the one explained here:

server {
    listen       80;
    server_name  mysite.com;

    index index.html;
    root /var/www/mysite/public;

    location / { 
        try_files $uri $uri/ @htmlext;
    }   

    location ~ \.html$ {
        try_files $uri =404;
    }   

    location @htmlext {
        rewrite ^(.*)$ $1.html last;
    }   
}
1
  • 1
    Hm, that doesn't look quite correct - although the clean URLs are now served, the non-clean ones are also served rather than redirected. That behavior could have also been accomplished with only the try_files $uri $uri.html $uri/ =404; index index.html; directives. Did I miss something? Feb 13, 2018 at 17:04
0

I believe I have found a solution, though I'm not experienced enough to tell if this could break in special cases or could be solved more easily in another way.

Basically, the problem is that a location ~ ... {} block does not only get matched on the original request URI, but also on the result of try_files and other rewrites. So if I have a location block to strip off the index.html or .html with a redirect, then it will not only run when a client requests index.html or abc.html directly, but also if the client requests / or abc and the server internally redirects these to /index.html and abc.html respectively, causing a redirect loop.

However, the redirect module provides an if directive which can check the $request_uri variable - this remains unchanged by internal redirects:

index index.html;
try_files $uri $uri.html $uri/ =404;

# like "location ~", but only for matching the original request. 
if ($request_uri ~ /index(.html)?$) {
  rewrite ^(.*/)index(.html)?$ $1 permanent;
}
if ($request_uri ~ \.html$) {
  rewrite ^(.*)\.html$ $1 permanent;
}

(Note that all of these directives now exist in the server context, without any location blocks.)

1
  • There's a problem with $request_uri: It's the raw URI part of the incoming request, meaning it will retain any query parameters if present.
    – iBug
    Jan 24 at 17:51

You must log in to answer this question.

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