19

I would like the following URLs on my site to be equivalent:

/foo/bar
/foo/bar/
/foo/bar/index.html

and further I would like the second two forms to issue HTTP 301 redirects to the first form. I am just serving static pages, and they are arranged according to the third form. (In other words, when a user requests /foo/bar they should receive the file at /usr/share/.../foo/bar/index.html).

My nginx.conf currently contains the following:

rewrite ^(.+)/$ $1 permanent;
index index.html;
try_files $uri $uri/index.html =404;

This works for requests for /foo/bar/index.html, but when I request /foo/bar or /foo/bar/ Safari tells me that “too many redirects occurred”—I assume there’s an infinite redirect loop or something like that. How can I get nginx to map URLs to files in the way I’ve described?

Edit: My full configuration

Here is my entire nginx.conf with my domain name replaced with “example.com”.

user www-data;
worker_processes 1;
pid /run/nginx.pid;

events {
  worker_connections 768;
}

http {
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;
  server_tokens off;

  server_names_hash_bucket_size 64;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss application/atom+xml text/javascript image/svg+xml;

  server {
    server_name www.example.com;
    listen 80;
    return 301 $scheme://example.com$request_uri;
  }

  server {
    server_name example.com 123.45.67.89 localhost;
    listen 80 default_server;

    # Redirect /foobar/ to /foobar
    rewrite ^(.+)/$ $1 permanent;

    root /usr/share/nginx/www/example.com;
    index index.html;
    try_files $uri $uri/index.html =404;

    error_page 404 /404.html;
    error_page 500 502 503 504 /50x.html;

    location = /50x.html {
      root /usr/share/nginx/html;
    }
  }
}
bdesham
  • 1,725
  • 4
  • 14
  • 22
  • Do these files actually exist on the filesystem? – Michael Hampton May 20 '14 at 18:51
  • @MichaelHampton Yes. Requests for `/foo/bar/index.html` should return the file at `/usr/share/nginx/www/foo/bar/index.html` or however it’s set up. All of the paths on the website correspond directly to filesystem paths. – bdesham May 20 '14 at 19:23
  • @bdesham I can't reproduce. Here what I get with your config http://paste.ubuntu.com/7501697/ – Alexey Ten May 22 '14 at 14:52
  • @AlexeyTen It’s odd that you’re getting something different. Thanks for looking into it. I’ve posted a configuration that ended up working for me. – bdesham May 22 '14 at 15:31

6 Answers6

30

Having this regex on your server block:

rewrite ^/(.*)/$ /$1 permanent;

would redirect all trailing slash URL's to the respective non trailing slash.

Francisco Costa
  • 401
  • 4
  • 5
17

Never use rewrite:

  location ~ (?<no_slash>.*)/$ {
       return 301 $scheme://$host$no_slash;
  }
Eldar Agalarov
  • 279
  • 2
  • 3
  • 3
    Can you expand on why you don’t think rewrite is a good idea? – bdesham Jan 06 '18 at 04:50
  • Read nginx guide: https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/ – Eldar Agalarov Jan 07 '18 at 00:58
  • The reason your links suggests avoiding rewrites is for impoved legibility, not because they can cause unintended side effects. Your answer is far less readable than `rewrite ^(.+)/+$ $1 permanent;` – chrBrd Oct 04 '18 at 12:44
  • 2
    This is a good answer, I don't know why it's downvoted. [The article](https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#taxing-rewrites) has a heading "Taxing Rewrites" that explains why `rewrite` can be bad. That's being said, the provided answer does also capture and match the URI, I'm not sure if will improve performance, it needs testing. Use this regex `(?.+)/$` instead to not redirect the home page. – Razor Nov 07 '19 at 04:22
  • 4
    Used `.+` to ensure alteast 1 char match to avoid infinite redirects when accessing root `/` path. – Prasanth Jul 17 '20 at 19:38
  • 5
    Alternatively, `location ~ ^(.+)/$ { return 301 $1$is_args$args; }`. – x-yuri Jan 11 '21 at 22:17
6

I was able to get my desired behavior by using this as the final server block in my configuration:

server {
  server_name example.com 123.45.67.89 localhost;
  listen 80 default_server;

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

  root /usr/share/nginx/www/example.com;
  index index.html;
  try_files $uri $uri/index.html =404;

  error_page 404 /404.html;
  error_page 500 502 503 504 /50x.html;

  location = /50x.html {
    root /usr/share/nginx/html;
  }
}
bdesham
  • 1,725
  • 4
  • 14
  • 22
  • This doesn't seem to work for me - the `/index.html` URL is answered with HTTP 200 instead of a redirect; the "rewrite" lines are ignored. Is this still current? – Christoph Burschka Feb 13 '18 at 11:39
  • @ChristophBurschka I think the issue is the line `rewrite ^(.+)/index.html$ $1`. If you change the `.+` to `.*`, I’m guessing that requests for `/index.html` will start being processed. – bdesham May 16 '23 at 20:42
2
    location ~ ^(.+)/$ {
      return 301 $scheme://$host$1;
    }

Used this to host sites from Next.js (v9&v10) static site generations where host/url works while host/url/ triggers an Nginx 404.

KuN
  • 211
  • 2
  • 10
0
#removing trailing slashes at the end of url
if ($request_uri ~ (.*?)(\/+)$ ) {
    return 301 $scheme://$host$1;
}

This rule will remove(redirect tot non-slashed url) any number of trailing slashes in the end of url.

Sid
  • 101
  • 1
0
if ($request_uri ~ (.*?\/)(\/+)$ ) {
return 301 $scheme://$host$1;
}

This rule will take care of any number of trailing slashes and will retain URL. It will also take care of base URL trailing slashes

RalfFriedl
  • 3,108
  • 4
  • 13
  • 17