External Publication
Visit Post

Trailing and Non Trailing Slash in Nginx

DEV Community [Unofficial] June 23, 2026
Source

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;
}

notice

http://localhost:3000
                     ↑
                 no slash

request

http://localhost/api/users

What Nginx sends to backend

http://localhost:3000/api/users

The entire original URI is preserved.

CASE 2: proxy_pass WITH trailing slash

location /api/ {
    proxy_pass http://localhost:3000/;
}

notice

http://localhost:3000/
                     ↑
                  slash

request

http://localhost/api/users

what backend receives

http://localhost:3000/users

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

VISUAL REPRESENTATION (With Slash)

Client:
    /api/users

location:
    /api/

proxy_pass:
    http://localhost:3000/

Result:
    http://localhost:3000/users

Why? Because internally Nginx does:

NO SLASH

backend_url + original_uri

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

=
http://localhost:3000/api/users

WITH SLASH

replace(location_prefix, proxy_pass_uri)

/api/users
↓ remove /api/
/users

prepend /
↓

http://localhost:3000/users

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;
}

notice

proxy_pass = http://localhost:3000

There is:

scheme → http
host → localhost
port → 3000
URI part → ❌ NONE

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

No replacement happens.

CASE 2: TRAILING SLASH

location /api/ {
    proxy_pass http://localhost:3000/;
}

notice

proxy_pass = http://localhost:3000/

There is / at end which is a URI part

/

As there is / in the URI part

scheme → http
host → localhost
port → 3000
URI part → /

Now Nginx switches to replacement mode.

Request: /api/users
Matched location: /api/
Remaining: users
Result: http://localhost:3000/users

Why does / count as a URI?

Because internally Nginx parses:

http://localhost:3000

as:

host = localhost
port = 3000
uri = NULL

but

http://localhost:3000/

as:

host = localhost
port = 3000
uri = /

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();
}

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

Discussion in the ATmosphere

Loading comments...