Caddyfile
Rama 3.1.x · réplica de lectura del repositorio original.
{
{$CADDY_GLOBAL_OPTIONS}
admin off
auto_https {$AUTO_HTTPS:disable_redirects}
servers {
trusted_proxies static {$CADDY_TRUSTED_PROXIES:private_ranges}
client_ip_headers Cf-Connecting-Ip X-Forwarded-For
}
# Logging
log {
output stdout
format {$SOLIDINVOICE_LOG_FORMAT:console}
level {$LOG_LEVEL:INFO}
}
frankenphp {
{$FRANKENPHP_CONFIG}
}
}
{$CADDY_EXTRA_CONFIG}
{$SERVER_NAME} {
bind {$SERVER_IP:0.0.0.0}
root {$APP_PATH}/public
# Extra directives (for "tls internal" in dev mode)
{$CADDY_SERVER_EXTRA_DIRECTIVES}
# Health check endpoint
@health path /health
respond @health "OK" 200
# When behind Cloudflare, the intermediate proxy (e.g. Traefik, cloudflared)
# may set X-Forwarded-Proto to "http" because its connection to Caddy is plain HTTP.
# Cloudflare's Cf-Visitor header carries the original scheme, so use it to fix the proto.
@cloudflare header Cf-Visitor *https*
request_header @cloudflare X-Forwarded-Proto "https"
request_header @cloudflare X-Forwarded-Port "443"
# Caddy resolves the real client IP from client_ip_headers + trusted_proxies.
# PHP only sees headers, so write it back into X-Forwarded-For (which Symfony
# trusts). Untrusted peers get their own IP here, so XFF cannot be forged.
request_header X-Forwarded-For {client_ip}
# Basic security headers (for self-hosted deployments)
header {
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
-Permissions-Policy "browsing-topics=()"
-Server
}
# Access logging (JSON format for log ingestion)
# Note: For sensitive data filtering (headers, query params), configure your
# log aggregation system (e.g., Loki, Elasticsearch) to redact sensitive fields
log {
{$CADDY_SERVER_LOG_OPTIONS}
output stdout
format {$SOLIDINVOICE_LOG_FORMAT:console} {
time_format iso8601
time_key ts
level_key level
message_key msg
}
}
# Static asset caching: files with a content hash in the name never change, so cache them
# for good; anything else keeps its name across releases and must be revalidated.
@static_hashed {
path /css/* /js/* /static/* /images/* /fonts/* /build/* *.ico *.png *.jpg *.svg *.woff *.woff2
path_regexp \.[0-9a-f]{8,}\.[a-z0-9]+$
}
header @static_hashed Cache-Control "public, max-age=31536000, immutable" defer
@static {
path /css/* /js/* /static/* /images/* /fonts/* /build/* *.ico *.png *.jpg *.svg *.woff *.woff2
not path_regexp \.[0-9a-f]{8,}\.[a-z0-9]+$
}
header @static Cache-Control "public, max-age=3600, must-revalidate" defer
# Compression
encode zstd br gzip
# mercure {
# # Transport to use (default to Bolt)
# transport_url {$MERCURE_TRANSPORT_URL:bolt:///data/mercure.db}
# # Publisher JWT key
# publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
# # Subscriber JWT key
# subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
# # Allow anonymous subscribers (double-check that it's what you want)
# anonymous
# # Enable the subscription API (double-check that it's what you want)
# subscriptions
# # Extra directives
# {$MERCURE_EXTRA_DIRECTIVES}
# }
# vulcain
@phpRoute {
not path /.well-known/mercure*
not path /health
not file {path}
}
rewrite @phpRoute index.php
@frontController path index.php
php @frontController
file_server {
hide .env* .git* .php* composer.* package.json *.lock
}
}