{ email {$ACME_EMAIL} admin off servers { listener_wrappers { proxy_protocol { timeout 2s allow {$TRUST_PROXY} } tls } trusted_proxies static {$TRUST_PROXY} client_ip_headers X-Forwarded-For X-Real-IP } } # Internal only, for the container healthcheck. Never published in compose. :8080 { respond /healthz "ok" 200 respond "not found" 404 } http://{$SITE_ADDRESS} { handle /.well-known/acme-challenge/* { respond "ACME challenge not found" 404 } handle { redir https://{$SITE_ADDRESS}{uri} 308 } } {$SITE_ADDRESS} { root * /srv encode zstd gzip # The service worker and the HTML shell must be revalidated on every request. # If a browser is allowed to serve sw.js from its own cache, a new build is # never discovered and the in-app update prompt can never fire — the app would # silently freeze on whatever version it first installed. @volatile path / /index.html /sw.js /manifest.webmanifest header @volatile Cache-Control "no-cache, must-revalidate" # Content-hashed filenames; the name changes whenever the bytes do. @hashed path /assets/* /workbox-*.js header @hashed Cache-Control "public, max-age=31536000, immutable" # Icons keep stable names across builds, so cap them at a day rather than # forever, otherwise a replaced icon never surfaces. @icons path *.png *.svg *.ico header @icons Cache-Control "public, max-age=86400" # Some clients reject a manifest that is not served with this exact type. @manifest path /manifest.webmanifest header @manifest Content-Type "application/manifest+json" header { X-Content-Type-Options nosniff X-Frame-Options DENY Referrer-Policy strict-origin-when-cross-origin -Server } # The app routes on the hash, so this only catches someone typing a stray path. try_files {path} /index.html file_server }