Fixing Nginx proxy_pass Trailing Slash Resolution for Subfolders on Ubuntu 22.04 LTS
Resolve Nginx proxy_pass subfolder routing issues caused by trailing slash discrepancies on Ubuntu 22.04 LTS. Correctly configure proxy rules for seamless application access.
Resolve Nginx proxy_pass subfolder routing issues caused by trailing slash discrepancies on Ubuntu 22.04 LTS. Correctly configure proxy rules for seamless application access.
This guide provides a comprehensive solution for troubleshooting and resolving common Nginx proxy_pass issues related to trailing slashes when configuring a reverse proxy for a subfolder application on Ubuntu 22.04 LTS. As an expert Systems Administrator, you'll understand that subtle differences in URI processing can lead to frustrating 404 errors, incorrect content display, or even redirect loops. We will dissect the underlying mechanics and provide precise configuration examples to ensure your backend applications are correctly served through Nginx.
Symptom & Error Signature
Users attempting to access a web application proxied through Nginx via a subfolder path (e.g., https://example.com/my-app/) may encounter one of the following symptoms:
- HTTP 404 Not Found: The most common symptom. Nginx successfully proxies the request, but the backend application responds with a 404, indicating it couldn't find the requested resource. This often happens because the URI passed to the backend is incorrect (e.g.,
/my-app/index.htmlinstead of/index.html). - Incorrect Content or Malfunctioning Application: The application loads partially, displays broken links (relative paths resolving incorrectly), or interactive elements fail due to misrouted API calls.
- Redirect Loops: The browser continuously redirects between paths, often involving
/my-appand/my-app/, or between different paths on the backend, leading to a "Too many redirects" error.
While there isn't a single "error signature" in Nginx logs for this misconfiguration, typical access logs might show the Nginx server successfully forwarding the request, followed by the backend returning a 404 status.
For example, a curl -v command might reveal:
$ curl -v https://example.com/my-app/dashboard
* Trying 203.0.113.10:443...
* Connected to example.com (203.0.113.10) port 443 (#0)
... (SSL handshake) ...
> GET /my-app/dashboard HTTP/1.1
> Host: example.com
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark large easy handle as doing multi (no longer waiting for background connect)
* We are currently in DO_DONE state
< HTTP/1.1 404 Not Found
< Server: Kestrel
< Date: Thu, 13 Aug 2026 10:00:00 GMT
< Content-Length: 0
<
* Connection #0 to host example.com left intact
In this scenario, Nginx forwarded /my-app/dashboard to the backend, which expected just /dashboard and responded with a 404.
Root Cause Analysis
The core of this problem lies in how Nginx processes and rewrites Uniform Resource Identifiers (URIs) based on the presence or absence of a trailing slash in both the location block and the proxy_pass directive. Understanding this behavior is critical.
Nginx's proxy_pass directive has two distinct behaviors concerning URI manipulation:
proxy_passwith a trailing slash (e.g.,proxy_pass http://backend/;):- When the
proxy_passdirective itself ends with a trailing slash, Nginx strips the portion of the URI matched by thelocationblock from the client request. The remaining part of the URI is then appended to theproxy_passURL. - Example:
location /my-app/ { proxy_pass http://backend:8080/; }- Client request:
GET /my-app/dashboard - URI sent to backend:
GET /dashboard(The/my-app/prefix is stripped)
- This is typically desired when your backend application expects to be served from its root path (i.e., it doesn't know or care about the
/my-appsubfolder prefix).
- When the
proxy_passwithout a trailing slash (e.g.,proxy_pass http://backend;):- When the
proxy_passdirective does not end with a trailing slash, Nginx passes the entire URI portion that matched thelocationblock (including the prefix) to the backend server. - Example:
location /my-app/ { proxy_pass http://backend:8080; }- Client request:
GET /my-app/dashboard - URI sent to backend:
GET /my-app/dashboard(The entire matched URI is passed)
- This is useful if your backend application is configured to handle requests with the subfolder prefix (e.g., a multi-tenant application where
/my-app/maps to a specific tenant).
- When the
Interaction with location blocks:
- A
location /subfolder/block matches requests that explicitly include the trailing slash (e.g.,/subfolder/foo). Nginx will often internally redirect/subfolderto/subfolder/if alocationfor the directory/subfolder/exists. - A
location /subfolderblock (without a trailing slash) typically matches requests that start with/subfolder, but the exact matching behavior can be tricky, especially with exact matches (=) or regular expressions (~). For subfolder proxying, usinglocation /subfolder/or a regexlocation ~ ^/subfolder/is generally more predictable.
The "resolving error" commonly occurs because the Nginx configuration's proxy_pass directive (and its trailing slash status) does not align with what the backend application expects regarding the URI path.
Step-by-Step Resolution
Follow these steps to correctly configure your Nginx proxy_pass directive for subfolder applications on Ubuntu 22.04 LTS.
1. Identify Your Backend's Expected URI Path
Before making any changes, determine whether your backend application expects requests to its root (/) or to a specific subpath (/my-app/).
- Most common scenario: Backend expects requests relative to its root (e.g.,
GET /dashboard). This means Nginx needs to strip the subfolder prefix. - Less common scenario: Backend is aware of the subfolder and expects requests to include it (e.g.,
GET /my-app/dashboard). This means Nginx needs to preserve the subfolder prefix.
2. Access Your Nginx Configuration
Your Nginx virtual host configuration files are typically located in /etc/nginx/sites-available/.
sudo nano /etc/nginx/sites-available/your_domain.conf
Replace your_domain.conf with the actual name of your Nginx configuration file.
3. Configure Nginx location and proxy_pass Directives
Based on your backend's requirements (identified in Step 1), apply one of the following configurations. We recommend using a location block that ends with a trailing slash for consistency.
Option A: Backend expects requests relative to its root (most common)
If your backend application expects the URI path to not include the subfolder prefix (e.g., a request to https://example.com/my-app/dashboard should translate to http://backend:8080/dashboard), use proxy_pass with a trailing slash:
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri; # Redirect HTTP to HTTPS
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # Your SSL cert
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; # Your SSL key
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Serve static files for the root domain if needed
root /var/www/html;
index index.html index.htm;
# Configuration for the subfolder application
location /my-app/ {
# Proxy to the backend, stripping the /my-app/ prefix
proxy_pass http://127.0.0.1:8080/; # <--- IMPORTANT: Note the trailing slash here!
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Adjust for websockets if needed
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# Prevent Nginx from intercepting error pages from the backend
proxy_intercept_errors off;
}
# Optional: Redirect non-trailing-slash /my-app to /my-app/
# This location handles requests to /my-app (without trailing slash)
# and redirects them to /my-app/ to ensure consistent behavior
location = /my-app {
return 301 $scheme://$host/my-app/;
}
# Other locations...
}
When
proxy_passends with a trailing slash (http://127.0.0.1:8080/;), Nginx strips the part of the URI matched by thelocationdirective (/my-app/) before sending it to the backend. A request to/my-app/dashboardwill be forwarded as/dashboard. This is ideal for backend applications unaware of the subfolder prefix.
Option B: Backend expects requests to include the full subfolder path
If your backend application is designed to receive the full subfolder path (e.g., a request to https://example.com/my-app/dashboard should translate to http://backend:8080/my-app/dashboard), use proxy_pass without a trailing slash:
server {
# ... (same server block header as above, including SSL) ...
# Configuration for the subfolder application
location /my-app/ {
# Proxy to the backend, preserving the /my-app/ prefix
proxy_pass http://127.0.0.1:8080; # <--- IMPORTANT: Note NO trailing slash here!
# Standard proxy headers
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
proxy_intercept_errors off;
}
# Optional: Redirect non-trailing-slash /my-app to /my-app/
location = /my-app {
return 301 $scheme://$host/my-app/;
}
# Other locations...
}
When
proxy_passdoes not end with a trailing slash (http://127.0.0.1:8080;), Nginx passes the entire matched URI (including thelocationprefix,/my-app/) to the backend. A request to/my-app/dashboardwill be forwarded as/my-app/dashboard. This is suitable for backend applications that expect to handle the subfolder path themselves.
4. Test Nginx Configuration and Reload
After making changes to your Nginx configuration file, it's crucial to test for syntax errors before reloading the service.
sudo nginx -t
You should see nginx: the configuration file /etc/nginx/nginx.conf syntax is ok and nginx: configuration file /etc/nginx/nginx.conf test is successful.
If there are any errors, Nginx will point them out, and you must correct them before proceeding.
Once the test is successful, reload Nginx to apply the new configuration:
sudo systemctl reload nginx
5. Verify the Solution
Open your web browser and try accessing your subfolder application (e.g., https://example.com/my-app/). Also, test accessing the path without a trailing slash (e.g., https://example.com/my-app) to ensure the optional redirect works as expected.
Use curl -v again to observe the HTTP headers and status codes, verifying that requests are now correctly reaching your backend and receiving appropriate responses (e.g., HTTP/1.1 200 OK).
curl -v https://example.com/my-app/dashboard
If your backend application itself issues redirects (e.g., from
/to/login), ensure these redirects are correctly handled and do not result in absolute URLs that omit the/my-app/prefix. Sometimes, additionalproxy_redirectdirectives might be needed for complex backend redirect scenarios, though Nginx handles many common cases automatically. For instance:proxy_redirect http://backend:8080/ /my-app/;. This tells Nginx to rewriteLocationheaders from the backend.
Our Production Verification Guarantee
Encountering a bug not covered here or running a non-standard kernel configuration? Our solutions are continually refined against real production incidents. Submit an environment trace for our editorial team to replicate.