{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreig5ptrnyymwrwson5lckqzl4c3mhtwhsmkbzdfwft3q6jijg27iym",
    "uri": "at://did:plc:25rdn5elo5izoxrmtis34zuk/app.bsky.feed.post/3mowvpavlhob2"
  },
  "coverImage": {
    "$type": "blob",
    "ref": {
      "$link": "bafkreigv4hnvzb5hwhraxp6htu5arffjmzovzbgtsrrwdkk7fcp6mo36vm"
    },
    "mimeType": "image/webp",
    "size": 368032
  },
  "path": "/rohitsharmaj7/trailing-and-non-trailing-slash-in-nginx-1ghp",
  "publishedAt": "2026-06-23T07:22:42.000Z",
  "site": "https://dev.to",
  "tags": [
    "backend",
    "devops",
    "tutorial",
    "webdev"
  ],
  "textContent": "_**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_\n\n**There are 2 Cases:**\n\n**CASE 1: proxy_pass WITHOUT trailing slash**\n\n\n\n    location /api/ {\n        proxy_pass http://localhost:3000;\n    }\n\n\nnotice\n\n\n\n    http://localhost:3000\n                         ↑\n                     no slash\n\n\nrequest\n\n\n\n    http://localhost/api/users\n\n\nWhat Nginx sends to backend\n\n\n\n    http://localhost:3000/api/users\n\n\n**The entire original URI is preserved.**\n\n**CASE 2: proxy_pass WITH trailing slash**\n\n\n\n    location /api/ {\n        proxy_pass http://localhost:3000/;\n    }\n\n\nnotice\n\n\n\n    http://localhost:3000/\n                         ↑\n                      slash\n\n\nrequest\n\n\n\n    http://localhost/api/users\n\n\nwhat backend receives\n\n\n\n    http://localhost:3000/users\n\n\n**Nginx removes the matched location (/api/) and replaces it with /**\n\n**VISUAL REPRESENTATION (Without Slash)**\n\n\n\n    Client:\n        /api/users\n\n    location:\n        /api/\n\n    proxy_pass:\n        http://localhost:3000\n\n    Result:\n        http://localhost:3000/api/users\n\n\n**VISUAL REPRESENTATION (With Slash)**\n\n\n\n    Client:\n        /api/users\n\n    location:\n        /api/\n\n    proxy_pass:\n        http://localhost:3000/\n\n    Result:\n        http://localhost:3000/users\n\n\n**Why? Because internally Nginx does:**\n\nNO SLASH\n\n\n\n    backend_url + original_uri\n\n    http://localhost:3000\n    +\n    /api/users\n\n    =\n    http://localhost:3000/api/users\n\n\nWITH SLASH\n\n\n\n    replace(location_prefix, proxy_pass_uri)\n\n    /api/users\n    ↓ remove /api/\n    /users\n\n    prepend /\n    ↓\n\n    http://localhost:3000/users\n\n\n**In simpler words, think of it as:**\n\nNo slash (Keep the original URI)\nSlash (Replace the matched prefix)\n\nNginx does NOT look at whether /api/ exists in the request and remove it automatically.\n\nIt only removes the matched location prefix when proxy_pass contains a URI part (a trailing slash or some path).\n\nUnderstanding the both cases carefully\n**Case 1: No trailing slash**\n\n\n\n    location /api/ {\n        proxy_pass http://localhost:3000;\n    }\n\n\nnotice\n\n\n\n    proxy_pass = http://localhost:3000\n\n\nThere is:\n\n\n\n    scheme → http\n    host → localhost\n    port → 3000\n    URI part → ❌ NONE\n\n\nBecause there is no URI part, Nginx simply forwards the entire original URI.\n\n`Request : /api/users`\n\nNginx does\n\n\n\n    http://localhost:3000\n    +\n    /api/users\n    =\n    http://localhost:3000/api/users\n\n\n**No replacement happens.**\n\n**CASE 2: TRAILING SLASH**\n\n\n\n    location /api/ {\n        proxy_pass http://localhost:3000/;\n    }\n\n\nnotice\n\n\n\n    proxy_pass = http://localhost:3000/\n\n\nThere is / at end which is a URI part\n\n\n\n    /\n\n\nAs there is / in the URI part\n\n\n\n    scheme → http\n    host → localhost\n    port → 3000\n    URI part → /\n\n\n**Now Nginx switches to replacement mode.**\n\n\n\n    Request: /api/users\n    Matched location: /api/\n    Remaining: users\n    Result: http://localhost:3000/users\n\n\nWhy does / count as a URI?\n\nBecause internally Nginx parses:\n\n`http://localhost:3000`\n\nas:\n\n\n\n    host = localhost\n    port = 3000\n    uri = NULL\n\n\n\nbut\n\n`http://localhost:3000/`\n\nas:\n\n\n\n    host = localhost\n    port = 3000\n    uri = /\n\n\n**That tiny / changes the algorithm completely.**\n\n**Internal logic (simplified). Nginx does something like:**\n\n\n\n    if (proxy_pass_contains_uri) {\n        replace_location_prefix();\n    }\n    else {\n        append_original_uri();\n    }\n\n\nOR IN SOME SIMPLE WORDS, How Nginx actually thinks\n\n\n\n    Did proxy_pass contain a URI part?\n                |\n          +-----+-----+\n          |           |\n         NO          YES\n          |           |\n    Keep URI      Replace matched location prefix\n    as-is         with proxy_pass URI\n",
  "title": "Trailing and Non Trailing Slash in Nginx"
}