Stripping index.html and .html from URLs with nginx

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.


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;
    }   
}