vcl 4.1; import vsthrottle; backend app { .host = "app"; .port = "${APP_PORT}"; } acl purge { "localhost"; "127.0.0.1"; } sub vcl_recv { # RATE LIMIT: 10 żądań / 10s, po przekroczeniu blokada na 30s if (vsthrottle.is_denied(client.identity, 10, 10s, 30s)) { return (synth(429, "Too Many Requests")); } # PURGE tylko lokalnie if (req.method == "PURGE") { if (!client.ip ~ purge) { return (synth(405, "Not allowed")); } return (purge); } # omijamy cache dla healthchecków / wewn. nagłówka if (req.url == "/healthcheck" || req.http.X-Internal-Check) { return (pass); } # metody inne niż GET/HEAD bez cache if (req.method != "GET" && req.method != "HEAD") { return (pass); } # statyczne – agresywny cache if (req.url ~ "^/static/" || req.url ~ "\.(css|js|png|jpg|svg|ico|woff2?)$") { return (hash); } return (hash); } sub vcl_backend_response { if (bereq.url ~ "^/static/" || bereq.url ~ "\.(css|js|png|jpg|svg|ico|woff2?)$") { set beresp.ttl = 24h; } else { if (beresp.http.Cache-Control ~ "no-cache|no-store|private") { set beresp.uncacheable = true; set beresp.ttl = 0s; } else { set beresp.ttl = 60s; # domyślny TTL } } } sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } # Nagłówki rate limit – MUSZĄ używać tej samej czwórki parametrów co is_denied() set resp.http.X-RateLimit-Limit = "10"; set resp.http.X-RateLimit-Window = "10s"; set resp.http.X-RateLimit-Remaining = vsthrottle.remaining(client.identity, 10, 10s, 30s); set resp.http.Retry-After = vsthrottle.blocked(client.identity, 10, 10s, 30s); unset resp.http.Via; unset resp.http.X-Varnish; #unset resp.http.Age; unset resp.http.Server; }