DEV Community

Cover image for Trailing and Non Trailing Slash in Nginx
Rohit Sharma
Rohit Sharma

Posted on

Trailing and Non Trailing Slash in Nginx

Most tricky part of Nginx: The trailing slash behavior in proxy_pass is one of the most confusing parts of Nginx. Once you understand one rule, it becomes easy

There are 2 Cases:

CASE 1: proxy_pass WITHOUT trailing slash

location /api/ {
    proxy_pass http://localhost:3000;
}
Enter fullscreen mode Exit fullscreen mode

notice

http://localhost:3000
                     ↑
                 no slash
Enter fullscreen mode Exit fullscreen mode

request

http://localhost/api/users
Enter fullscreen mode Exit fullscreen mode

What Nginx sends to backend

http://localhost:3000/api/users
Enter fullscreen mode Exit fullscreen mode

The entire original URI is preserved.

CASE 2: proxy_pass WITH trailing slash

location /api/ {
    proxy_pass http://localhost:3000/;
}
Enter fullscreen mode Exit fullscreen mode

notice

http://localhost:3000/
                     ↑
                  slash
Enter fullscreen mode Exit fullscreen mode

request

http://localhost/api/users
Enter fullscreen mode Exit fullscreen mode

what backend receives

http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

Nginx removes the matched location (/api/) and replaces it with /

VISUAL REPRESENTATION (Without Slash)

Client:
    /api/users

location:
    /api/

proxy_pass:
    http://localhost:3000

Result:
    http://localhost:3000/api/users
Enter fullscreen mode Exit fullscreen mode

VISUAL REPRESENTATION (With Slash)

Client:
    /api/users

location:
    /api/

proxy_pass:
    http://localhost:3000/

Result:
    http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

Why? Because internally Nginx does:

NO SLASH

backend_url + original_uri

http://localhost:3000
+
/api/users

=
http://localhost:3000/api/users
Enter fullscreen mode Exit fullscreen mode

WITH SLASH

replace(location_prefix, proxy_pass_uri)

/api/users
↓ remove /api/
/users

prepend /
↓

http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

In simpler words, think of it as:

No slash (Keep the original URI)
Slash (Replace the matched prefix)

Nginx does NOT look at whether /api/ exists in the request and remove it automatically.

It only removes the matched location prefix when proxy_pass contains a URI part (a trailing slash or some path).

Understanding the both cases carefully
Case 1: No trailing slash

location /api/ {
    proxy_pass http://localhost:3000;
}
Enter fullscreen mode Exit fullscreen mode

notice

proxy_pass = http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

There is:

scheme → http
host → localhost
port → 3000
URI part → ❌ NONE
Enter fullscreen mode Exit fullscreen mode

Because there is no URI part, Nginx simply forwards the entire original URI.

Request : /api/users

Nginx does

http://localhost:3000
+
/api/users
=
http://localhost:3000/api/users
Enter fullscreen mode Exit fullscreen mode

No replacement happens.

CASE 2: TRAILING SLASH

location /api/ {
    proxy_pass http://localhost:3000/;
}
Enter fullscreen mode Exit fullscreen mode

notice

proxy_pass = http://localhost:3000/
Enter fullscreen mode Exit fullscreen mode

There is / at end which is a URI part

/
Enter fullscreen mode Exit fullscreen mode

As there is / in the URI part

scheme → http
host → localhost
port → 3000
URI part → /
Enter fullscreen mode Exit fullscreen mode

Now Nginx switches to replacement mode.

Request: /api/users
Matched location: /api/
Remaining: users
Result: http://localhost:3000/users
Enter fullscreen mode Exit fullscreen mode

Why does / count as a URI?

Because internally Nginx parses:

http://localhost:3000

as:

host = localhost
port = 3000
uri = NULL

Enter fullscreen mode Exit fullscreen mode

but

http://localhost:3000/

as:

host = localhost
port = 3000
uri = /
Enter fullscreen mode Exit fullscreen mode

That tiny / changes the algorithm completely.

Internal logic (simplified). Nginx does something like:

if (proxy_pass_contains_uri) {
    replace_location_prefix();
}
else {
    append_original_uri();
}
Enter fullscreen mode Exit fullscreen mode

OR IN SOME SIMPLE WORDS, How Nginx actually thinks

Did proxy_pass contain a URI part?
            |
      +-----+-----+
      |           |
     NO          YES
      |           |
Keep URI      Replace matched location prefix
as-is         with proxy_pass URI
Enter fullscreen mode Exit fullscreen mode

Top comments (0)