nginx

Nginx Static File Serving Configuration

Optimized Nginx config for serving static files with caching headers, directory structure, and SPA fallback support.

Overview

Nginx excels at serving static files directly from disk with minimal overhead. This configuration includes aggressive caching for hashed assets, proper MIME types, SPA (single-page application) fallback routing, and security hardening.

Configuration

# /etc/nginx/sites-available/static-site.conf

server {
    listen 80;
    server_name static.example.com;

    # Root directory for static files
    root /var/www/html;
    index index.html;

    # Character encoding
    charset utf-8;

    # Disable access log for static assets (reduce I/O)
    access_log off;
    error_log /var/log/nginx/static_error.log warn;

    # Security: hide Nginx version
    server_tokens off;

    # Hashed assets — cache aggressively (JS, CSS, images with hash in filename)
    location ~* \.(js|css)$ {
        expires 1y;                          # Cache for 1 year
        add_header Cache-Control "public, immutable";  # Never revalidate
        add_header X-Content-Type-Options "nosniff";
        try_files $uri =404;
    }

    # Images and fonts — cache for 30 days
    location ~* \.(png|jpg|jpeg|gif|svg|ico|webp|avif|woff2?|ttf|eot)$ {
        expires 30d;
        add_header Cache-Control "public";
        try_files $uri =404;
    }

    # Media files — cache for 7 days
    location ~* \.(mp4|webm|ogg|mp3|wav)$ {
        expires 7d;
        add_header Cache-Control "public";
        try_files $uri =404;
    }

    # HTML files — no caching (always fetch latest)
    location ~* \.html$ {
        expires -1;                          # No cache
        add_header Cache-Control "no-store, no-cache, must-revalidate";
        try_files $uri =404;
    }

    # SPA fallback — serve index.html for all unmatched routes
    location / {
        try_files $uri $uri/ /index.html;    # Check file, then directory, then fallback
    }

    # Block access to hidden files (.env, .git, etc.)
    location ~ /\. {
        deny all;
        return 404;
    }

    # Block access to source maps in production
    location ~* \.map$ {
        deny all;
        return 404;
    }

    # Custom 404 page
    error_page 404 /404.html;
    location = /404.html {
        internal;                            # Only accessible via error_page
    }
}

Key Options Explained

  • try_files $uri $uri/ /index.html — First checks if the exact file exists, then the directory, then falls back to index.html. Essential for SPAs with client-side routing.
  • expires 1y + immutable — Tells browsers to cache the file for one year and never revalidate. Use only for files with content hashes in their filenames (e.g., app.a1b2c3.js).
  • expires -1 — Sends headers that instruct browsers to always revalidate HTML files, ensuring users get the latest version.
  • access_log off — Disabling access logs for static assets reduces disk I/O and improves performance under heavy traffic.
  • internal — The error page location can only be reached through internal Nginx redirects, not direct client requests.

Common Modifications

  • Remove SPA fallback: For a plain static site, change try_files to try_files $uri $uri/ =404; to return 404 for missing files.
  • Enable directory listing: Add autoindex on; inside a location block to allow browsing directories.
  • Serve pre-compressed files: Add gzip_static on; so Nginx serves .gz files if they exist alongside originals.
  • Add CORS headers: Add add_header Access-Control-Allow-Origin "*"; for assets served to other domains.
  • Custom MIME types: Include include /etc/nginx/mime.types; and add entries for formats like .wasm or .avif.