Files

2.3 KiB

Caddy Static File MIME Type & Permissions Fix

Problem

JS/CSS files served by Caddy return Content-Type: application/json and the browser refuses to execute/render them. The site appears broken with missing styles and unresponsive JS.

Root Cause

Two possible causes — check both:

1. File permissions (most common)

Caddy runs as the caddy user (not root). If static files have permissions 600 (root-only), Caddy returns HTTP 403 Forbidden. The browser silently fails to load JS/CSS — no console error in some browsers.

Fix: chmod 644 /path/to/static/files/*.js /path/to/static/files/*.css

Check: curl -sI https://ops.itpropartner.com/js/app.js | grep -i content-type Expected: content-type: text/javascript; charset=utf-8 Broken: content-type: application/json or HTTP 403

2. Caddy handle_path nested inside another block

If a handle_path /js/* or handle_path /css/* block is accidentally nested inside another handle_path /data/* block (or any other block), it will only match URLs that start with the outer path first. E.g. /js/app.js won't match handle_path /data/* { handle_path /js/* { ... } } because /js/ doesn't start with /data/.

Fix: Ensure all static file handle_path blocks are at the TOP LEVEL of the site config, not nested inside other blocks.

ops.itpropartner.com {
    handle_path /data/* {
        root * /var/www/ops/data/
        file_server
    }
    handle_path /js/* {           # ← TOP LEVEL, not nested
        root * /opt/ops-portal/static/js
        file_server
    }
    handle_path /css/* {          # ← TOP LEVEL, not nested
        root * /opt/ops-portal/static/css
        file_server
    }
    reverse_proxy 127.0.0.1:8090
}

Verification

# JS MIME type
curl -sI https://ops.itpropartner.com/js/app.js | grep -i content-type
# Must show: text/javascript; charset=utf-8

# CSS MIME type
curl -sI https://ops.itpropartner.com/css/ops.css | grep -i content-type
# Must show: text/css; charset=utf-8

# HTTP status
curl -sS -o /dev/null -w '%{http_code}\n' https://ops.itpropartner.com/js/app.js
# Must show: 200
  • Backend FastAPI FileResponse may also return wrong MIME types. Fixed by adding media_type parameter: FileResponse(..., media_type="application/javascript"). But the Caddy file_server approach is more reliable.