├── .gitignore ├── README.md ├── conf.d ├── error-404.vcl ├── error.vcl ├── fetch │ ├── drupal7.vcl │ ├── forkcms.vcl │ └── wordpress.vcl └── receive │ ├── drupal7.vcl │ ├── forkcms.vcl │ └── wordpress.vcl ├── custom.acl.vcl-sample ├── custom.backend.vcl-sample ├── custom.fetch.vcl-sample ├── custom.recv.vcl-sample └── production.vcl /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore these files 2 | .gitconfig 3 | secret 4 | default.vcl 5 | custom.acl.vcl 6 | custom.backend.vcl 7 | custom.fetch.vcl 8 | custom.recv.vcl 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Varnish Configuration Templates (boilerplate) 2 | 3 | ### 🚀 Need help implementing Varnish? 4 | 5 | I'm available [for consultancy](https://ma.ttias.be/consultancy/) if you're struggling with implementing Varnish and speeding up your site. Don't be afraid to [reach out!](https://ma.ttias.be/consultancy/) 6 | 7 | ### Warning: Varnish 3 is [end-of-life](https://ma.ttias.be/varnish-cache-3-0-is-end-of-life/) 8 | 9 | You can still use Varnish 3 of course, but there will be no more security or bug fixes to the Varnish 3.x release. It's probably wise to focus your Varnish adventures on the new [varnish 4 VCL config template](https://ma.ttias.be/varnish-4-0-0-released-together-with-configuration-templates/). 10 | 11 | 12 | ### Installation 13 | 14 | You can use the configuration templates found in this repository to quickly get started with a complete Varnish configuration that offers support for most functionality. Start of by looking into "production.vcl" and taking the bits you need, copy it to your own default.vcl. 15 | 16 | ### What is it? 17 | 18 | A set of configuration samples used for Varnish 3.0. This includes templates for: 19 | * Wordpress 20 | * Drupal (works decently for Drupal 7, depends on your addons obviously) 21 | * Joomla (WIP) 22 | * Fork CMS 23 | * OpenPhoto 24 | 25 | And various configuration for: 26 | 27 | * Server-side URL rewriting 28 | * Clean error pages for debugging 29 | * Virtual Host implementations 30 | * Various header normalizations 31 | * Cookie manipulations 32 | * 301/302 redirects from within Varnish 33 | 34 | ### Common troubleshooting 35 | 36 | Common list of errors and their fixes: 37 | 38 | * [FetchError http first read error: -1 11 (Resource temporarily unavailable)](https://ma.ttias.be/varnish-fetcherror-http-first-read-error-1-11-resource-temporarily-unavailable/) 39 | * [FetchError: straight insufficient bytes](https://ma.ttias.be/varnish-fetcherror-straight-insufficient-bytes/) 40 | * [FetchError: Gunzip+ESI Failed at the very end](https://ma.ttias.be/varnish-fetcherror-testgunzip-gunzip-esi-failed-very-end/) 41 | 42 | Basic troubleshooting: 43 | 44 | * [Test if your Varnish VCL compiles and Varnish starts](https://ma.ttias.be/varnish-running-in-foreground-but-fails-to-run-as-servicedaemon/) 45 | * [See which cookies are being stripped in your VCL](https://ma.ttias.be/varnish-tip-see-cookies-stripped-vcl/) 46 | * [Reload Varnish VCL without losing cache data](https://ma.ttias.be/reload-varnish-vcl-without-losing-cache-data/) 47 | * [Combine Apache'S HTTP authentication with Varnish IP whitelisting](https://ma.ttias.be/apache-http-authentication-with-x-forwarded-for-ip-whitelisting-in-varnish/) 48 | 49 | [Click here for a Varnish 4 VCL config template](https://github.com/mattiasgeniar/varnish-4.0-configuration-templates) 50 | 51 | [Click here for a Varnish 5 VCL config template](https://github.com/mattiasgeniar/varnish-5.0-configuration-templates) 52 | -------------------------------------------------------------------------------- /conf.d/error-404.vcl: -------------------------------------------------------------------------------- 1 | # The vcl_error() procedure 2 | set obj.http.Content-Type = "text/html; charset=utf-8"; 3 | set obj.http.Retry-After = "5"; 4 | 5 | synthetic {" 6 | 7 | 8 | 9 | 10 | "} + obj.status + " " + obj.response + {" 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 27 | 28 | 29 |
30 | 33 |
34 | 35 | 36 | 37 | "}; 38 | -------------------------------------------------------------------------------- /conf.d/error.vcl: -------------------------------------------------------------------------------- 1 | # The vcl_error() procedure 2 | set obj.http.Content-Type = "text/html; charset=utf-8"; 3 | set obj.http.Retry-After = "5"; 4 | 5 | synthetic {" 6 | 7 | 8 | 9 | 10 | "} + obj.status + " " + obj.response + {" 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 27 | 28 | 29 |
30 | 33 |
34 | 35 | We're very sorry, but the page could not be loaded properly. 36 | 37 |
38 | 39 |
This should be fixed very soon, and we apologize for any inconvenience.
40 | 41 |
42 | 45 |
46 |
47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 |

Debug Information

VariableValue
General
XID"} + req.xid + {"
Time"} + now + {"
Request
HTTP host"} + req.http.Host + {"
Request type"} + req.request + {"
HTTP Protocol version"} + req.proto + {"
URL"} + req.url + {"
Cookies"} + regsuball(req.http.cookie, "; ", "
") + {"
Accept-Encoding"} + req.http.Accept-Encoding + {"
Cache-Control"} + req.http.Cache-Control + {"
HTTP header"} + req.http.header + {"
GZIP supported"} + req.can_gzip + {"
Backend"} + req.backend + {"
Server
Identity"} + server.identity + {"
IP:port"} + server.ip + {":"} + server.port + {"
Client
IP"} + client.ip + {"
129 |
130 |
131 |
132 | 134 | 135 | 136 | 137 | 138 | "}; 139 | -------------------------------------------------------------------------------- /conf.d/fetch/drupal7.vcl: -------------------------------------------------------------------------------- 1 | # The vcl_fetch routine, when the request is fetched from the backend 2 | 3 | # For static content related to the theme, strip all backend cookies 4 | # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/ 5 | if (req.url ~ "^/themes/" && req.url ~ "\.(css|js|png|gif|jp(e?)g)") { 6 | unset beresp.http.cookie; 7 | } 8 | 9 | # A TTL of 30 minutes 10 | set beresp.ttl = 1800s; 11 | -------------------------------------------------------------------------------- /conf.d/fetch/forkcms.vcl: -------------------------------------------------------------------------------- 1 | # The vcl_fetch routine, when the request is fetched from the backend 2 | 3 | # If it's from the /private area, don't touch it 4 | if (req.url !~ "(private|backend)") { 5 | # Remove the PHPSESSID cookie 6 | set beresp.http.cookie = regsuball(beresp.http.cookie, "PHPSESSID=[^;]+(; )?", ""); 7 | } 8 | 9 | # For static content related to the theme, strip all backend cookies 10 | # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/ 11 | if (req.url ~ "^/frontend/" && req.url ~ "\.(css|js|png|gif|jp(e?)g)") { 12 | unset beresp.http.cookie; 13 | } 14 | 15 | # A TTL of 30 minutes 16 | set beresp.ttl = 1800s; 17 | -------------------------------------------------------------------------------- /conf.d/fetch/wordpress.vcl: -------------------------------------------------------------------------------- 1 | # Drop any cookies Wordpress tries to send back to the client. 2 | if (!(req.url ~ "wp-(login|admin)")) { 3 | unset beresp.http.set-cookie; 4 | } 5 | -------------------------------------------------------------------------------- /conf.d/receive/drupal7.vcl: -------------------------------------------------------------------------------- 1 | # A configuration file specific for Drupal 7 2 | 3 | # Either the admin pages or the login 4 | if (req.url ~ "/admin/?") { 5 | # Don't cache, pass to backend 6 | return (pass); 7 | } 8 | 9 | # Static content unique to the theme can be cached (so no user uploaded images) 10 | # Before you blindly enable this, have a read here: http://mattiasgeniar.be/2012/11/28/stop-caching-static-files/ 11 | if (req.url ~ "^/themes/" && req.url ~ "\.(css|js|png|gif|jp(e)?g)") { 12 | unset req.http.cookie; 13 | } 14 | 15 | # Don't cache the install, update or cron files in Drupal 16 | if (req.url ~ "install\.php|update\.php|cron\.php") { 17 | return (pass); 18 | } 19 | 20 | # Uncomment this to trigger the vcl_error() subroutine, which will HTML output you some variables (HTTP 700 = pretty debug) 21 | #error 700; 22 | 23 | # Anything else left? 24 | if (!req.http.cookie) { 25 | unset req.http.cookie; 26 | } 27 | 28 | # Try a cache-lookup 29 | return (lookup); 30 | -------------------------------------------------------------------------------- /conf.d/receive/forkcms.vcl: -------------------------------------------------------------------------------- 1 | # A configuration file specific to Fork CMS 2 | 3 | # Either the admin pages or the login 4 | if (req.url ~ "(private|backend)") { 5 | # Don't cache, pass to backend 6 | return (pass); 7 | } 8 | 9 | # Someone placed comments on the site, there are still cookies left 10 | if (req.http.cookie ~ "comment_(website|email|author)") { 11 | # Don't cache these pages, allow direct request 12 | return (pass); 13 | } 14 | 15 | # If no "comment_" cookies were found, we will simply remove the PHPSESSID 16 | # If your PHP configuration has a different naming for the PHP Session IDs, change it here 17 | set req.http.Cookie = regsuball(req.http.Cookie, "PHPSESSID=[^;]+(; )?", ""); 18 | 19 | # Anything else left? 20 | if (!req.http.cookie) { 21 | unset req.http.cookie; 22 | } 23 | 24 | # Try a cache-lookup 25 | return (lookup); 26 | -------------------------------------------------------------------------------- /conf.d/receive/wordpress.vcl: -------------------------------------------------------------------------------- 1 | # Drop any cookies sent to Wordpress. 2 | if (!(req.url ~ "wp-(login|admin)")) { 3 | unset req.http.cookie; 4 | } 5 | 6 | # Anything else left? 7 | if (!req.http.cookie) { 8 | unset req.http.cookie; 9 | } 10 | 11 | # Try a cache-lookup 12 | return (lookup); 13 | -------------------------------------------------------------------------------- /custom.acl.vcl-sample: -------------------------------------------------------------------------------- 1 | acl purge { 2 | # For now, I'll only allow purges coming from localhost 3 | "127.0.0.1"; 4 | "localhost"; 5 | } 6 | -------------------------------------------------------------------------------- /custom.backend.vcl-sample: -------------------------------------------------------------------------------- 1 | # First backend definition, with a built-in probe 2 | backend default { 3 | # I have Virtual Hosts that only listen to the Public IP 4 | # so no 127.0.0.1 for me 5 | # Backend is running on port 81 6 | .host = "127.0.0.1"; 7 | .port = "80"; 8 | .probe = { 9 | .url = "/ping"; 10 | .timeout = 1s; 11 | .interval = 10s; 12 | .window = 5; 13 | .threshold = 2; 14 | } 15 | .first_byte_timeout = 300s; # How long to wait before we receive a first byte from our backend? 16 | .connect_timeout = 5s; # How long to wait for a backend connection? 17 | .between_bytes_timeout = 2s; # How long to wait between bytes received from our backend? 18 | } 19 | 20 | # Backend definition with entirely custom backend probes (raw HTTP headers) in a separated definition 21 | backend custom_healthchecks { 22 | .host = "127.0.0.1"; 23 | .port = "80"; 24 | .probe = custom_backend_probe; # Refer to the probe definition below, so we can reuse the probe. 25 | 26 | .first_byte_timeout = 300s; 27 | .connect_timeout = 5s; 28 | .between_bytes_timeout = 2s; 29 | } 30 | 31 | probe custom_backend_probe { 32 | .request = "GET /some/url HTTP/1.1" 33 | "Host: some.virtualhost.domain.Tld" 34 | "Connection: close" 35 | "Accept-Encoding: text/html" 36 | "User-Agent: Varnish Health Probe"; 37 | .interval = 10s; 38 | .timeout = 10s; 39 | .window = 2; 40 | .threshold = 2; 41 | } 42 | 43 | # Below is an example redirector based on the Client IP (same returning class C subnet IP will get 44 | # rerouted to the same backend, as long as it's available). 45 | # 46 | # In order for these to work, you need to define 2 backends as shown above named 'web1' and 'web2' (replace 'default' 47 | # from the example above). 48 | director pool_clientip client { 49 | { 50 | .backend = web1; 51 | .weight = 1; 52 | } 53 | { 54 | .backend = web2; 55 | .weight = 1; 56 | } 57 | } 58 | 59 | # Below is an example that will round-robin (based on the weight) to each backend. 60 | # Web2 will get twice the hits as web1 since it has double the weight (= preference). 61 | director pool_clientip { 62 | { 63 | .backend = web1 64 | .weight = 1; 65 | } 66 | { 67 | .backend = web2 68 | .weight = 2; 69 | } 70 | } 71 | 72 | -------------------------------------------------------------------------------- /custom.fetch.vcl-sample: -------------------------------------------------------------------------------- 1 | # stream big file, add missing extention below 2 | if (req.url ~ "\.(avi|deb|tar|gz|rar|iso|img|dmg|mkv|zip)$") { 3 | set beresp.do_stream = true; 4 | set beresp.ttl = 1d; 5 | } 6 | if (req.http.Host == "my-wp1.localhost" || req.http.Host == "my-wp2.localhost") { 7 | # Since this is a Wordpress setup, the Wordpress-specific Fetch 8 | include "conf.d/fetch/wordpress.vcl"; 9 | } elsif (req.http.Host == "my-formcms1.localhost" || req.http.Host == "my-forkcms2.localhost") { 10 | # Include the FormCMS specific VCL 11 | include "conf.d/fetch/forkcms.vcl"; 12 | } elsif (req.http.Host == "my-drupal1.localhost" || req.http.Host == "my-drupal2.localhost") { 13 | # Include the Drupal 7 specific VCL 14 | include "conf.d/fetch/drupal7.vcl"; 15 | } 16 | -------------------------------------------------------------------------------- /custom.recv.vcl-sample: -------------------------------------------------------------------------------- 1 | # Include the correct Virtual Host configuration file 2 | if (req.http.Host == "my-wp1.localhost" || req.http.Host == "my-wp2.localhost") { 3 | # The Wordpress specific receive 4 | include "conf.d/receive/wordpress.vcl"; 5 | } elsif (req.http.Host == "my-forkcms1.localhost" || req.http.Host == "my-forkcms.localhost") { 6 | # The ForkCMS specific config 7 | include "conf.d/receive/forkcms.vcl"; 8 | } elsif (req.http.Host == "my-drupal1.localhost" || req.http.Host == "my-drupal2.localhost") { 9 | # The Drupal 7 specific receive 10 | include "conf.d/receive/drupal7.vcl"; 11 | } 12 | -------------------------------------------------------------------------------- /production.vcl: -------------------------------------------------------------------------------- 1 | # Default backend definition. Set this to point to your content server. 2 | # all paths relative to varnish option vcl_dir 3 | 4 | include "custom.backend.vcl"; 5 | include "custom.acl.vcl"; 6 | 7 | # Handle the HTTP request received by the client 8 | sub vcl_recv { 9 | # shortcut for DFind requests 10 | if (req.url ~ "^/w00tw00t") { 11 | error 404 "Not Found"; 12 | } 13 | 14 | if (req.restarts == 0) { 15 | if (req.http.X-Forwarded-For) { 16 | set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip; 17 | } else { 18 | set req.http.X-Forwarded-For = client.ip; 19 | } 20 | } 21 | 22 | # Normalize the header, remove the port (in case you're testing this on various TCP ports) 23 | set req.http.Host = regsub(req.http.Host, ":[0-9]+", ""); 24 | 25 | # Remove the proxy header (see https://httpoxy.org/#mitigate-varnish) 26 | unset req.http.proxy; 27 | 28 | # Allow purging 29 | if (req.request == "PURGE") { 30 | if (!client.ip ~ purge) { 31 | # Not from an allowed IP? Then die with an error. 32 | error 405 "This IP is not allowed to send PURGE requests."; 33 | } 34 | 35 | # If you got this stage (and didn't error out above), do a cache-lookup 36 | # That will force entry into vcl_hit() or vcl_miss() below and purge the actual cache 37 | return (lookup); 38 | } 39 | 40 | # Only deal with "normal" types 41 | if (req.request != "GET" && 42 | req.request != "HEAD" && 43 | req.request != "PUT" && 44 | req.request != "POST" && 45 | req.request != "TRACE" && 46 | req.request != "OPTIONS" && 47 | req.request != "PATCH" && 48 | req.request != "DELETE") { 49 | /* Non-RFC2616 or CONNECT which is weird. */ 50 | return (pipe); 51 | } 52 | 53 | # Only cache GET or HEAD requests. This makes sure the POST requests are always passed. 54 | if (req.request != "GET" && req.request != "HEAD") { 55 | return (pass); 56 | } 57 | 58 | # For Websocket support, always pipe the requests: https://www.varnish-cache.org/docs/3.0/tutorial/websockets.html 59 | if (req.http.Upgrade ~ "(?i)websocket") { 60 | return (pipe); 61 | } 62 | 63 | # Configure grace period, in case the backend goes down. This allows otherwise "outdated" 64 | # cache entries to still be served to the user, because the backend is unavailable to refresh them. 65 | # This may not be desireable for you, but showing a Varnish Guru Meditation error probably isn't either. 66 | if (req.backend.healthy) { 67 | set req.grace = 30s; 68 | } else { 69 | unset req.http.Cookie; 70 | set req.grace = 6h; 71 | } 72 | 73 | # Some generic URL manipulation, useful for all templates that follow 74 | # First remove the Google Analytics added parameters, useless for our backend 75 | if (req.url ~ "(\?|&)(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=") { 76 | set req.url = regsuball(req.url, "&(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", ""); 77 | set req.url = regsuball(req.url, "\?(utm_source|utm_medium|utm_campaign|utm_content|gclid|cx|ie|cof|siteurl)=([A-z0-9_\-\.%25]+)", "?"); 78 | set req.url = regsub(req.url, "\?&", "?"); 79 | set req.url = regsub(req.url, "\?$", ""); 80 | } 81 | 82 | # Strip hash, server doesn't need it. 83 | if (req.url ~ "\#") { 84 | set req.url = regsub(req.url, "\#.*$", ""); 85 | } 86 | 87 | # Strip a trailing ? if it exists 88 | if (req.url ~ "\?$") { 89 | set req.url = regsub(req.url, "\?$", ""); 90 | } 91 | 92 | # This is an example to redirect with a 301/302 HTTP status code from within Varnish 93 | # if (req.http.Host ~ "secure.mysite.tld") { 94 | # # We may want to force our users from the secure site to the HTTPs version? 95 | # error 720 "https://secure.mysite.tld"; 96 | # # If you want to keep the URLs intact, this also works: 97 | # error 720 "https://" + req.http.Host + req.url; 98 | # } 99 | # 100 | # Or to force a 302 temporary redirect, use error 721 101 | # if (req.http.Host ~ "temp.mysite.tld") { 102 | # # Temporary redirect 103 | # error 721 "http://mysite.tld/temp"; 104 | # } 105 | # 106 | 107 | # Some generic cookie manipulation, useful for all templates that follow 108 | # Remove the "has_js" cookie 109 | set req.http.Cookie = regsuball(req.http.Cookie, "has_js=[^;]+(; )?", ""); 110 | 111 | # Remove any Google Analytics based cookies 112 | set req.http.Cookie = regsuball(req.http.Cookie, "__utm.=[^;]+(; )?", ""); 113 | set req.http.Cookie = regsuball(req.http.Cookie, "_ga=[^;]+(; )?", ""); 114 | set req.http.Cookie = regsuball(req.http.Cookie, "utmctr=[^;]+(; )?", ""); 115 | set req.http.Cookie = regsuball(req.http.Cookie, "utmcmd.=[^;]+(; )?", ""); 116 | set req.http.Cookie = regsuball(req.http.Cookie, "utmccn.=[^;]+(; )?", ""); 117 | 118 | # Remove DoubleClick offensive cookies 119 | set req.http.Cookie = regsuball(req.http.Cookie, "__gads=[^;]+(; )?", ""); 120 | 121 | # Remove the Quant Capital cookies (added by some plugin, all __qca) 122 | set req.http.Cookie = regsuball(req.http.Cookie, "__qc.=[^;]+(; )?", ""); 123 | 124 | # Remove the AddThis cookies 125 | set req.http.Cookie = regsuball(req.http.Cookie, "__atuv.=[^;]+(; )?", ""); 126 | 127 | # Remove a ";" prefix in the cookie if present 128 | set req.http.Cookie = regsuball(req.http.Cookie, "^;\s*", ""); 129 | 130 | # Are there cookies left with only spaces or that are empty? 131 | if (req.http.cookie ~ "^\s*$") { 132 | unset req.http.cookie; 133 | } 134 | 135 | # Normalize Accept-Encoding header 136 | # straight from the manual: https://www.varnish-cache.org/docs/3.0/tutorial/vary.html 137 | if (req.http.Accept-Encoding) { 138 | if (req.url ~ "\.(7z|bz2|docx|flac|flv|gz|jpeg|jpg|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|png|pptx|rar|svgz|tbz|tgz|txz|webm|webp|woff2|xlsx|xz|zip)$") { 139 | # No point in compressing these 140 | remove req.http.Accept-Encoding; 141 | } elsif (req.http.Accept-Encoding ~ "gzip") { 142 | set req.http.Accept-Encoding = "gzip"; 143 | } elsif (req.http.Accept-Encoding ~ "deflate") { 144 | set req.http.Accept-Encoding = "deflate"; 145 | } else { 146 | # unkown algorithm 147 | remove req.http.Accept-Encoding; 148 | } 149 | } 150 | 151 | # Large static files should be piped, so they are delivered directly to the end-user without 152 | # waiting for Varnish to fully read the file first. 153 | # TODO: once the Varnish Streaming branch merges with the master branch, use streaming here to avoid locking. 154 | if (req.url ~ "^[^?]*\.(7z|avi|bz2|flac|flv|gz|mka|mkv|mov|mp3|mp4|mpeg|mpg|ogg|ogm|opus|rar|tar|tgz|tbz|txz|wav|webm|xz|zip)(\?.*)?$") { 155 | unset req.http.Cookie; 156 | return (pipe); 157 | } 158 | 159 | # Remove all cookies for static files 160 | # A valid discussion could be held on this line: do you really need to cache static files that don't cause load? Only if you have memory left. 161 | # Sure, there's disk I/O, but chances are your OS will already have these files in their buffers (thus memory). 162 | # Before you blindly enable this, have a read here: https://ma.ttias.be/stop-caching-static-files/ 163 | if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|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)(\?.*)?$") { 164 | unset req.http.Cookie; 165 | return (lookup); 166 | } 167 | 168 | # Send Surrogate-Capability headers to announce ESI support to backend 169 | set req.http.Surrogate-Capability = "key=ESI/1.0"; 170 | 171 | if (req.http.Authorization) { 172 | # Not cacheable by default 173 | return (pass); 174 | } 175 | 176 | # Include custom vcl_recv logic 177 | include "custom.recv.vcl"; 178 | 179 | return (lookup); 180 | } 181 | 182 | sub vcl_pipe { 183 | # Note that only the first request to the backend will have 184 | # X-Forwarded-For set. If you use X-Forwarded-For and want to 185 | # have it set for all requests, make sure to have: 186 | # set bereq.http.connection = "close"; 187 | # here. It is not set by default as it might break some broken web 188 | # applications, like IIS with NTLM authentication. 189 | 190 | #set bereq.http.Connection = "Close"; 191 | 192 | # Needed for WS (Websocket) support: https://www.varnish-cache.org/docs/3.0/tutorial/websockets.html 193 | if (req.http.upgrade) { 194 | set bereq.http.upgrade = req.http.upgrade; 195 | } 196 | 197 | return (pipe); 198 | } 199 | 200 | sub vcl_pass { 201 | return (pass); 202 | } 203 | 204 | # The data on which the hashing will take place 205 | sub vcl_hash { 206 | hash_data(req.url); 207 | 208 | if (req.http.host) { 209 | hash_data(req.http.host); 210 | } else { 211 | hash_data(server.ip); 212 | } 213 | 214 | # hash cookies for requests that have them 215 | if (req.http.Cookie) { 216 | hash_data(req.http.Cookie); 217 | } 218 | 219 | return (hash); 220 | } 221 | 222 | sub vcl_hit { 223 | # Allow purges 224 | if (req.request == "PURGE") { 225 | purge; 226 | error 200 "purged"; 227 | } 228 | 229 | return (deliver); 230 | } 231 | 232 | sub vcl_miss { 233 | # Allow purges 234 | if (req.request == "PURGE") { 235 | purge; 236 | error 200 "purged"; 237 | } 238 | 239 | return (fetch); 240 | } 241 | 242 | # Handle the HTTP request coming from our backend 243 | sub vcl_fetch { 244 | # Include custom vcl_fetch logic 245 | include "custom.fetch.vcl"; 246 | 247 | # Parse ESI request and remove Surrogate-Control header 248 | if (beresp.http.Surrogate-Control ~ "ESI/1.0") { 249 | unset beresp.http.Surrogate-Control; 250 | set beresp.do_esi = true; 251 | } 252 | 253 | # https://www.varnish-cache.org/docs/3.0/tutorial/compression.html 254 | # gzip content that can be compressed 255 | # Do wildcard matches, since additional info (like charsets) can be added in the Content-Type header 256 | if (beresp.http.content-type ~ "text/plain" 257 | || beresp.http.content-type ~ "text/xml" 258 | || beresp.http.content-type ~ "text/css" 259 | || beresp.http.content-type ~ "text/html" 260 | || beresp.http.content-type ~ "application/(x-)?javascript" 261 | || beresp.http.content-type ~ "application/(x-)?font-ttf" 262 | || beresp.http.content-type ~ "application/(x-)?font-opentype" 263 | || beresp.http.content-type ~ "application/font-woff" 264 | || beresp.http.content-type ~ "application/vnd\.ms-fontobject" 265 | || beresp.http.content-type ~ "image/svg\+xml" 266 | ) { 267 | set beresp.do_gzip = true; 268 | } 269 | 270 | # If the request to the backend returns a code is 5xx, restart the loop 271 | # If the number of restarts reaches the value of the parameter max_restarts, 272 | # the request will be error'ed. max_restarts defaults to 4. This prevents 273 | # an eternal loop in the event that, e.g., the object does not exist at all. 274 | if (beresp.status >= 500 && beresp.status <= 599){ 275 | return(restart); 276 | } 277 | 278 | # Enable cache for all static files 279 | # The same argument as the static caches from above: monitor your cache size, if you get data nuked out of it, consider giving up the static file cache. 280 | # Before you blindly enable this, have a read here: https://ma.ttias.be/stop-caching-static-files/ 281 | if (req.url ~ "^[^?]*\.(7z|avi|bmp|bz2|css|csv|doc|docx|eot|flac|flv|gif|gz|ico|jpeg|jpg|js|less|mka|mkv|mov|mp3|mp4|mpeg|mpg|odt|otf|ogg|ogm|opus|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)(\?.*)?$") { 282 | unset beresp.http.set-cookie; 283 | } 284 | 285 | # Sometimes, a 301 or 302 redirect formed via Apache's mod_rewrite can mess with the HTTP port that is being passed along. 286 | # This often happens with simple rewrite rules in a scenario where Varnish runs on :80 and Apache on :8080 on the same box. 287 | # A redirect can then often redirect the end-user to a URL on :8080, where it should be :80. 288 | # This may need finetuning on your setup. 289 | # 290 | # To prevent accidental replace, we only filter the 301/302 redirects for now. 291 | if (beresp.status == 301 || beresp.status == 302) { 292 | set beresp.http.Location = regsub(beresp.http.Location, ":[0-9]+", ""); 293 | } 294 | 295 | # Set 2min cache if unset for static files 296 | if (beresp.ttl <= 0s || beresp.http.Set-Cookie || beresp.http.Vary == "*") { 297 | set beresp.ttl = 120s; 298 | return (hit_for_pass); 299 | } 300 | 301 | # If the backend response is an HTTP error (500, 502, 503), enter saint mode. 302 | # This will block this particular request from happening again to this backend, and will restart the request on the next available backend. 303 | # In case, for instance, the first backend is temporarily unavailable, this will restart the request to the second backend, without the client noticing it. 304 | # 305 | # Explained from the manual: by setting beresp.saintmode to a period of time, Varnish will not ask that backend again for this object for that amount of time. 306 | # This only works if you have multiple baceknds. 307 | if (beresp.status == 500 || beresp.status == 502 || beresp.status == 503) { 308 | # Don't use this server, for this particular URL, for the next 10 seconds. 309 | set beresp.saintmode = 10s; 310 | 311 | # Restart the HTTP request, this will automatically happen on the next available server (due to saintmode, see above). 312 | # But we don't want to restart POST requests, as that's dangerous (duplicate form submits etc.) 313 | if (req.request != "POST") { 314 | return(restart); 315 | } 316 | } 317 | 318 | # Keep all objects for 6h longer in the cache than their TTL specifies. 319 | # So even if HTTP objects are expired (they've passed their TTL), we can still use them in case all backends go down. 320 | # Remember: old content to show is better than no content at all (or an error page). 321 | set beresp.grace = 6h; 322 | 323 | return (deliver); 324 | } 325 | 326 | # The routine when we deliver the HTTP request to the user 327 | # Last chance to modify headers that are sent to the client 328 | sub vcl_deliver { 329 | if (obj.hits > 0) { 330 | set resp.http.X-Cache = "cached"; 331 | } else { 332 | set resp.http.x-Cache = "uncached"; 333 | } 334 | 335 | # Remove some headers: PHP version 336 | unset resp.http.X-Powered-By; 337 | 338 | # Remove some headers: Apache version & OS 339 | unset resp.http.Server; 340 | unset resp.http.X-Drupal-Cache; 341 | unset resp.http.X-Varnish; 342 | unset resp.http.Via; 343 | unset resp.http.Link; 344 | 345 | return (deliver); 346 | } 347 | 348 | sub vcl_error { 349 | if (obj.status >= 500 && obj.status <= 599) { 350 | # 4 retry for 5xx error 351 | return(restart); 352 | } elseif (obj.status >= 400 && obj.status <= 499 ) { 353 | # use 404 error page for 4xx error 354 | include "conf.d/error-404.vcl"; 355 | } elseif (obj.status == 720) { 356 | # We use this special error status 720 to force redirects with 301 (permanent) redirects 357 | # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html" 358 | set obj.status = 301; 359 | set obj.http.Location = obj.response; 360 | return (deliver); 361 | } elseif (obj.status == 721) { 362 | # And we use error status 721 to force redirects with a 302 (temporary) redirect 363 | # To use this, call the following from anywhere in vcl_recv: error 720 "http://host/new.html" 364 | set obj.status = 302; 365 | set obj.http.Location = obj.response; 366 | return (deliver); 367 | } else { 368 | # for other errors (not 5xx, not 4xx and not 2xx) 369 | include "conf.d/error.vcl"; 370 | } 371 | 372 | return (deliver); 373 | } 374 | 375 | sub vcl_init { 376 | return (ok); 377 | } 378 | 379 | sub vcl_fini { 380 | return (ok); 381 | } 382 | --------------------------------------------------------------------------------