# HTTPS with automatic Let's Encrypt (only if CADDY_DOMAIN is set) # Fallback: HTTP only if no domain { auto_https off } :443 { reverse_proxy web:3000 # Security Headers header { X-Content-Type-Options nosniff X-Frame-Options DENY X-XSS-Protection "1; mode=block" Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://*; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'" Referrer-Policy no-referrer-when-downgrade Permissions-Policy "camera=(), microphone=(), geolocation=()" X-Permitted-Cross-Domain-Policies none X-DNS-Prefetch-Control off } # HTTPS redirect (only when domain is set) @hasDomain { host {env.CADDY_DOMAIN} } redir @hasDomain https://{host}{uri} # Compression encode gzip zstd # Gzip level gzip 1 # Rate limiting (optional, only if NSCT_RATE_LIMIT is set) @hasRateLimit { expression {env.NSCT_RATE_LIMIT} != "" } # Log log { format json output stdout } } # HTTP fallback (when no domain is set) :80 { reverse_proxy web:3000 header { X-Content-Type-Options nosniff X-Frame-Options DENY X-XSS-Protection "1; mode=block" Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' https://*; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'self'" Referrer-Policy no-referrer-when-downgrade } encode gzip zstd }