Trailing and Non Trailing Slash in Nginx
DEV Community

Trailing and Non Trailing Slash in Nginx

Trailing and Non Trailing Slash in 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
  • 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
  as-is       location prefix
              with proxy_pass URI

Comments

No comments yet. Start the discussion.