ThatAIguyA world of ideas

THE CONSCIOUSNESS ATLAS
Read. Experiment. Follow the evidence.

ConsciousnessLearn · Explore · Joseph Anady

Janady13/crawler-signal-reference / framework-http-content-headers.md · Lines 1501–2325

Source identity, revision, and recovered contents.

GitHub sourceSource text recovered

A continuation of Janady13/crawler-signal-reference / framework-http-content-headers.md. The complete source identity, every recorded occurrence, and linked equations are on its first page.

Source transcript · continued

Download complete source text ↓2,325 lines · 94.8 KB
## 11. ASSET CLASS RECIPES Each block is a paste ready location stanza for the five content headers. Copy the ones relevant to your build. ### 11.1 HTML (UTF-8 always, no Content-Disposition) ```nginx location ~* \.html$ { # Content-Type: text/html; charset=utf-8 (auto from mime.types + charset utf-8) # Content-Language set at server level: en-US # Content-Encoding: gzip or br (auto from gzip/brotli on) # Content-Length: auto for static, chunked when on the fly compression active # Content-Disposition: none (default inline) expires 0; add_header Cache-Control "public, max-age=0, must-revalidate" always; } ``` ### 11.2 CSS ```nginx location ~* \.css$ { # Content-Type: text/css; charset=utf-8 (auto) # Content-Encoding: gzip/br/zstd (auto, large savings) # Content-Length: auto for precompressed, chunked otherwise expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always; } ``` ### 11.3 JavaScript ```nginx location ~* \.(js|mjs)$ { # Content-Type: application/javascript; charset=utf-8 (auto) # Content-Encoding: gzip/br/zstd (auto) expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always; } ``` ### 11.4 JSON ```nginx location ~* \.json$ { # Content-Type: application/json; charset=utf-8 (auto) # Content-Encoding: gzip/br/zstd (auto) expires 1h; add_header Cache-Control "public, max-age=3600, must-revalidate" always; } # For schema and structured data location ~* \.jsonld$ { types { application/ld+json jsonld; } expires 1h; add_header Cache-Control "public, max-age=3600" always; } ``` ### 11.5 XML (sitemaps, feeds) ```nginx location ~* \.xml$ { # Content-Type: application/xml; charset=utf-8 (auto) # Content-Encoding: gzip/br/zstd (auto) expires 10m; add_header Cache-Control "public, max-age=600" always; } ``` ### 11.6 SVG ```nginx location ~* \.svg$ { # Content-Type: image/svg+xml; charset=utf-8 (auto) # Content-Encoding: gzip/br/zstd (auto; SVG is text, compresses ~70 percent) expires 30d; add_header Cache-Control "public, max-age=2592000" always; } ``` ### 11.7 Raster images (PNG, JPEG, WebP, AVIF) ```nginx location ~* \.(png|jpe?g|gif|webp|avif|ico)$ { # Content-Type: image/png, image/jpeg, etc (auto) # Content-Encoding: none (already compressed internally) # Make sure these are NOT in gzip_types or brotli_types expires 30d; add_header Cache-Control "public, max-age=2592000" always; } ``` ### 11.8 Fonts (woff2, woff, ttf, otf) ```nginx location ~* \.(woff2|woff|ttf|otf|eot)$ { # Content-Type: font/woff2, font/ttf, etc (auto) # Content-Encoding: NONE for woff2 (already brotli compressed) # gzip/br OK for ttf, otf (uncompressed formats) # CORS: required if loaded from a different origin add_header Access-Control-Allow-Origin "*" always; expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always; } ``` ### 11.9 PDF (with explicit attachment) ```nginx location ~* \.pdf$ { # Content-Type: application/pdf (auto) # Content-Encoding: NONE (PDF already compressed internally) expires 1d; add_header Cache-Control "public, max-age=86400" always; add_header Content-Disposition "attachment" always; add_header X-Content-Type-Options "nosniff" always; } # Or for inline preview location /preview/ { add_header Content-Disposition "inline" always; } ``` ### 11.10 Downloadable archives (zip, tar, gz) ```nginx location ~* \.(zip|tar|gz|bz2|7z|rar)$ { # Content-Type: application/zip, application/x-tar, etc (auto) # Content-Encoding: NONE (these ARE the archive; do not double compress) expires 1d; add_header Cache-Control "public, max-age=86400" always; add_header Content-Disposition "attachment" always; } ``` ### 11.11 User uploaded content (security hardened) ```nginx location /user-uploads/ { # Force download, force binary type, prevent XSS via uploaded HTML add_header Content-Type "application/octet-stream" always; add_header Content-Disposition "attachment" always; add_header X-Content-Type-Options "nosniff" always; add_header Content-Security-Policy "default-src 'none'" always; expires 1d; } ``` ### 11.12 robots.txt, ai.txt, llms.txt, security.txt ```nginx location ~* /(robots\.txt|ai\.txt|llms\.txt|humans\.txt|\.well-known/security\.txt)$ { # Content-Type: text/plain; charset=utf-8 (auto from .txt) # Content-Encoding: gzip (auto; saves bandwidth on long robots files) expires 1h; add_header Cache-Control "public, max-age=3600" always; } ``` ### 11.13 WebAssembly modules ```nginx location ~* \.wasm$ { # Content-Type: application/wasm (must add to types {} if missing from mime.types) types { application/wasm wasm; } # Content-Encoding: gzip/br/zstd (auto; .wasm benefits from compression) expires 1y; add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Cross-Origin-Embedder-Policy "require-corp" always; add_header Cross-Origin-Opener-Policy "same-origin" always; } ``` ### 11.14 PWA manifest ```nginx location ~* \.webmanifest$ { types { application/manifest+json webmanifest; } # Charset: utf-8 (auto via charset_types) expires 1d; add_header Cache-Control "public, max-age=86400" always; } ``` ### 11.15 Generated CSV exports (from FastAPI sidecar) ```nginx location /export/ { proxy_pass http://127.0.0.1:9090; proxy_pass_header Content-Type; proxy_pass_header Content-Disposition; # FastAPI must emit: # Content-Type: text/csv; charset=utf-8 # Content-Disposition: attachment; filename="export-2026-05-24.csv" } ``` --- ## 12. BUBBLES NGINX REFERENCE BLOCK (PASTE READY) The complete content header stanza, layered with the caching stanza from framework-http-caching-headers.md. ```nginx # /etc/nginx/nginx.conf (or http context) http { # ===== CONTENT TYPE ===== include /etc/nginx/mime.types; default_type application/octet-stream; types { image/avif avif; font/woff2 woff2; application/wasm wasm; application/manifest+json webmanifest manifest; application/ld+json jsonld; model/gltf-binary glb; model/gltf+json gltf; text/markdown md; } # ===== CHARSET ===== charset utf-8; charset_types text/html text/css text/plain text/xml text/csv application/javascript application/json application/xml application/atom+xml application/rss+xml application/ld+json image/svg+xml; # ===== COMPRESSION: GZIP ===== gzip on; gzip_vary on; gzip_min_length 1100; gzip_comp_level 6; gzip_proxied any; gzip_static on; gzip_types text/plain text/css text/xml text/csv application/javascript application/json application/xml application/atom+xml application/rss+xml application/ld+json application/manifest+json application/wasm image/svg+xml font/ttf font/otf; gzip_disable "msie6"; # ===== COMPRESSION: BROTLI (if module installed) ===== brotli on; brotli_static on; brotli_comp_level 6; brotli_min_length 1100; brotli_types text/plain text/css text/xml text/csv application/javascript application/json application/xml application/atom+xml application/rss+xml application/ld+json application/manifest+json application/wasm image/svg+xml font/ttf font/otf; # ===== COMPRESSION: ZSTD (if module installed) ===== zstd on; zstd_static on; zstd_comp_level 6; zstd_min_length 1100; zstd_types text/plain text/css text/xml application/javascript application/json application/xml application/ld+json image/svg+xml; } # Per server block server { listen 443 ssl; http2 on; server_name example.com www.example.com; root /var/www/sites/example.com; index index.html; # ===== CONTENT-LANGUAGE ===== # Default language for the site # NOTE: snippets pattern is required so location blocks do not drop this set $site_language "en-US"; # ===== SECURITY FAMILY (no MIME sniffing, etc) ===== include snippets/common-security-headers.conf; add_header Content-Language $site_language always; # ===== ASSET CLASS BLOCKS ===== # (See Section 11 for all asset class recipes; combine with caching recipes from framework-http-caching-headers.md) location ~* \.html$ { include snippets/common-security-headers.conf; add_header Content-Language $site_language always; add_header Cache-Control "public, max-age=0, must-revalidate" always; } location ~* \.(css|js|mjs)$ { include snippets/common-security-headers.conf; add_header Cache-Control "public, max-age=31536000, immutable" always; } location ~* \.(woff2|woff|ttf|otf|eot)$ { include snippets/common-security-headers.conf; add_header Access-Control-Allow-Origin "*" always; add_header Cache-Control "public, max-age=31536000, immutable" always; } location ~* \.(png|jpe?g|gif|webp|avif|svg|ico)$ { include snippets/common-security-headers.conf; add_header Cache-Control "public, max-age=2592000" always; } location ~* \.pdf$ { include snippets/common-security-headers.conf; add_header Content-Disposition "attachment" always; add_header Cache-Control "public, max-age=86400" always; } location /user-uploads/ { include snippets/common-security-headers.conf; add_header Content-Type "application/octet-stream" always; add_header Content-Disposition "attachment" always; add_header Content-Security-Policy "default-src 'none'" always; add_header Cache-Control "public, max-age=86400" always; } location /api/ { include snippets/common-security-headers.conf; proxy_pass http://127.0.0.1:9090; 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_header Content-Type; proxy_pass_header Content-Disposition; proxy_pass_header Content-Language; add_header Vary "Accept-Encoding" always; } location / { include snippets/common-security-headers.conf; add_header Content-Language $site_language always; add_header Cache-Control "public, max-age=0, must-revalidate" always; try_files $uri $uri/ $uri.html =404; } } ``` The shared security header snippet at `/etc/nginx/snippets/common-security-headers.conf`: ```nginx add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; ``` After deploying: ```bash nginx -t && systemctl reload nginx ``` --- ## 13. AUDIT CHECKLIST Run through these 50 items for any production site. Each item is a single curl command or visual inspection. Pass means the listed expected value appears. ### Content-Type 1. [ ] HTML response has `Content-Type: text/html; charset=utf-8`. 2. [ ] CSS response has `Content-Type: text/css; charset=utf-8`. 3. [ ] JavaScript response has `Content-Type: application/javascript; charset=utf-8` (or `text/javascript`). 4. [ ] JSON response has `Content-Type: application/json` (charset optional). 5. [ ] XML sitemap has `Content-Type: application/xml`. 6. [ ] SVG response has `Content-Type: image/svg+xml`. 7. [ ] AVIF image has `Content-Type: image/avif`. 8. [ ] WebP image has `Content-Type: image/webp`. 9. [ ] WOFF2 font has `Content-Type: font/woff2` (NOT `application/octet-stream`). 10. [ ] WASM module has `Content-Type: application/wasm`. 11. [ ] PDF has `Content-Type: application/pdf`. 12. [ ] `X-Content-Type-Options: nosniff` is present on every response. 13. [ ] No response has `Content-Type: application/octet-stream` for known file extensions. 14. [ ] No response has `Content-Type: text/html` for non HTML files. 15. [ ] `default_type` in nginx config is `application/octet-stream` (not `text/html`). ### Content-Language 16. [ ] HTML response has `Content-Language: en-US` (or the correct site language). 17. [ ] Language code uses hyphen, not underscore (`en-US` not `en_US`). 18. [ ] Region code is uppercase two letter ISO 3166-1 (`US` not `usa`). 19. [ ] HTML `<html>` element has matching `lang` attribute. 20. [ ] HTML `<head>` has matching `<meta http-equiv="content-language" content="...">`. 21. [ ] Multi region sites have hreflang annotations in `<head>` for every language version. 22. [ ] hreflang includes `x-default` for the global default. 23. [ ] Localized paths (e.g. `/es/`) emit the correct `Content-Language` for that locale. ### Content-Encoding 24. [ ] HTML response with `Accept-Encoding: gzip` returns `Content-Encoding: gzip`. 25. [ ] HTML response with `Accept-Encoding: br` returns `Content-Encoding: br` (if brotli installed). 26. [ ] HTML response with `Accept-Encoding: zstd` returns `Content-Encoding: zstd` (if zstd installed). 27. [ ] CSS, JS, JSON, XML, SVG all compress. 28. [ ] Already compressed types (JPEG, PNG, WebP, MP4, WOFF2) do NOT have `Content-Encoding`. 29. [ ] Compressed responses have `Vary: Accept-Encoding`. 30. [ ] Request without `Accept-Encoding` returns uncompressed body. 31. [ ] No use of `Content-Encoding: deflate`. 32. [ ] Brotli module loaded via `load_module` if used. 33. [ ] Zstd module loaded via `load_module` if used. 34. [ ] Precompressed `.gz`, `.br`, `.zst` files exist for static assets in production. ### Content-Length 35. [ ] Static file response has `Content-Length` (when not on the fly compressed). 36. [ ] On the fly compressed response uses `Transfer-Encoding: chunked`. 37. [ ] No response has both `Content-Length` and `Transfer-Encoding`. 38. [ ] Declared `Content-Length` matches actual body byte count. 39. [ ] Large precompressed files have `Content-Length` showing the compressed size. ### Content-Disposition 40. [ ] PDF download endpoints have `Content-Disposition: attachment`. 41. [ ] Zip and archive endpoints have `Content-Disposition: attachment`. 42. [ ] User upload serving endpoints have `Content-Disposition: attachment` AND `Content-Type: application/octet-stream`. 43. [ ] Non download responses (HTML pages, CSS, JS, images) do NOT have `Content-Disposition: attachment`. 44. [ ] Filename parameters are wrapped in double quotes. 45. [ ] Non ASCII filenames include both `filename=` (ASCII fallback) and `filename*=` (RFC 5987 UTF-8). 46. [ ] User input is never directly concatenated into Content-Disposition values without sanitization. ### Cross-cutting 47. [ ] `nginx -t` passes without warnings. 48. [ ] `nginx -T` (effective config) shows all expected directives. 49. [ ] Lighthouse "Properly size images" and "Enable text compression" warnings are clear. 50. [ ] Bing Webmaster Tools shows correct language detection (if Bing account is connected). A site that passes all 50 is ready for production traffic and crawling. --- ## 14. COMMON PITFALLS Fifteen patterns to recognize and avoid. **Pitfall 1: Default type set to `text/html`.** Symptom: unknown file extensions render as broken HTML in the browser. Why it breaks: `default_type text/html` causes nginx to label every unknown extension as HTML, opening an XSS surface for any user controlled path. Fix: `default_type application/octet-stream`. Add specific types via `types {}` block for any extension you actually serve. **Pitfall 2: Charset missing from text Content-Type.** Symptom: pages with non ASCII characters (curly quotes, em dashes, accented characters) display as mojibake. Why it breaks: browser falls back to Windows-1252 or another locale default. Fix: enable `charset utf-8;` and `charset_types` directive (Section 5.5). **Pitfall 3: Mime type missing for new file extensions.** Symptom: AVIF images not loading, WOFF2 fonts falling back to system fonts, WASM modules failing to instantiate. Why it breaks: `/etc/nginx/mime.types` on older Debian releases predates these formats. nginx falls back to `default_type`. Fix: add types in the http block (Section 5.6). **Pitfall 4: Compressing already compressed content.** Symptom: high CPU on nginx, no measurable savings. Why it breaks: JPEGs, PNGs, MP4s, WOFF2 fonts are already compressed. Gzipping them costs CPU and may even increase size by a few bytes. Fix: do not list `image/jpeg`, `image/png`, `image/webp`, `image/avif`, `video/*`, `audio/*`, `font/woff2`, `application/zip` in `gzip_types`, `brotli_types`, or `zstd_types`. **Pitfall 5: Vary: Accept-Encoding missing.** Symptom: random clients receive binary garbage; cache hit rates from CDNs or corporate proxies break. Why it breaks: a cache serves a gzipped body to a client that did not request gzip. Fix: `gzip_vary on;` (Section 7.4 and framework-http-caching-headers.md Section 9). **Pitfall 6: Content-Disposition: attachment on every response.** Symptom: every page tries to download instead of render. Why it breaks: a server wide `add_header Content-Disposition "attachment"` was added somewhere. Fix: remove the global declaration. Only apply `attachment` to specific download locations. **Pitfall 7: Unsanitized filename in Content-Disposition.** Symptom: security scanner reports vulnerability; user uploads can inject CRLF and forge headers. Why it breaks: unsanitized user input flowing directly into a header value. Fix: sanitize per Section 9.4. Never trust client supplied filenames. **Pitfall 8: User uploaded HTML rendered inline.** Symptom: stored XSS in user uploads. Why it breaks: file served with `Content-Type: text/html` and no `Content-Disposition: attachment`. Fix: see Section 9.5. Force `attachment` plus `application/octet-stream` on every user upload endpoint. **Pitfall 9: Content-Language using underscores or wrong case.** Symptom: language detection fails in screen readers, translation tools, Bing indexing. Why it breaks: `en_US` is POSIX locale syntax, not BCP 47. `EN-US` violates the conventional casing. Fix: `en-US` exactly. Lowercase language, uppercase region, hyphen between. **Pitfall 10: Setting Content-Language without matching `<html lang>`.** Symptom: inconsistent language signals; some crawlers ignore the page, some misindex. Why it breaks: the HTTP header says `es-MX`, the HTML says `<html lang="en">`. Crawlers do not know which to trust. Fix: align all three layers: HTTP header, meta tag, html attribute. **Pitfall 11: Both Content-Length and Transfer-Encoding present.** Symptom: HTTP request smuggling vulnerability scanner alert. Why it breaks: per the RFC, this combination must not occur. Different intermediaries may interpret framing differently, enabling smuggling. Fix: find the upstream emitting both. Remove the manual Content-Length and let chunked dominate. **Pitfall 12: Content-Length wrong for an upstream response.** Symptom: clients report "incomplete response" or truncated content. Why it breaks: upstream computed Content-Length before adding (or after removing) some bytes. Fix: find the upstream code path. Either trust the framework to set Content-Length automatically, or compute it after the body is finalized. **Pitfall 13: Double compression (serving a .gz file with Content-Encoding: gzip).** Symptom: browser fails to decompress. Why it breaks: a `.tar.gz` archive is application data; serving it as `Content-Encoding: gzip` tells the browser to decompress the wire bytes, but then the result is still a `.tar` that the browser cannot show. Fix: serve `.gz` files (when intended as downloads) with `Content-Type: application/gzip` and NO `Content-Encoding`. Use `gzip_static on` for precompressed CSS/JS, which sets the headers correctly. **Pitfall 14: Add_header inheritance trap on Content-Type.** Symptom: nosniff or other security headers missing on certain file types after a location block override. Why it breaks: any `add_header` in a location wipes out all parent add_headers (see framework-http-caching-headers.md Section 5.5). Fix: snippet include pattern in every location. **Pitfall 15: Hardcoded Content-Type overriding nginx mime detection.** Symptom: PNG returns `Content-Type: text/html`, JS returns `Content-Type: text/plain`, etc. Why it breaks: someone added `add_header Content-Type "..."` in the wrong scope. Fix: search the config: `grep -rn "Content-Type" /etc/nginx/`. Remove inappropriate manual declarations. Let mime.types handle it. --- ## 15. DIAGNOSTIC COMMANDS Reference of every command useful for content header investigation. ### Inspect content headers ```bash # Full header set curl -sI https://example.com/page.html # Content family only curl -sI https://example.com/page.html | grep -iE "content-(type|language|encoding|length|disposition)" # As Googlebot curl -sI -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \ https://example.com/page.html # As Bingbot (matters for Content-Language) curl -sI -A "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" \ https://example.com/page.html # As ClaudeBot curl -sI -A "Mozilla/5.0 (compatible; ClaudeBot/1.0; +claudebot@anthropic.com)" \ https://example.com/page.html ``` ### Verify Content-Type ```bash # Confirm declared type curl -sI https://example.com/script.js | grep -i content-type # Verify actual file type curl -s -o /tmp/asset https://example.com/script.js file --mime-type /tmp/asset # Compare to declared # Detect mojibake risk: dump first 200 bytes curl -s https://example.com/ | head -c 200 | xxd # Verify HTML meta charset is in first 1024 bytes curl -s https://example.com/ | head -c 1024 | grep -i charset ``` ### Verify Content-Language ```bash # Header check curl -sI https://example.com/ | grep -i content-language curl -sI https://example.com/es/ | grep -i content-language # HTML lang attribute curl -s https://example.com/ | grep -oE '<html[^>]*lang="[^"]*"' # Meta http-equiv curl -s https://example.com/ | grep -oE '<meta[^>]*http-equiv="content-language"[^>]*>' # All hreflang annotations curl -s https://example.com/ | grep -oE 'hreflang="[^"]*"' | sort -u ``` ### Verify Content-Encoding ```bash # Compression negotiation for enc in gzip br zstd identity; do SIZE=$(curl -s -H "Accept-Encoding: $enc" --output - https://example.com/page.html | wc -c) HDR=$(curl -sI -H "Accept-Encoding: $enc" https://example.com/page.html | grep -i content-encoding | awk '{print $2}' | tr -d '\r') echo "$enc: declared=$HDR size=$SIZE" done # Verify compressed payload actually decompresses to expected content curl -s --compressed https://example.com/page.html > /tmp/decompressed.html curl -s -H "Accept-Encoding: identity" https://example.com/page.html > /tmp/raw.html diff /tmp/decompressed.html /tmp/raw.html && echo "Match" # Check that brotli module is loaded nginx -V 2>&1 | grep -o brotli ls /etc/nginx/modules/ | grep brotli # Check that zstd module is loaded nginx -V 2>&1 | grep -o zstd ls /etc/nginx/modules/ | grep zstd ``` ### Verify Content-Length ```bash # Declared vs actual (identity, no compression) DECLARED=$(curl -sI -H "Accept-Encoding: identity" https://example.com/style.css | grep -i content-length | awk '{print $2}' | tr -d '\r') ACTUAL=$(curl -s -H "Accept-Encoding: identity" https://example.com/style.css | wc -c) echo "Declared: $DECLARED, Actual: $ACTUAL" [ "$DECLARED" = "$ACTUAL" ] && echo "Match" || echo "MISMATCH" # Confirm chunked when compressed curl -sI -H "Accept-Encoding: gzip" https://example.com/page.html | grep -iE "content-length|transfer-encoding" # Look for the dangerous both-headers combo curl -sI https://example.com/api/data | awk 'tolower($0) ~ /^(content-length|transfer-encoding):/ {print}' ``` ### Verify Content-Disposition ```bash # Confirm attachment for downloads curl -sI https://example.com/downloads/report.pdf | grep -i content-disposition # Confirm inline (or absent) for renderable content curl -sI https://example.com/page.html | grep -i content-disposition # Test that user uploads are forced to download curl -sI https://example.com/user-uploads/file.html | grep -iE "content-(type|disposition)" # Both must be present and restrictive # Check filename quoting curl -sI https://example.com/downloads/report.pdf | grep -i content-disposition # Filename should be wrapped in double quotes ``` ### Server side investigation ```bash # Check nginx mime.types cat /etc/nginx/mime.types | grep -i avif cat /etc/nginx/mime.types | grep -i woff2 cat /etc/nginx/mime.types | grep -i wasm # Show all add_header in active config nginx -T 2>/dev/null | grep -i add_header # Show all compression config in effect nginx -T 2>/dev/null | grep -iE "gzip|brotli|zstd" # Show all charset directives nginx -T 2>/dev/null | grep -i charset # Confirm modules loaded nginx -V 2>&1 | tr ' ' '\n' | grep -iE "brotli|zstd" # Inspect source file encoding file -bi /var/www/sites/example.com/index.html # Expected: text/html; charset=utf-8 # Find files that are not UTF-8 find /var/www/sites/example.com -name "*.html" -exec file -bi {} \; | grep -v utf-8 # After config changes nginx -t && systemctl reload nginx ``` ### Browser DevTools quick reference In Chrome DevTools Network panel, click any request, then: * **Headers** tab: shows all response headers including the five in this framework. * **Response** tab: shows the decompressed body. If the body looks like binary garbage here, Content-Encoding is wrong. * **Size column**: shows wire size (compressed). Hover for "encoded size" and "actual size" (decompressed). * The "Type" column shows the browser's interpretation based on Content-Type (Document, Stylesheet, Script, XHR, Image, Font, etc). To force a download when the server returned inline: * Right click on a link, "Save Link As". * Or add `?download=true` and have the upstream check for the query and add `Content-Disposition: attachment`. --- ## 16. CROSS-REFERENCES * [framework-http-caching-headers.md](framework-http-caching-headers.md): Cache-Control, ETag, Last-Modified, Expires, Vary, Age. Vary: Accept-Encoding is shared between this framework and that one; gzip weakens ETags is shared knowledge. * [UNIVERSAL-RANKING-FRAMEWORK.md](UNIVERSAL-RANKING-FRAMEWORK.md): the master ranking reference. Section 24 (Bubbles Nginx config) inherits content header configuration from here. * [SEO-BUILD-REFERENCE.md](SEO-BUILD-REFERENCE.md) v2.4: the build playbook. References this framework for any content header decision. * [framework-security-headers.md](framework-security-headers.md): HSTS, CSP, X-Frame-Options, X-Content-Type-Options. The nosniff header is critical for the Content-Type discipline described here. * [framework-corewebvitals.md](framework-corewebvitals.md): LCP, INP, CLS targets. Content-Encoding directly affects LCP (smaller payloads transmit faster). * [framework-hreflang.md](framework-hreflang.md): international SEO. Companion to Content-Language Section 6 here. * [framework-compression.md](framework-compression.md): deep dive on gzip, brotli, zstd configuration, performance tuning, and migration. This file references it for advanced compression scenarios. * Google official documentation on Content-Type and indexing: https://developers.google.com/search/docs/crawling-indexing/indexable-content * Google international and multilingual sites: https://developers.google.com/search/docs/specialty/international/overview * MDN Content-Type: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type * MDN Content-Disposition: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Disposition * RFC 9110 (HTTP Semantics): https://www.rfc-editor.org/rfc/rfc9110 * RFC 6266 (Content-Disposition in HTTP): https://www.rfc-editor.org/rfc/rfc6266 * RFC 5987 (Character set and language encoding for HTTP header parameters): https://www.rfc-editor.org/rfc/rfc5987 --- ## APPENDIX A: ONE PAGE QUICK REFERENCE For the person who just wants the answer. ### Content-Type table | Asset type | Content-Type | |---|---| | HTML | `text/html; charset=utf-8` | | CSS | `text/css; charset=utf-8` | | JavaScript | `application/javascript; charset=utf-8` | | JSON | `application/json` | | JSON-LD | `application/ld+json` | | XML sitemap | `application/xml` | | SVG | `image/svg+xml` | | PNG | `image/png` | | JPEG | `image/jpeg` | | WebP | `image/webp` | | AVIF | `image/avif` | | WOFF2 | `font/woff2` | | PDF | `application/pdf` | | ZIP | `application/zip` | | WASM | `application/wasm` | | Web manifest | `application/manifest+json` | | Plain text | `text/plain; charset=utf-8` | | Unknown (safe default) | `application/octet-stream` | ### Content-Language * Always `en-US` (or correct BCP 47) on every HTML response. * Match `<html lang="en-US">`. * Match `<meta http-equiv="content-language" content="en-US">`. * Add hreflang for multi region sites. ### Content-Encoding * Compress: HTML, CSS, JS, JSON, XML, SVG, plain text, fonts (ttf/otf only). * Do NOT compress: JPEG, PNG, WebP, AVIF, MP4, WOFF2 (already compressed). * Always pair with `Vary: Accept-Encoding`. * Prefer precompressed (`gzip_static`, `brotli_static`, `zstd_static`) for static assets. ### Content-Length * Auto for static files. * Auto for precompressed files (the compressed size). * Replaced with `Transfer-Encoding: chunked` when on the fly compression is active. * Never appears with `Transfer-Encoding` together. ### Content-Disposition * `attachment` for downloads (PDF, ZIP, generated CSVs). * Omitted (default `inline`) for HTML, CSS, JS, images that should render. * User uploads: always `attachment` plus `application/octet-stream`. * Filenames wrapped in double quotes, both `filename` and `filename*` for non ASCII. Three commands every operator should know: ```bash # Confirm content type is correct curl -sI https://example.com/script.js | grep -i content-type # Confirm compression is working curl -sI -H "Accept-Encoding: br" https://example.com/page.html | grep -iE "content-encoding|vary" # Apply changes nginx -t && systemctl reload nginx ``` And two end to end test invocations: ```bash # Verify a JSON API returns the right type and decompresses correctly curl -s --compressed -H "Accept-Encoding: br, gzip" https://example.com/api/data | head -c 200 curl -sI --compressed https://example.com/api/data | grep -iE "content-(type|encoding|length)" # Verify a PDF download has the right disposition curl -sI https://example.com/downloads/report.pdf | grep -iE "content-(type|disposition|length)" ``` If both produce expected output, the content header stack is correctly wired. --- End of framework-http-content-headers.md.

Search equations, builds, manuscript text, and recovered sources.