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
Top comments (0)