48 lines
2.8 KiB
Markdown
48 lines
2.8 KiB
Markdown
# Caddy Static File MIME Type Fix (Jul 12, 2026)
|
|
|
|
## Problem
|
|
The ops portal at `ops.itpropartner.com` had JS and CSS files served with wrong MIME types (`application/json` instead of `text/javascript` / `text/css`). Browsers enforce correct MIME types for scripts and stylesheets — any JS file served as `application/json` is silently rejected. The entire `Ops` namespace never loaded, login overlays stayed visible, the mobile hamburger menu never worked, and pages appeared broken.
|
|
|
|
Root cause was twofold:
|
|
|
|
### 1. File permissions (the real fix)
|
|
Caddy runs as the `caddy` system user. Files in `/opt/ops-portal/static/` were created by root with `chmod 600` — Caddy returned **403 Forbidden** for all static file requests. Even though a Caddy `handle_path` block was configured, the `file_server` handler couldn't read the files and returned 403.
|
|
|
|
**Fix:** `chmod 644 /opt/ops-portal/static/js/*.js /opt/ops-portal/static/css/*.css`
|
|
|
|
### 2. Caddy handle_path nesting error (compounding factor)
|
|
The `/js/*` and `/css/*` `handle_path` blocks were initially nested INSIDE the `/data/*` handler block due to a `sed` command error. They only triggered when the URL started with `/data/`, so `/js/app.js` never matched.
|
|
|
|
**Fix:** Extract to the top level of the Caddy site block, before `reverse_proxy`:
|
|
```caddy
|
|
ops.itpropartner.com {
|
|
handle_path /data/* {
|
|
root * /var/www/ops/data/
|
|
file_server
|
|
}
|
|
handle_path /js/* {
|
|
root * /opt/ops-portal/static/js
|
|
file_server
|
|
}
|
|
handle_path /css/* {
|
|
root * /opt/ops-portal/static/css
|
|
file_server
|
|
}
|
|
reverse_proxy 127.0.0.1:8090
|
|
}
|
|
```
|
|
|
|
### 3. Backend `media_type` workaround (not needed)
|
|
The FastAPI backend has an `@app.get("/js/{file_path:path}")` route using `FileResponse`. Even with `media_type="application/javascript"` explicitly set, the response still returned `Content-Type: application/json` for unknown reasons (possibly a FastAPI 0.115.6 quirk, or the `media_type` parameter being ignored in certain response scenarios). The Caddy static file approach bypasses the backend entirely.
|
|
|
|
## Detection
|
|
- Curl the JS URL: `curl -sI https://ops.itpropartner.com/js/app.js` — check `content-type` header
|
|
- Expected: `text/javascript; charset=utf-8`
|
|
- Broken: `application/json` (backend) or `text/html` (caddy error page) or empty (403/404)
|
|
- Browser symptom: Pages load but no JS executes — login overlay stays visible, nav not working, "Ops is not defined" errors in console
|
|
|
|
## Prevention
|
|
- Always `chmod 644` new static files added to `/opt/ops-portal/static/`
|
|
- When adding a new Caddy `handle_path` block, verify it's at the TOP LEVEL of the site block, not indented inside another handler
|
|
- After ANY Caddy config change: `caddy fmt --overwrite /etc/caddy/Caddyfile && caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy`
|