20

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;
    }
  }
}
4
  • Do these files actually exist on the filesystem? Commented May 20, 2014 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
    Commented May 20, 2014 at 19:23
  • @bdesham I can't reproduce. Here what I get with your config paste.ubuntu.com/7501697
    – Alexey Ten
    Commented May 22, 2014 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
    Commented May 22, 2014 at 15:31

7 Answers 7

31

Having this regex on your server block:

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

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

4
  • 2
    This addresses only part of the question.
    – bdesham
    Commented May 11, 2015 at 18:15
  • 8
    This addresses the full title of the question.
    – Jivan
    Commented Jun 28, 2016 at 7:43
  • Alternatively, rewrite ^(.+)/$ $1 permanent.
    – x-yuri
    Commented Jan 11, 2021 at 22:18
  • Alternatively: rewrite ^/(.*)/+$ /$1 permanent; ` Commented Aug 15, 2023 at 13:34
19

Never use rewrite:

  location ~ (?<no_slash>.*)/$ {
       return 301 $scheme://$host$no_slash;
  }
7
  • 3
    Can you expand on why you don’t think rewrite is a good idea?
    – bdesham
    Commented Jan 6, 2018 at 4:50
  • Read nginx guide: nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls Commented Jan 7, 2018 at 0:58
  • 2
    This is a good answer, I don't know why it's downvoted. The article 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 (?<no_slash>.+)/$ instead to not redirect the home page.
    – Razor
    Commented Nov 7, 2019 at 4:22
  • 5
    Used .+ to ensure alteast 1 char match to avoid infinite redirects when accessing root / path.
    – Prasanth
    Commented Jul 17, 2020 at 19:38
  • 5
    Alternatively, location ~ ^(.+)/$ { return 301 $1$is_args$args; }.
    – x-yuri
    Commented Jan 11, 2021 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;
  }
}
2
  • 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? Commented Feb 13, 2018 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
    Commented May 16, 2023 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.

-1
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

-1
#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.

-1

After careful docs reading, consulting with ChatGPT and evaluating them, reading some Q&As in Stackoverflow. Here is my final solution. I'll update this answer if I come across any new insight or improvements.

Why

  1. I have a dockerized app
  2. I am using same dockerfile for deployment
  3. Our QA uses same dockerfile to have the app up and running to perform manual testing and or e2e

What should we do now?

Add this to your NGINX config file:

location ~ ^(.+)/$ { return 301 $scheme://$http_host$1; }

JFI

  1. I used $http_host* instead of $host. Why? Briefly touching the ocean: $http_host is always the value of the Host header field in the request header. READ MORE.
  2. $host was redirecting my app in local env from http://localhost:3000/ to http://localhost. Not desirable at all.
  3. Did not face any issue in the server (deployed version of app), not yet at least 😁.
  4. Did not use any if clause since it is not appreciated by NGINX, besides that fact our location block gonna redirect soly when URL has a trailing slash :) and the rest of the times you could say it is idle 😁.

*BTW I also did not find a case that HOST header was not sent to NGINX so I guess it is safe. But if you know any exception please comment it or edit this answer. I would be more than happy to address any improvement 🙏.

1
  • 1
    Chatgpt can give incorrect answers
    – Turdie
    Commented Jan 6 at 4:31

You must log in to answer this question.

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