You know how sometimes you copy an nginx config snippet and it just... doesn't work? You stare at it, reload, check the logs, and nothing seems wrong. Then you spot it: a missing or extra slash at the end of the proxy_pass line. It’s one of those tiny details that silently breaks your setup.
Let’s look at two examples.
First, with a trailing slash:
location /app/ {
proxy_pass http://backend/;
}
Here, nginx replaces the matched part (/app/) with the URI in proxy_pass (http://backend/). So a request to /app/index.html goes to http://backend/index.html.
Now, without the trailing slash:
location /app/ {
proxy_pass http://backend;
}
In this case, nginx passes the entire request URI unchanged. So /app/index.html becomes http://backend/app/index.html.
The difference is subtle but important. If your backend expects requests at the root (like /index.html), the first form is what you want. If it expects the path to be preserved (like /app/index.html), the second form works.
I’ve seen this trip up people when they move from a location block that matches a prefix to one that’s more specific. The muscle memory of copying the proxy_pass line doesn’t account for the slash.
It’s not a bug. It’s documented behavior. But it’s easy to overlook when you’re in a hurry. Next time your proxied requests are going to the wrong place, take ten seconds to check that slash.
Top comments (1)
This is a great example of how a one-character difference in infrastructure configuration can completely change application behavior.
The important detail is that proxy_pass with a URI component triggers nginx’s URI replacement behavior. With:
location /app/ { proxy_pass backend/; }
nginx effectively replaces the matched /app/ prefix, so /app/index.html becomes /index.html upstream.
Whereas:
location /app/ { proxy_pass backend; }
preserves the original request URI, sending /app/index.html upstream.
One additional detail worth remembering: the behavior becomes even more interesting when location uses regex or rewrite directives. In those cases, nginx may not have enough information to determine the replaced URI automatically, and explicit handling is often safer.
For production systems, I usually recommend validating this with an upstream access log that records $request_uri (client-side URI) alongside the upstream URI/status. It makes these routing issues immediately visible instead of debugging them from the application layer.
Small nginx details like this are exactly why reproducible configs, integration tests, and documented routing contracts matter. A 30-second config review can save hours of debugging.
Your explanation is concise and technically useful. 👍