vcl 4.1; import vsthrottle; import std; backend app { .host = "app"; .port = "${APP_PORT}"; } /* unikamy duplikatu; dodajemy IPv6 */ acl purge { "127.0.0.1"; "::1"; } sub vcl_recv { # RATE LIMIT: 100 żądań / 10s, blokada 60s if (vsthrottle.is_denied(client.identity, 100, 10s, 60s)) { 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 { # Treści prywatne / zakazane do cache if (beresp.http.Cache-Control ~ "(?i)no-store|private") { set beresp.uncacheable = true; set beresp.ttl = 0s; return (deliver); } # Preferuj s-maxage, potem max-age if (beresp.http.Cache-Control ~ "(?i)s-maxage=([0-9]+)") { set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*s-maxage=([0-9]+).*", "\1") + "s", 0s); } else if (beresp.http.Cache-Control ~ "(?i)max-age=([0-9]+)") { set beresp.ttl = std.duration(regsub(beresp.http.Cache-Control, "(?i).*max-age=([0-9]+).*", "\1") + "s", 0s); } else if (beresp.http.Expires) { # fallback na Expires set beresp.ttl = std.time(beresp.http.Expires, now, 0s) - now; } else { # ostateczny fallback set beresp.ttl = 60s; } # Jeśli immutable – zwiększ grace/keep if (beresp.http.Cache-Control ~ "(?i)immutable") { set beresp.grace = 1h; set beresp.keep = 24h; } # statykom daj minimalne TTL, gdy backend NIE ustawił CC if ((bereq.url ~ "^/static/" || bereq.url ~ "\.(css|js|png|jpg|svg|ico|woff2?)$") && !(beresp.http.Cache-Control ~ "(?i)(s-maxage|max-age)")) { set beresp.ttl = 24h; } } sub vcl_deliver { if (obj.hits > 0) { set resp.http.X-Cache = "HIT"; # ukryj niepotrzebny nagłówek z MISS #} 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 = "100"; #set resp.http.X-RateLimit-Window = "10s"; #set resp.http.X-RateLimit-Remaining = vsthrottle.remaining(client.identity, 100, 10s, 60s); #set resp.http.Retry-After = vsthrottle.blocked(client.identity, 100, 10s, 60s); unset resp.http.Via; unset resp.http.X-Varnish; #unset resp.http.Age; unset resp.http.Server; }