# 2023-04-01 vcl 4.1; import std; # -------------------------------------------------- # Backends # -------------------------------------------------- backend default none; backend subdomain { .host = "[service ip]"; .port = "[service port]"; } # -------------------------------------------------- # VCL # -------------------------------------------------- sub vcl_recv { # Normalize host header set req.http.host = std.tolower(req.http.host); # Normalize url set req.url = std.tolower(req.url); # Remove empty query string parameters # e.g.: www.example.com/index.html? if (req.url ~ "\?$") { set req.url = regsub(req.url, "\?$", ""); } # Remove port number from host header set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); # Sorts query string parameters alphabetically for cache normalization purposes set req.url = std.querysort(req.url); # Remove the proxy header to mitigate the httpoxy vulnerability # See https://httpoxy.org/ unset req.http.proxy; # Add X-Forwarded-Proto header when using https if (!req.http.X-Forwarded-Proto && (std.port(server.ip) == 443)) { set req.http.X-Forwarded-Proto = "https"; } # Remove cookies except for these url: # /admin # /ghost if ( !(req.url ~ "^/admin/") && !(req.url ~ "^/ghost/") ) { unset req.http.Cookie; } # Remove has_js and Google Analytics __* cookies. set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-z]+|has_js)=[^;]*", ""); # Remove a ";" prefix, if present. set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", ""); # # Setup backend depending on 'host' name # if (req.http.host ~ "subdomain.domain.com") { set req.backend_hint = subdomain; } # Pipe (direct connection on to the backend) for websocket if (req.http.upgrade ~ "(?i)websocket") { return (pipe); } # Non-RFC2616 or CONNECT which is weird if (req.method != "GET" && req.method != "HEAD" && req.method != "PUT" && req.method != "POST" && req.method != "TRACE" && req.method != "OPTIONS" && req.method != "DELETE") { return (pipe); } # Don't cache for these url: # /api # /admin # /ghost # /p if (req.url ~ "/(api|admin|p|ghost)/") { return (pass); } # Mark static files with the X-Static-File header, and remove any cookies if ( req.url ~ "^[^?]*\.(7z|avi|avif|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|json|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|ogg|ogm|opus|otf|pdf|png|ppt|pptx|rar|rtf|svg|svgz|swf|tar|tbz|tgz|ttf|txt|txz|wav|webm|webp|woff|woff2|xls|xlsx|xml|xz|zip)(\?.*)?$" ) { set req.http.X-Static-File = "true"; unset req.http.Cookie; } return (hash); } sub vcl_hash { # Normalize url set req.url = std.tolower(req.url); hash_data(req.url); if (req.http.host) { hash_data(req.http.host); } else { hash_data(server.ip); } return (lookup); } sub vcl_backend_response { # Define grace set beresp.grace = 2m; set beresp.keep = 8m; # Here you clean the response headers, removing silly Set-Cookie headers and other mistakes your backend does # Inject URL & Host header into the object for asynchronous banning purposes set beresp.http.x-url = bereq.url; set beresp.http.x-host = bereq.http.host; # Default TTL set beresp.ttl = 60s; if (bereq.url ~ "^/static/") { set beresp.ttl = 1d; } # Keep the response in cache for 4 hours if the response has validating headers if (beresp.http.ETag || beresp.http.Last-Modified) { set beresp.keep = 4h; } # Allow GZIP compression on all JavaScript/CSS files and all text-based content # Allow caching extension if (beresp.http.content-type ~ "text/plain|text/css|application/json|application/x-javascript|text/xml|application/xml|application/xml+rss|text/javascript" ) { set beresp.do_gzip = true; set beresp.http.cache-control = "public, max-age=1209600"; } # Remove the Set-Cookie header for cacheable content # Only for HTTP GET & HTTP HEAD requests if (beresp.ttl > 0s && (bereq.method == "GET" || bereq.method == "HEAD")) { unset beresp.http.set-cookie; } # Don't cache content with a negative TTL # Don't cache content for no-cache or no-store content # Don't cache content where all headers are varied if ( beresp.ttl <= 0s || beresp.http.Surrogate-control ~ "no-store" || (!beresp.http.Surrogate-Control && beresp.http.Cache-Control ~ "no-cache|no-store") || beresp.http.Vary == "*") { # Mark as Hit-For-Pass for the next 2 minutes set beresp.ttl = 120s; set beresp.uncacheable = true; } # Cache only successfully responses if ( beresp.status != 200 && beresp.status != 410 && beresp.status != 301 && beresp.status != 302 && beresp.status != 304 && beresp.status != 307 ) { set beresp.http.X-Cacheable = "NO:UNCACHEABLE"; set beresp.ttl = 10s; set beresp.uncacheable = true; } else { # If we dont get a Cache-Control header from the backend we default to cache for all objects if (!beresp.http.Cache-Control) { set beresp.ttl = 1h; set beresp.http.X-Cacheable = "YES:FORCED"; } # If the file is marked as static we cache it if (bereq.http.X-Static-File == "true") { unset beresp.http.Set-Cookie; set beresp.http.X-Cacheable = "YES:FORCED:STATIC"; set beresp.ttl = 1h; } if (beresp.http.Set-Cookie) { set beresp.http.X-Cacheable = "NO:GOTCOOKIES"; } elseif (beresp.http.Cache-Control ~ "private") { if (beresp.http.Cache-Control ~ "public" && bereq.http.X-Static-File == "true" ) { set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private,", ""); set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private", ""); set beresp.http.X-Cacheable = "YES"; } elseif (bereq.http.X-Static-File == "true" && (beresp.http.Content-type ~ "image\/webp" || beresp.http.Content-type ~ "image\/avif") ) { set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private,", ""); set beresp.http.Cache-Control = regsub(beresp.http.Cache-Control, "private", ""); set beresp.http.X-Cacheable = "YES"; } else { set beresp.http.X-Cacheable = "NO:CACHE-CONTROL=PRIVATE"; } } } return (deliver); } sub vcl_hit { if (obj.ttl >= 0s) { return (deliver); } if (std.healthy(req.backend_hint)) { if (obj.ttl + 300s > 0s) { # Hit after TTL expiration, but within grace period set req.http.grace = "normal (healthy server)"; return (deliver); } else { # Hit after TTL and grace expiration return (restart); } } else { # Server is not healthy, retrieve from cache set req.http.grace = "unlimited (unhealthy server)"; return (deliver); } return (restart); } sub vcl_deliver { # Debug header if (req.http.X-Cacheable) { set resp.http.X-Cacheable = req.http.X-Cacheable; } elseif (obj.uncacheable) { if (!resp.http.X-Cacheable) { set resp.http.X-Cacheable = "NO:UNCACHEABLE"; } } elseif (!resp.http.X-Cacheable) { set resp.http.X-Cacheable = "YES"; } # End Debug Header if (resp.http.X-Varnish ~ "[0-9]+ +[0-9]+") { set resp.http.X-Cache = "HIT"; } else { set resp.http.X-Cache = "MISS"; } set resp.http.X-Cache-Hits = obj.hits; return (deliver); } sub vcl_pipe { if (req.http.upgrade) { set bereq.http.upgrade = req.http.upgrade; set bereq.http.connection = req.http.connection; } }