Static files fail behind an NGINX subdirectory when the browser URL, NGINX’s filesystem mapping, and the upstream application disagree about the path. The fix is to decide where each asset is served, map /app/ deliberately, and prevent missing assets from falling into an application fallback. The critical distinction is simple: a proxy_pass URL with a URI replaces the matching location prefix; one without a URI generally preserves the request path.
Need a server to try this on? Antihost offers Linux VPS plans with NVMe storage, full root access and a 14-day free trial.
Why assets break under /app/
A page loaded at https://example.com/app/ may request /app/css/site.css, /css/site.css, or even /static/app.css. Those are different URLs. NGINX then has to do one of two things with each request: construct a local filesystem path, or construct a new request for an upstream server. A deployment works only when both transformations match the actual directory tree and the application’s expectations.
As an Amazon Associate I earn from qualifying purchases.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
There are therefore two independent questions:
- What path did the browser request? Copy the complete URL, including the
/app/prefix, before changing configuration. - What path does NGINX use next? For local files it is derived from
rootoralias; for proxied files it is derived from theproxy_passform.
For example, with location /app/ { proxy_pass http://backend/; }, a request for /app/css/site.css is sent upstream as /css/site.css. With proxy_pass http://backend;, the request path is generally preserved as /app/css/site.css. The trailing slash is not cosmetic.
Choose where static files are served
Make this decision before editing directives. It determines the correct location block and the path the application must generate.
#1 Best Overall
| Design | Where files live | Expected upstream path | Best fit |
|---|---|---|---|
| Local static directory | On the NGINX host, such as /srv/app/static/ |
None for those files | Stable assets that should not consume application workers |
| Upstream serves static files | Inside the application server or its container | /css/... and /js/... when the public prefix is stripped |
The application already owns its asset pipeline |
| SPA entry plus proxy fallback | Built files on NGINX, application handles other routes | Only requests that fail local checks | Single-page applications with client-side routing |
Also check whether generated HTML uses prefix-aware URLs. A root-relative URL such as /static/app.css starts at the domain root and bypasses /app/. The application may need an external base URL or a build-time public path that includes /app/.
How NGINX constructs local paths
root appends the complete URI
With:
location /app/ {
root /var/www/html;
}
a request for /app/css/site.css makes NGINX look for /var/www/html/app/css/site.css. This is correct only when the on-disk tree really contains an app/ directory beneath /var/www/html. If the file is actually /var/www/html/css/site.css, the public prefix has been duplicated in the filesystem lookup.
alias maps the location directly
Use alias when the URL prefix should point directly at another directory. A location of /app/static/ with alias /srv/app/static/; maps /app/static/site.css to /srv/app/static/site.css, rather than appending the whole URI. Match the slash form: a location ending in / should use an alias directory ending in /.
try_files is the existence gate
try_files checks candidate files or directories using the active root or alias. It processes arguments in order; the final argument can be an internal redirect target or a status such as =404. For assets, an explicit =404 is important because it stops a missing stylesheet or script from being replaced with an HTML page.
Rank #2
Pattern 1: serve a local static directory and proxy the application
Separate the asset prefix from the broader application prefix. NGINX selects the more specific /app/static/ location for those requests, so they never reach the proxy block.
location ^~ /app/static/ {
alias /srv/app/static/;
try_files $uri =404;
}
location /app/ {
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_pass http://app_backend/;
}
The two trailing slashes have distinct jobs. The slash in proxy_pass http://app_backend/; tells NGINX to replace the matching /app/ location prefix, so /app/dashboard reaches the upstream as /dashboard. The slash on the alias directory makes the /app/static/ URL map cleanly into /srv/app/static/. Confirm that the files really exist there and that the NGINX worker can read them.
^~ gives the static prefix priority over regular-expression locations. Keeping the static block more specific than location /app/ avoids accidentally proxying assets. If your configuration has other exact or regex locations, inspect those as well; location selection can change the result.
Pattern 2: let the upstream application serve assets
If the upstream already serves /css/, /js/, and image paths, proxy those requests instead of creating a second static tree:
location /app/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app_backend/;
}
This form strips the public prefix. The application must therefore expect /css/site.css, not /app/css/site.css. If the upstream is configured with the latter as its base path, omit the URI from proxy_pass:
location /app/ {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://app_backend;
}
Without a URI parameter, NGINX generally passes the original request path, including /app/. Another valid solution is to keep the slash form and configure the application’s external base path so it serves and generates URLs consistently. Do not mix the two assumptions: an upstream expecting /app/ will commonly return 404 when it receives /css/, while an upstream expecting the stripped path will commonly 404 when it receives /app/css/.
Pattern 3: SPA files with a safe application fallback
A single-page application often needs local files for the shell and a fallback for client-side routes. Check assets before invoking the fallback:
location /app/ {
root /srv/app/dist;
try_files $uri $uri/ @app;
}
location @app {
proxy_pass http://app_backend;
}
Here, try_files tests the requested path and directory first. A request for an existing JavaScript bundle is served locally; an application route that is not a local file is sent to the named location. For known asset prefixes, a stricter block is safer:
Rank #4
location ^~ /app/assets/ {
alias /srv/app/dist/assets/;
try_files $uri =404;
}
location /app/ {
root /srv/app/dist;
try_files $uri $uri/ @app;
}
location @app {
proxy_pass http://app_backend;
}
Without this separation, a missing .css or .js file can receive index.html. The browser then reports a misleading MIME-type or JavaScript parse error instead of the useful fact that the asset path is wrong.
Diagnose one failing URL from browser to disk or upstream
- Capture the exact request. Open browser developer tools, reload the page, and copy one failing asset URL. Record its status and response content type. Include the subdirectory prefix;
/app/css/site.cssand/css/site.cssare different tests. - Determine the winning location. Review exact, prefix, regex, and named locations. A broad
location /app/may not win, and regex locations can affect whether NGINX can determine a proxy URI replacement. If a regex location is involved, avoid relying on a URI parameter inproxy_pass; the official behavior is indeterminate for some such combinations. - Compute a local filesystem path. For
root, append the request URI to the root directory. Foralias, replace the matching location prefix with the alias directory. Check for a duplicated/app/or an omitted directory, then verify permissions and spelling. - Compute the upstream path. If
proxy_passincludes a URI, NGINX replaces the matching location portion with that URI. If it has no URI, compare the original request path with what the application expects. Check whether a trailing slash changed the result. - Read both sets of logs. NGINX access and error logs show whether the failure occurred during local lookup or proxying. An NGINX-generated 404 points toward filesystem mapping or a restrictive
try_files; an upstream 404 points toward the forwarded path or application base-path handling. Then inspect the upstream log for the exact route it received. - Inspect generated URLs. View the HTML source and CSS references. Root-relative links beginning with
/bypass/app/. Relative links, absolute links generated by the server, and JavaScript build paths can each produce a different public URL. - Validate and reload safely. Run
nginx -t. Only after a successful syntax check reload NGINX, then retest the application entry page and one asset. A command-line check such ascurl -I https://example.com/app/static/site.cssconfirms the status and headers without relying on browser caching.
Common failure modes and precise fixes
The prefix is duplicated on disk
root /var/www/html; under location /app/ searches for /var/www/html/app/.... If deployment files are under /var/www/html/..., use an appropriate alias, move the files into an app/ directory, or change the root layout. Do not “fix” this by adding another rewrite until the intended mapping is explicit.
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minutePC Slower Than It Used to Be?
A free scan shows the junk files, broken settings and background clutter dragging Windows down - then fixes them in one click.Free scan · Windows 10 & 11An unexpected prefix appears upstream
proxy_pass http://backend/app/; replaces the matching location with /app/. Depending on the location and original URI, this can add or retain a prefix the application does not serve. Use the exact URI the upstream expects, or omit the URI to preserve the request path.
A regex location makes rewriting unpredictable
In regex and some named locations, NGINX may not have a determinate matching prefix to replace. The safer configuration is a proxy_pass without a URI, followed by explicit application base-path configuration if needed.
HTML is returned for a missing asset
An SPA fallback that returns index.html for every unknown path masks missing files. Put a specific static location first and use try_files $uri =404 for asset prefixes. Confirm the response content type as well as the status.
The upstream redirects to the site root
An application may generate an absolute redirect to / instead of /app/. Forward Host and X-Forwarded-Proto, and configure the application’s external base path. Otherwise the first page may load while subsequent redirects and asset URLs escape the subdirectory.
Free tools Windows power users keep installed
One-click scans. No signup required.
Configuration checklist before you declare it fixed
- The public prefix is settled:
/app/is used consistently in links, redirects, and documentation. - Each static URL has one owner: the NGINX host or the upstream application.
- Every
rootlookup andaliassubstitution has been calculated for a real failing URL. - The
proxy_passtrailing-slash choice matches the upstream’s expected path. - A specific static location precedes the broad application proxy where both are needed.
- Asset misses return 404 instead of an SPA document.
- Forwarded host and protocol headers are present when the upstream creates redirects or absolute URLs.
nginx -tsucceeds, logs show the expected path, and a fresh request to the entry page plus one CSS or JavaScript file succeeds.
Which design should you use?
Keep files on NGINX when the directory is known, the assets are mostly immutable, and you want requests for them isolated from application failures. Let the upstream serve them when its framework, authentication, or asset pipeline already owns those paths. Use the SPA pattern when local build output and application routes share the same public prefix; the explicit asset check is what keeps a typo in a bundle URL from being hidden by a successful HTML response.
Whichever design you select, document the public prefix, the on-disk directory (if any), the exact upstream path, and whether the application emits prefix-aware URLs. That four-part contract prevents most future 404s when a build or deployment changes.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




