Nginx: Access same content from multiple URLs

rewrite in nginx doesn't lead to 301 status in general (unless explicitly configured), so 301 is probably caused by other parts of your nginx config.

rewrite is the proper way to achieve what you want, your aproach is right.


This is the default behavior of web servers showing directory indexes, for example Apache does the same. Whenever you request just "directory", it will redirect you to "directory/" like that.

Apache documentation is explicit about it, e.g. https://httpd.apache.org/docs/2.4/mod/mod_dir.html says

A "trailing slash" redirect is issued when the server receives a request for a URL http://servername/foo/dirname where dirname is a directory. Directories require a trailing slash, so mod_dir issues a redirect to http://servername/foo/dirname/.

It seems nginx documentation is a fair bit more coy about it, e.g. http://nginx.org/en/docs/http/ngx_http_autoindex_module.html just says:

The ngx_http_autoindex_module module processes requests ending with the slash character (‘/’) and produces a directory listing. Usually a request is passed to the ngx_http_autoindex_module module when the ngx_http_index_module module cannot find an index file.

It doesn't explain the previous step, how the request get to ending with the slash, that seems to be implicit.

There's another old question on the network about the same issue: https://stackoverflow.com/questions/15555428/nginx-causes-301-redirect-if-theres-no-trailing-slash


Okay, after some experimentation, I found a few different solutions.

To recap, I want /A to stay as /A in the user's browser (no redirect to /B), but I still want it to display the content from /B.

Option 1: Use a symlink (: This works, but you need access to the file system. Sidesteps the issue.

Option 2: Prevent the automatic trailing-slash 301 redirect by using an exact-match (=) location block:

location = /A {
    rewrite "" /B/;
}
# Note: the below should probably be inside a location block.
rewrite ^/A(.+)$ /B$1 last;

I found out about this from the Nginx docs:

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

Option 3: Use a redirect from /A to /A/, but do it manually, so it doesn't get returned to the client as a (rewritten) redirect to /B/:

location = /A {
    return 301 /A/;
}
location /A/ {
    rewrite ^/A/(.*)$ /B/$1 last;
}

I ended up going with option 3, since it makes /A behave just like /B, including the trailing-slash redirect (but to the right place, now).