MIME Types, Charset, and Caching Headers
Introduction
Browsers rely on Content-Type and charset to render pages correctly, and on cache headers to avoid re-downloading assets on every visit. Nginx sets these via mime.types, charset, expires, and add_header. This chapter fixes wrong MIME issues, ensures UTF-8 text, and applies sensible cache policies for static sites.
Prerequisites
- Serving Static Files
- Basic HTTP headers concept (
Content-Type,Cache-Control)
MIME Types and mime.types
Nginx maps file extensions to MIME types:
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
}Code explanation:
mime.typesdefines hundreds of mappings (.html→text/html,.css→text/css)default_typeapplies when extension is unknown—often triggers download instead of display
Common mappings
| Extension | Content-Type |
|---|---|
.html | text/html |
.css | text/css |
.js | application/javascript |
.json | application/json |
.png | image/png |
.svg | image/svg+xml |
.woff2 | font/woff2 |
Wrong MIME symptoms
| Symptom | Likely cause |
|---|---|
| CSS not applied | Served as text/plain or octet-stream |
| JS not executed | Wrong type or missing ; charset=utf-8 for modules |
| Download dialog for HTML | default_type used; missing extension |
Fix: ensure file has correct extension, or override:
location ~* \.mjs$ {
types { application/javascript mjs; }
}Inspect response headers:
curl -I http://127.0.0.1/style.cssLook for Content-Type: text/css.
Charset for Text Responses
http {
charset utf-8;
charset_types text/html text/css application/javascript application/json;
}Code explanation:
- Adds
; charset=utf-8to listed text types - Prevents mojibake for non-ASCII content when files are UTF-8
Ensure HTML declares UTF-8:
<meta charset="UTF-8">Both file encoding and headers should agree.
expires and Cache-Control
expires directive
location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff2)$ {
expires 30d;
add_header Cache-Control "public";
}Code explanation:
expires 30dsetsExpiresheader andCache-Control: max-age=2592000publicallows shared caches (CDN) to store the response
add_header for fine control
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location = /index.html {
add_header Cache-Control "no-cache";
}Code explanation:
- Hashed assets (
app.8f3a2.js) — longmax-age+immutableis safe index.html—no-cacheforces revalidation so users get new asset references after deploy
Tip
Cache Strategy for SPAs
Long cache for /assets/*, short or no cache for index.html—standard Vite/React production pattern.
etag and Conditional Requests
Nginx sends ETag by default for static files. Clients may send:
If-None-Match: "abc123"Server responds 304 Not Modified without body—saves bandwidth.
etag on;Usually leave etag on unless a CDN replaces validation logic.
if_modified_since works with Last-Modified similarly (defaults on).
Disable Caching for Development
location / {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}Use only in dev/staging—never on production assets you want browsers to cache.
Layered location Example
Code explanation:
/assets/— fingerprinted files from modern bundlersindex.html— always revalidate after deploycharset utf-8— site-wide text encoding hint
Verify Headers
curl -I http://app.example.com/assets/main.css
curl -I http://app.example.com/index.htmlCompare Cache-Control and Content-Type between asset and HTML.
FAQ
add_header in nested locations?
Headers from inner location may replace parent add_header for that response—test with curl -I.
Must I set MIME for every extension?
No—mime.types covers common web types. Add overrides for rare extensions only.
CDN in front of Nginx?
CDN respects origin headers. Set cache policy at Nginx; CDN may override with its own rules.
expires vs Cache-Control only?
expires is convenient; add_header Cache-Control alone is enough if you prefer explicit control.
Why immutable?
Tells browsers the URL content never changes—safe for hash-named files; wrong for URLs that can update in place.
JSON API cache headers?
Dynamic API responses often use Cache-Control: no-store—reverse proxy chapter covers API caching separately.