1
+ # VCL version marker. Has no correlation with the Varnish version. VCL version 4.1 requires Varnish 6 or higher
2
+ vcl 4 .1 ;
3
+
4
+ # Import Varnish modules
5
+ import uri;
6
+ import urlplus;
7
+ import cookieplus;
8
+ import std;
9
+ import tls;
10
+ import ykey;
11
+ import activedns;
12
+ import udo;
13
+
14
+ # Access Control List (ACL) for cache invalidation access
15
+ acl purge {
16
+ " localhost" ;
17
+ " 127.0.0.1" ;
18
+ " ::1" ;
19
+ " 172.18.0.0/24" ; # Change this and add the right IP addresses & CIDRs
20
+ }
21
+
22
+ # Probe template, used by UDO
23
+ probe health {
24
+ .url = " /health_check.php" ;
25
+ .timeout = 2s ;
26
+ .interval = 5s ;
27
+ .window = 10 ;
28
+ .threshold = 5 ;
29
+ }
30
+
31
+ # Backend template, used by UDO
32
+ backend default {
33
+ .host = " 0.0.0.0" ;
34
+ .ssl = 1 ;
35
+ .ssl_verify_host = 0 ;
36
+ .ssl_verify_peer = 0 ;
37
+ .ssl_sni = 1 ;
38
+ .host_header = " example.com" ; # Change this and add the right host header
39
+ .first_byte_timeout = 600s ;
40
+ }
41
+
42
+ # Initialize DNS-based dynamic backends
43
+ sub vcl_init {
44
+ # Initialize DNS group with the right hostname and port number
45
+ # Supports SRV, A and AAAA records
46
+ new group = activedns.dns_group(" magento.example.com:443" ); # Change this and add the right backend hostname and port number
47
+ # Only allow IPv4 addresses
48
+ group.set_ipv_rule(ipv4);
49
+ # Ignore DNS TTL from the DNS server and force it to 5 seconds
50
+ group.set_ttl_rule(force);
51
+ group.set_ttl(5s );
52
+ # Assign backend and health probe templates
53
+ # The DNS group will override the host and port number
54
+ group.set_backend_template(default);
55
+ group.set_probe_template(health);
56
+
57
+ # Initiliaze a Unified Director Object (UDO)
58
+ new magento = udo.director();
59
+ # If the DNS record returns multiple values, perform random loadbalancing based on those values
60
+ magento.set_type(random);
61
+ # Subscribe to the DNS group to capture potential DNS changes
62
+ magento.subscribe(group.get_tag());
63
+ }
64
+
65
+ sub vcl_backend_fetch {
66
+ # Assign a backend dynamically using the Unified Director Object
67
+ set bereq.backend = magento.backend ();
68
+ }
69
+
70
+ sub vcl_backend_error {
71
+ # Capture potential backend failures and retry on another server
72
+ return (retry );
73
+ }
74
+
75
+ sub vcl_recv {
76
+ # Remove all marketing get parameters to minimize the cache objects
77
+ # You can add additional parameters that occur in your setup
78
+ urlplus.query_delete(" _branch_match_id" );
79
+ urlplus.query_delete(" srsltid" );
80
+ urlplus.query_delete(" _bta_c" );
81
+ urlplus.query_delete(" _bta_tid" );
82
+ urlplus.query_delete(" _ga" );
83
+ urlplus.query_delete(" _gl" );
84
+ urlplus.query_delete(" _ke" );
85
+ urlplus.query_delete(" _kx" );
86
+ urlplus.query_delete(" campid" );
87
+ urlplus.query_delete(" cof" );
88
+ urlplus.query_delete(" customid" );
89
+ urlplus.query_delete(" cx" );
90
+ urlplus.query_delete(" dclid" );
91
+ urlplus.query_delete(" dm_i" );
92
+ urlplus.query_delete(" ef_id" );
93
+ urlplus.query_delete(" epik" );
94
+ urlplus.query_delete(" fbclid" );
95
+ urlplus.query_delete(" gad_source" );
96
+ urlplus.query_delete(" gbraid" );
97
+ urlplus.query_delete(" gclid" );
98
+ urlplus.query_delete(" gclsrc" );
99
+ urlplus.query_delete(" gdffi" );
100
+ urlplus.query_delete(" gdfms" );
101
+ urlplus.query_delete(" gdftrk" );
102
+ urlplus.query_delete(" hsa_acc" );
103
+ urlplus.query_delete(" hsa_ad" );
104
+ urlplus.query_delete(" hsa_cam" );
105
+ urlplus.query_delete(" hsa_grp" );
106
+ urlplus.query_delete(" hsa_kw" );
107
+ urlplus.query_delete(" hsa_mt" );
108
+ urlplus.query_delete(" hsa_net" );
109
+ urlplus.query_delete(" hsa_src" );
110
+ urlplus.query_delete(" hsa_tgt" );
111
+ urlplus.query_delete(" hsa_ver" );
112
+ urlplus.query_delete(" ie" );
113
+ urlplus.query_delete(" igshid" );
114
+ urlplus.query_delete(" irclickid" );
115
+ urlplus.query_delete(" matomo_campaign" );
116
+ urlplus.query_delete(" matomo_cid" );
117
+ urlplus.query_delete(" matomo_content" );
118
+ urlplus.query_delete(" matomo_group" );
119
+ urlplus.query_delete(" matomo_keyword" );
120
+ urlplus.query_delete(" matomo_medium" );
121
+ urlplus.query_delete(" matomo_placement" );
122
+ urlplus.query_delete(" matomo_source" );
123
+ urlplus.query_delete(" mc_cid" );
124
+ urlplus.query_delete(" mc_eid" );
125
+ urlplus.query_delete(" mkcid" );
126
+ urlplus.query_delete(" mkevt" );
127
+ urlplus.query_delete(" mkrid" );
128
+ urlplus.query_delete(" mkwid" );
129
+ urlplus.query_delete(" msclkid" );
130
+ urlplus.query_delete(" mtm_campaign" );
131
+ urlplus.query_delete(" mtm_cid" );
132
+ urlplus.query_delete(" mtm_content" );
133
+ urlplus.query_delete(" mtm_group" );
134
+ urlplus.query_delete(" mtm_keyword" );
135
+ urlplus.query_delete(" mtm_medium" );
136
+ urlplus.query_delete(" mtm_placement" );
137
+ urlplus.query_delete(" mtm_source" );
138
+ urlplus.query_delete(" nb_klid" );
139
+ urlplus.query_delete(" ndclid" );
140
+ urlplus.query_delete(" origin" );
141
+ urlplus.query_delete(" pcrid" );
142
+ urlplus.query_delete(" piwik_campaign" );
143
+ urlplus.query_delete(" piwik_keyword" );
144
+ urlplus.query_delete(" piwik_kwd" );
145
+ urlplus.query_delete(" pk_campaign" );
146
+ urlplus.query_delete(" pk_keyword" );
147
+ urlplus.query_delete(" pk_kwd" );
148
+ urlplus.query_delete(" redirect_log_mongo_id" );
149
+ urlplus.query_delete(" redirect_mongo_id" );
150
+ urlplus.query_delete(" rtid" );
151
+ urlplus.query_delete(" sb_referer_host" );
152
+ urlplus.query_delete(" ScCid" );
153
+ urlplus.query_delete(" si" );
154
+ urlplus.query_delete(" siteurl" );
155
+ urlplus.query_delete(" s_kwcid" );
156
+ urlplus.query_delete(" sms_click" );
157
+ urlplus.query_delete(" sms_source" );
158
+ urlplus.query_delete(" sms_uph" );
159
+ urlplus.query_delete(" toolid" );
160
+ urlplus.query_delete(" trk_contact" );
161
+ urlplus.query_delete(" trk_module" );
162
+ urlplus.query_delete(" trk_msg" );
163
+ urlplus.query_delete(" trk_sid" );
164
+ urlplus.query_delete(" ttclid" );
165
+ urlplus.query_delete(" twclid" );
166
+ urlplus.query_delete(" utm_campaign" );
167
+ urlplus.query_delete(" utm_content" );
168
+ urlplus.query_delete(" utm_creative_format" );
169
+ urlplus.query_delete(" utm_id" );
170
+ urlplus.query_delete(" utm_marketing_tactic" );
171
+ urlplus.query_delete(" utm_medium" );
172
+ urlplus.query_delete(" utm_source" );
173
+ urlplus.query_delete(" utm_source_platform" );
174
+ urlplus.query_delete(" utm_term" );
175
+ urlplus.query_delete(" wbraid" );
176
+ urlplus.query_delete(" yclid" );
177
+ urlplus.query_delete(" zanpid" );
178
+
179
+ # Writes changes back to the URL and sorts query string parameters alphabetically
180
+ urlplus.write();
181
+ # Remove port number from host header
182
+ uri.set_port();
183
+ uri.write();
184
+
185
+ # Remove the proxy header to mitigate the httpoxy vulnerability
186
+ # See https://httpoxy.org/
187
+ unset req.http.proxy ;
188
+
189
+ # Add X-Forwarded-Proto and Ssl-Offloaded header value based on the protocol
190
+ if (req.http.Ssl-Offloaded == " 1" ) {
191
+ set req.http.X-Forwarded-Proto = " https" ;
192
+ } elseif (!req.http.Ssl-Offloaded && req.http.X-Forwarded-Proto == " https" ) {
193
+ set req.http.Ssl-Offloaded = " 1" ;
194
+ } elseif(!req.http.Ssl-Offloaded && !req.http.X-Forwarded-Proto && tls.is_tls()) {
195
+ set req.http.X-Forwarded-Proto = " https" ;
196
+ set req.http.Ssl-Offloaded = " 1" ;
197
+ } else {
198
+ set req.http.X-Forwarded-Proto = " http" ;
199
+ set req.http.Ssl-Offloaded = " 0" ;
200
+ }
201
+
202
+ # Reduce grace to the configured setting if the backend is healthy
203
+ # In case of an unhealthy backend, the original grace is used
204
+ if (std.healthy(req.backend_hint )) {
205
+ set req.grace = 300s ;
206
+ }
207
+
208
+ # Intercept purge requests from Magento to perform cache invalidations
209
+ if (req.method == " PURGE" ) {
210
+ # Only allow clients that match the ACL
211
+ if (client.ip !~ purge ) {
212
+ return (synth (405, "Method not allowed"));
213
+ }
214
+ # Perform a regular URL-based purge when X-Magento-Tags is not set
215
+ if (!req.http.X-Magento-Tags-Pattern ) {
216
+ return (purge );
217
+ }
218
+ # Remove regex content from X-Magento-Tags-Pattern and keep the actual comma-separated tags
219
+ set req.http.X-Key-Purge = regsuball (req.http.X-Magento-Tags-Pattern , " [\(\)\^\$]" , " " );
220
+ set req.http.X-Key-Purge = regsuball (req.http.X-Key-Purge , " [,\|]" , " " );
221
+ set req.http.X-Key-Purge = regsuball (req.http.X-Key-Purge , " \.\*" , " all" );
222
+ # Soft purge objects that match the tags
223
+ set req.http.n-purged = ykey.purge _header(req.http.X-Key-Purge , " " , true );
224
+ return (synth (200, "Purged " + req.http.n-purge d + " objects"));
225
+ }
226
+
227
+ # If the HTTP request method doesn't match one of these, it's probably not a valid HTTP request
228
+ # Send the content to the backend and abandon any notion of HTTP and HTTP caching
229
+ if (req.method != " GET" &&
230
+ req.method != " HEAD" &&
231
+ req.method != " PUT" &&
232
+ req.method != " POST" &&
233
+ req.method != " PATCH" &&
234
+ req.method != " TRACE" &&
235
+ req.method != " OPTIONS" &&
236
+ req.method != " DELETE" ) {
237
+ return (pipe );
238
+ }
239
+
240
+ # We only cache GET and HEAD requests
241
+ if (req.method != " GET" && req.method != " HEAD" ) {
242
+ return (pass );
243
+ }
244
+
245
+ # Bypass health check requests
246
+ if (req.url ~ " ^/(pub/)?(health_check.php)$" ) {
247
+ return (pass );
248
+ }
249
+
250
+ # Collapse multiple cookie headers into one
251
+ std.collect (req.http.Cookie );
252
+
253
+ # Static files caching
254
+ if (req.url ~ " ^/(pub/)?(media|static)/" ) {
255
+ # If you decide not to store static content in the cache, just uncomment the next line
256
+ #return (pass);
257
+
258
+ # If you decide to cache static files, remove cookies
259
+ unset req.http.Cookie ;
260
+ # Remove X-Forwarded-Proto to reduce cache variations
261
+ unset req.http.X-Forwarded-Proto ;
262
+ }
263
+
264
+ # Don't cache the authenticated GraphQL requests
265
+ if (req.url ~ " /graphql" && req.http.Authorization ~ " ^Bearer" ) {
266
+ return (pass );
267
+ }
268
+
269
+ return (hash );
270
+ }
271
+
272
+ sub vcl_hash {
273
+ # Create a cache variation for the GraphQL requests that based on the X-Magento-Vary cookie
274
+ if (req.url !~ " /graphql" ) {
275
+ hash_data (cookieplus.get(" X-Magento-Vary" ));
276
+ }
277
+
278
+ # Store HTTP and HTTPS content separately
279
+ hash_data (req.http.X-Forwarded-Proto );
280
+
281
+ if (req.url ~ " /graphql" ) {
282
+ if (req.http.X-Magento-Cache-Id ) {
283
+ hash_data (req.http.X-Magento-Cache-Id );
284
+ } else {
285
+ # if no X-Magento-Cache-Id (which already contains Store and Currency) is not set, use the HTTP headers
286
+ hash_data (req.http.Store );
287
+ hash_data (req.http.Content-Currency );
288
+ }
289
+ }
290
+ }
291
+
292
+ sub vcl_backend_response {
293
+ # Register X-Magento-Tags header with Ykey
294
+ ykey.add_header(beresp.http.X-Magento-Tags , sep=" ," );
295
+ # Associate every object with the "all" key, for when a full cache purge takes place
296
+ ykey.add_key(" all" );
297
+
298
+ # Serve stale content for three days after object expiration
299
+ # Perform asynchronous revalidation while stale content is served
300
+ set beresp.grace = 3d ;
301
+
302
+ # All text-based content can be parsed as ESI
303
+ if (beresp.http.content-type ~ " text" ) {
304
+ set beresp.do_esi = true ;
305
+ }
306
+
307
+ # Allow GZIP compression on all JavaScript files and all text-based content
308
+ if (urlplus.get_extension() == " js" || beresp.http.content-type ~ " text" ) {
309
+ set beresp.do_gzip = true ;
310
+ }
311
+
312
+ # Add debug headers
313
+ if (beresp.http.X-Magento-Debug ) {
314
+ set beresp.http.X-Magento-Cache-Control = beresp.http.Cache-Control ;
315
+ }
316
+
317
+ # Only cache HTTP 200 and HTTP 404 responses
318
+ if (beresp.status != 200 && beresp.status != 404 ) {
319
+ set beresp.ttl = 120s ;
320
+ set beresp.uncacheable = true ;
321
+ return (deliver );
322
+ }
323
+
324
+ # Don't cache if the request cache ID doesn't match the response cache ID for graphql requests
325
+ if (bereq.url ~ " /graphql" && bereq.http.X-Magento-Cache-Id && bereq.http.X-Magento-Cache-Id != beresp.http.X-Magento-Cache-Id ) {
326
+ set beresp.ttl = 120s ;
327
+ set beresp.uncacheable = true ;
328
+ return (deliver );
329
+ }
330
+
331
+ # Remove the Set-Cookie header for cacheable content
332
+ # Only for HTTP GET & HTTP HEAD requests
333
+ if (beresp.ttl > 0s && (bereq.method == " GET" || bereq.method == " HEAD" )) {
334
+ unset beresp.http.Set-Cookie ;
335
+ }
336
+ }
337
+
338
+ sub vcl_deliver {
339
+ if (obj.uncacheable ) {
340
+ set resp.http.X-Magento-Cache-Debug = " UNCACHEABLE" ;
341
+ } else if (obj.hits ) {
342
+ set resp.http.X-Magento-Cache-Debug = " HIT" ;
343
+ } else {
344
+ set resp.http.X-Magento-Cache-Debug = " MISS" ;
345
+ }
346
+
347
+ # Not letting browser cache non-static files.
348
+ if (resp.http.Cache-Control !~ " private" && req.url !~ " ^/(pub/)?(media|static)/" ) {
349
+ set resp.http.Pragma = " no-cache" ;
350
+ set resp.http.Expires = " -1" ;
351
+ set resp.http.Cache-Control = " no-store, no-cache, must-revalidate, max-age=0" ;
352
+ }
353
+
354
+ if (!resp.http.X-Magento-Debug ) {
355
+ unset resp.http.X-Magento-Cache-Debug ;
356
+ unset resp.http.Age ;
357
+ }
358
+ unset resp.http.X-Magento-Debug ;
359
+ unset resp.http.X-Magento-Tags ;
360
+ unset resp.http.X-Powered-By ;
361
+ unset resp.http.Server ;
362
+ unset resp.http.X-Varnish ;
363
+ unset resp.http.Via ;
364
+ unset resp.http.Link ;
365
+ }
0 commit comments