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:3000as: host = localhost, port = 3000, uri = NULLhttp://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.