Understanding the NGINX proxy_pass trailing slash behavior is essential for anyone configuring a reverse proxy. If you have ever searched for “NGINX proxy_pass not working” or spent hours debugging why your backend receives the wrong URL, the answer is almost always related to trailing slashes. This comprehensive guide explains exactly how URL transformation works, with verified examples you can test yourself.
The Core Rule: URI in proxy_pass Determines Everything
The single most important rule to understand is this:
If proxy_pass contains a URI (anything after the server address), NGINX strips the matching location prefix and appends the remainder to the proxy_pass URI. If proxy_pass has no URI, NGINX passes the original request URI unchanged.
A URI in proxy_pass includes:
- A trailing slash, for example: proxy_pass http://backend/
- A path, for example: proxy_pass http://backend/api
- A path with trailing slash, for example: proxy_pass http://backend/api/
No URI means just the server address with no slash after the hostname, for example: proxy_pass http://backend. This behavior is documented in the official NGINX proxy_pass documentation.
For a complete introduction to NGINX reverse proxy configuration, see our NGINX Reverse Proxy Guide.
Quick Reference Table
Before diving into details, here is the complete reference for how the NGINX proxy_pass trailing slash affects URL transformation:
| Location | proxy_pass | Request | Backend Receives |
|---|---|---|---|
| /app/ | http://backend | /app/users | /app/users |
| /app/ | http://backend/ | /app/users | /users |
| /app/ | http://backend/api | /app/users | /apiusers |
| /app/ | http://backend/api/ | /app/users | /api/users |
| /app | http://backend/ | /app/users | //users |
| /app | http://backend | /app/users | /app/users |
Notice the third row. When proxy_pass ends without a trailing slash but contains a path, NGINX concatenates directly without adding a slash. This causes /apiusers instead of /api/users. This is the most common cause of “proxy_pass not working” issues.
Detailed Explanation of Each Scenario
Scenario 1: No URI in proxy_pass (Original URI Preserved)
location /api/ {
proxy_pass http://backend;
}
Request: /api/users?id=123
Backend receives: /api/users?id=123
When proxy_pass has no URI (no path, no trailing slash), NGINX passes the entire original request URI to the backend unchanged. This is the simplest configuration.
Use this when: Your backend expects the same path structure as your frontend, or you want to proxy requests without any URL transformation.
Scenario 2: Trailing Slash Only (Location Prefix Stripped)
location /api/ {
proxy_pass http://backend/;
}
Request: /api/users?id=123
Backend receives: /users?id=123
The trailing slash after the hostname tells NGINX: “This proxy_pass contains a URI.” NGINX then strips the location prefix (/api) from the request URI and appends the remainder (/users?id=123) to the proxy_pass URI (/).
The formula is: proxy_pass_URI + (request_URI - location_prefix)
Use this when: You want to remove a prefix. For example, exposing /api/ externally while your backend expects requests at the root.
Scenario 3: Path Without Trailing Slash (The Bug-Prone Configuration)
location /api/ {
proxy_pass http://backend/v2;
}
Request: /api/users
Backend receives: /v2users (WRONG!)
This is where most people encounter “proxy_pass not working” problems. NGINX strips the location prefix (/api) and concatenates the remainder (/users) directly to the proxy_pass URI (/v2). Since there is no trailing slash, you get /v2 + /users = /v2users.
Never use this configuration. Always add a trailing slash to paths in proxy_pass.
Scenario 4: Path With Trailing Slash (The Correct Way)
location /api/ {
proxy_pass http://backend/v2/;
}
Request: /api/users
Backend receives: /v2/users (Correct!)
Adding the trailing slash to /v2/ ensures proper URL construction. NGINX strips /api from the request and appends /users to /v2/, giving you the correct /v2/users.
Use this when: You need to rewrite URLs to a different base path on your backend.
Scenario 5: Location Without Trailing Slash (Double Slash Problem)
location /app {
proxy_pass http://backend/;
}
Request: /app/users
Backend receives: //users (Double slash!)
When your location does not have a trailing slash but your proxy_pass does, you can get double slashes. NGINX strips /app from /app/users, leaving /users. It then appends this to the proxy_pass URI /, resulting in //users.
Most backends handle double slashes gracefully, but some don’t. For clean URLs, ensure consistency between your location and proxy_pass configurations.
Scenario 6: Neither Has Trailing Slash
location /app {
proxy_pass http://backend;
}
Request: /app/users
Backend receives: /app/users
When neither has a URI/trailing slash, the original URI passes through unchanged.
Why Does This Happen? The NGINX Source Code Explanation
Looking at the NGINX source code in ngx_http_proxy_module.c, the URL construction logic at line 1301 reveals the NGINX proxy_pass trailing slash mechanism:
loc_len = (r->valid_location && ctx->vars.uri.len) ?
plcf->location.len : 0;
This determines how many characters to strip from the beginning of the request URI:
- If
ctx->vars.uri.len > 0(proxy_pass has a URI), stripplcf->location.lencharacters (the location prefix length) - If
ctx->vars.uri.len == 0(no URI in proxy_pass), strip nothing (loc_len = 0)
The upstream request URI is then constructed by:
- Copying the proxy_pass URI (if any)
- Appending
request_uri[loc_len:](the request URI after stripping the location prefix)
This is why the trailing slash matters so much. When proxy_pass is http://backend/api (no trailing slash), the URI component is /api. When proxy_pass is http://backend/api/ (with trailing slash), the URI component is /api/. The difference of one character changes everything.
Regex Locations: A Special Case
With regex locations, you cannot include a URI in proxy_pass. NGINX explicitly forbids this:
# This will NOT work - NGINX refuses to start
location ~ ^/api/(.*)$ {
proxy_pass http://backend/v2/; # ERROR!
}
Error message: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block
This restriction exists because NGINX cannot determine what part of the matched URI should be stripped. Instead, use rewrite with the break flag:
location ~ ^/api/(.*)$ {
rewrite ^/api/(.*)$ /v2/$1 break;
proxy_pass http://backend;
}
Request: /api/users/123
Backend receives: /v2/users/123
The rewrite directive transforms the URI first, then proxy_pass (without a URI) passes it unchanged. For more on rewrite rules, see our NGINX Rewrite Rules Guide.
For a detailed explanation of how NGINX location matching works, read our NGINX Location Priority Guide.
The Decision Flowchart
Use this flowchart to determine the correct configuration:
START: What do you want to achieve?
│
├─► Keep original URI unchanged?
│ └─► Use: proxy_pass http://backend;
│ (No trailing slash, no path)
│
├─► Strip location prefix only?
│ └─► Use: proxy_pass http://backend/;
│ (Trailing slash only)
│
├─► Rewrite to different base path?
│ └─► Use: proxy_pass http://backend/newpath/;
│ (Path WITH trailing slash)
│
└─► Using regex location?
└─► Use rewrite + proxy_pass without URI:
rewrite ^/old/(.*)$ /new/$1 break;
proxy_pass http://backend;
Common Mistakes and How to Fix Them
Mistake 1: Missing Trailing Slash on Path
Wrong:
location /api/ {
proxy_pass http://backend/v2;
}
Right:
location /api/ {
proxy_pass http://backend/v2/;
}
Mistake 2: Inconsistent Trailing Slashes
Problematic:
location /app {
proxy_pass http://backend/;
}
Better:
location /app/ {
proxy_pass http://backend/;
}
Mistake 3: Trying URI in Regex Location
Wrong:
location ~ ^/api/v[0-9]+/(.*)$ {
proxy_pass http://backend/;
}
Right:
location ~ ^/api/v[0-9]+/(.*)$ {
rewrite ^/api/v[0-9]+/(.*)$ /$1 break;
proxy_pass http://backend;
}
Mistake 4: Forgetting Query Strings
Query strings are always preserved regardless of your configuration. You do not need to do anything special:
location /api/ {
proxy_pass http://backend/v2/;
}
Request: /api/users?page=1&sort=name
Backend receives: /v2/users?page=1&sort=name
Real-World Configuration Examples
Example 1: API Gateway Pattern
Route different API versions to different backends:
# API v1 - legacy backend
location /api/v1/ {
proxy_pass http://legacy-backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# API v2 - new backend
location /api/v2/ {
proxy_pass http://new-backend:8080/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
Requests:
/api/v1/usersgoes to http://legacy-backend:8080/users/api/v2/usersgoes to http://new-backend:8080/users
For high-availability setups, combine this with NGINX Load Balancing.
Example 2: Microservices Routing
Route to different services based on path:
location /users/ {
proxy_pass http://user-service:3000/;
}
location /orders/ {
proxy_pass http://order-service:3001/;
}
location /products/ {
proxy_pass http://product-service:3002/;
}
Example 3: Adding Prefix to Backend
Your backend expects /internal/api/ but you expose /api/:
location /api/ {
proxy_pass http://backend:8080/internal/api/;
}
Request: /api/users
Backend receives: /internal/api/users
Example 4: Stripping Multiple Path Segments
Using regex when you need complex transformations:
location ~ ^/services/([^/]+)/api/(.*)$ {
rewrite ^/services/([^/]+)/api/(.*)$ /$1/$2 break;
proxy_pass http://backend;
}
Request: /services/users/api/list
Backend receives: /users/list
Testing Your Configuration
Before deploying, test your configuration:
Step 1: Validate NGINX syntax
nginx -t
Step 2: Create a test backend that echoes requests
server {
listen 8080;
location / {
return 200 "URI: $uri\nArgs: $args\n";
default_type text/plain;
}
}
Step 3: Test with curl
curl http://localhost/your-location/test/path?query=value
Step 4: Check NGINX error logs for the upstream URI
tail -f /var/log/nginx/error.log
The error log shows the exact URI sent to upstream when there are connection issues.
Troubleshooting Common Issues
If you encounter 502 errors after configuring proxy_pass, check our NGINX 502 Bad Gateway Guide. The problem may be SELinux blocking network connections rather than a URL rewriting issue.
For timeout-related issues when proxying to slow backends, see our NGINX Timeout Guide.
Performance Considerations
URL rewriting with proxy_pass happens at the NGINX level and adds negligible overhead. However, consider these points:
- Regex locations are slightly slower than prefix locations. Use prefix locations when possible.
- Rewrite rules add minimal processing time but can make configuration harder to debug.
- Consistent patterns across your configuration make maintenance easier.
For optimizing connection reuse to your backends, see our NGINX Upstream Keepalive Guide.
Summary
The NGINX proxy_pass trailing slash behavior follows a simple rule: if proxy_pass contains a URI (including just a trailing slash), NGINX strips the location prefix and appends the remainder. If proxy_pass has no URI, the original request passes unchanged.
Key takeaways:
- No trailing slash (http://backend) preserves the original URI
- Trailing slash only (http://backend/) strips the location prefix
- Path with slash (http://backend/path/) replaces the prefix with a new path
- Path without slash (http://backend/path) causes broken concatenation – avoid this
- Regex locations cannot have URI in proxy_pass; use
rewriteinstead
When in doubt, add the trailing slash to both your location and your proxy_pass URI. Consistency prevents surprises and eliminates the frustrating debugging sessions that plague so many administrators.
Understanding these URL transformation rules will save you hours of debugging and help you design clean, maintainable NGINX configurations for your reverse proxy and load balancing needs.

