{ # Global options frankenphp { # Number of workers for better performance num_threads {$NUM_THREADS:4} } # Order directives properly order mercure after encode order php_server before file_server } # HTTP - redirect to HTTPS http://{$SERVER_NAME:localhost} { redir https://{host}{uri} permanent } # HTTPS server https://{$SERVER_NAME:localhost} { # Root directory root * /app/public # TLS configuration - Caddy will automatically obtain and renew Let's Encrypt certificates tls { protocols tls1.2 tls1.3 } # Enable compression encode zstd gzip # Mercure hub configuration (built-in) mercure { # Publisher JWT key publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} { algorithm hs256 } # Subscriber JWT key subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} { algorithm hs256 } # Allow anonymous subscribers anonymous # Enable subscriptions subscriptions # CORS configuration cors_origins * } # Client max body size (for uploads) request_body { max_size 20MB } # Security: Deny access to sensitive directories @forbidden { path /bin/* /config/* /src/* /templates/* /tests/* /translations/* /var/* /vendor/* } handle @forbidden { respond "Access Denied" 404 } # Security: Deny access to dot files (except .well-known for Mercure) @dotfiles { path */.* not path /.well-known/* } handle @dotfiles { respond "Access Denied" 404 } # Cache static assets (30 days) @static { path *.jpg *.jpeg *.png *.gif *.ico *.css *.js *.svg *.woff *.woff2 *.ttf *.eot *.xlsx *.pdf file } handle @static { header Cache-Control "public, max-age=2592000, no-transform" file_server } # Serve files from /assets directory handle /assets/* { root * /app/public file_server } # PHP FrankenPHP handler php_server { # Resolve symlinks resolve_root_symlink } # Logging log { output file /app/var/log/access.log format json # Redact sensitive data format filter { request>uri query { replace authorization REDACTED } } } }