Grafana Error Guide: Subpath Assets & Login 404 — root_url & serve_from_sub_path
Fix Grafana behind a reverse-proxy subpath returning 404 for assets and login — set root_url and serve_from_sub_path correctly, align proxy path handling, and restore the UI under /grafana.
- #grafana
- #troubleshooting
- #errors
- #reverse-proxy
Overview
When Grafana runs behind a reverse proxy under a subpath (e.g. https://example.com/grafana), it must generate asset and API URLs that include that prefix. Two grafana.ini settings control this: [server] root_url (the public URL) and [server] serve_from_sub_path. If they don’t match the proxy, Grafana emits links to the wrong path and the browser gets 404 for JS/CSS assets and the login page — a blank or unstyled page.
The literal errors you will see (browser network tab / proxy logs):
GET /grafana/public/build/app.<hash>.js 404 (Not Found)
GET /public/build/runtime.<hash>.js 404 (Not Found) # missing the /grafana prefix
"If you're seeing this Grafana has failed to load its application files"
It occurs immediately on loading Grafana through the proxy: the page is blank/unstyled and login won’t render.
Symptoms
- Grafana under
/grafanashows a blank white page or unstyled login. - Browser console/network shows
404forpublic/build/*.jsand*.css. - Assets 404 either with a doubled prefix (
/grafana/grafana/...) or a missing prefix (/public/...). - Direct access on
:3000works, but the proxied subpath doesn’t.
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" \
https://example.com/grafana/public/build/app.js
404 https://example.com/grafana/public/build/app.js
Common Root Causes
1. root_url missing the subpath
root_url doesn’t include /grafana, so Grafana generates asset links at /public/... and the proxy has nothing to route.
[server]
# WRONG for a subpath deployment
root_url = https://example.com/
2. serve_from_sub_path not enabled
root_url has the subpath but serve_from_sub_path = false, so Grafana serves assets from / internally while links point to /grafana — mismatch → 404.
[server]
root_url = https://example.com/grafana/
serve_from_sub_path = false # should be true here
3. Proxy strips the prefix while serve_from_sub_path is true
If the proxy rewrites /grafana/... to /... (strips the prefix) but Grafana expects to serve from the sub path, the paths double up or mismatch.
4. Trailing-slash / path mismatch in proxy config
A location /grafana without the correct proxy_pass trailing-slash semantics changes whether the prefix is forwarded, producing missing or doubled prefixes.
Diagnostic Workflow
Step 1: Check the effective server config
grep -A2 "^\[server\]" /etc/grafana/grafana.ini | grep -iE "root_url|serve_from_sub_path|domain"
# env-var form:
env | grep -iE "GF_SERVER_ROOT_URL|GF_SERVER_SERVE_FROM_SUB_PATH"
Step 2: See what path Grafana expects for assets
curl -s http://localhost:3000/grafana/api/health -o /dev/null -w "%{http_code}\n"
curl -s http://localhost:3000/api/health -o /dev/null -w "%{http_code}\n"
With serve_from_sub_path = true, the /grafana/... path should answer; without it, only /... does.
Step 3: Trace the failing asset URL through the proxy
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" \
https://example.com/grafana/public/build/runtime.js
A missing /grafana prefix in generated links means root_url is wrong; a doubled prefix means the proxy and serve_from_sub_path disagree.
Step 4: Set the canonical subpath config
[server]
protocol = http
http_port = 3000
domain = example.com
root_url = https://example.com/grafana/
serve_from_sub_path = true
systemctl restart grafana-server
Step 5: Align the proxy (do NOT strip the prefix)
Nginx — pass the prefix through unchanged:
# nginx (shown as config)
location /grafana/ {
proxy_pass http://127.0.0.1:3000; # no trailing slash -> keeps /grafana/ prefix
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
Example Root Cause Analysis
A team moves Grafana behind Nginx at https://ops.example.com/grafana. The page loads blank; the browser console:
GET https://ops.example.com/public/build/app.5f3.js 404 (Not Found)
Note the missing /grafana prefix. The config:
[server]
root_url = https://ops.example.com/
serve_from_sub_path = true
root_url lacks the subpath, so despite serve_from_sub_path = true, Grafana generates asset links at /public/... — which the proxy only serves under /grafana/.... Fix both settings to agree with the proxy:
[server]
root_url = https://ops.example.com/grafana/
serve_from_sub_path = true
And ensure Nginx forwards the prefix (no trailing slash on proxy_pass). After systemctl restart grafana-server, assets load from /grafana/public/... and the UI renders. Root cause: root_url didn’t include the subpath, so generated links dropped the prefix the proxy needed.
Prevention Best Practices
- Set
root_urlto the full public URL including the subpath and trailing slash, andserve_from_sub_path = truetogether — they must agree. - Decide one prefix strategy: let Grafana serve the sub path (keep the prefix in the proxy,
serve_from_sub_path = true), and don’t also strip it at the proxy. - Test the exact asset URL (
/grafana/public/build/*.js) withcurlafter any proxy or config change. - Use env vars (
GF_SERVER_ROOT_URL,GF_SERVER_SERVE_FROM_SUB_PATH) in containers so the setting is explicit and versioned. - Mirror
X-Forwarded-Proto/Hostheaders so redirects and login callbacks use the right scheme/host. - See more Grafana guides.
Quick Command Reference
# Effective server config
grep -iE "root_url|serve_from_sub_path|domain" /etc/grafana/grafana.ini
env | grep -iE "GF_SERVER_ROOT_URL|GF_SERVER_SERVE_FROM_SUB_PATH"
# Does the subpath answer?
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/grafana/api/health
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:3000/api/health
# Trace a failing asset through the proxy
curl -s -o /dev/null -w "%{http_code} %{url_effective}\n" \
https://example.com/grafana/public/build/runtime.js
# Canonical subpath config (grafana.ini)
# [server]
# root_url = https://example.com/grafana/
# serve_from_sub_path = true
systemctl restart grafana-server
Conclusion
Subpath 404s for assets and login mean Grafana’s generated URLs don’t match the path the reverse proxy serves. Typical root causes:
root_urlmissing the/grafanasubpath, so links drop the prefix.serve_from_sub_pathnot enabled whileroot_urlhas the subpath.- The proxy stripping the prefix while Grafana expects to serve the sub path (doubled/mismatched paths).
- Trailing-slash
proxy_passsemantics changing whether the prefix is forwarded.
Set root_url (with subpath + trailing slash) and serve_from_sub_path = true to agree, and make the proxy forward the prefix unchanged; then curl the exact asset URL to confirm it returns 200.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.