Search code examples
nginx

Removing all slashes at the end of the url


I am trying to remove any trailing slashes from the url. This works everywhere except the homepage.

Here's where it works:

  • sub.example.com/ => sub.example.com
  • sub.example.com/test/ => sub.example.com/test
  • sub.example.com/test/// => sub.example.com/test

Here's where it doesn't work:

  • sub.example.com/// => sub.example.com///

My config file:

server {
    server_name sub.example.com;
    root /var/www/example.com/;
    index index.php;
    charset utf-8;

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

    location / {
        try_files $uri /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    listen 443 ssl;

    #SSL settings
}

Solution

  • Looks like you don't understand some very important parts going under the hood.

    1. HTTP GET request can't contain an empty string as a path.

    When you type sub.example.com/ at your browser address bar, it is a browser who hides a trailing slash. This is applied only for the root requests, in any other case (including sub.example.com//, sub.example.com/test/, sub.example.com/test//, etc.) you'll see the full path at the address bar. No matter if you type that slash or not, HTTP request issued by the browser will look like

    GET / HTTP/1.1
    Host: sub.example.com
    ...
    

    2. rewrite nginx directive works with the normalized URI.

    Both location and rewrite directive works with so-called normalized URIs:

    The matching is performed against a normalized URI, after decoding the text encoded in the “%XX” form, resolving references to relative path components “.” and “..”, and possible compression of two or more adjacent slashes into a single slash.

    That means, for all the requests like sub.example.com/test/, sub.example.com/test//, sub.example.com/test///, etc., nginx see the normalized request URI as /test/ (that's the reason your rewrite rule works in a single step rather than the four-step sub.example.com/test/// -> sub.example.com/test// -> sub.example.com/test/ -> sub.example.com/test loop).

    And the same is true for any of the sub.example.com/, sub.example.com//, sub.example.com///, etc. requests, the normalized URI will be seen by nginx as / making any rewrite rule unusable.

    However, slashes compression can be turned off using the merge_slashes directive (read the security considerations). And to prevent multiply redirects where each redirect removes only a single trailing slash, use a non-greedy * and a greedy + quantifiers for your regex pattern:

    merge_slashes off;
    rewrite ^(/.*?)/+$ $1 permanent;