PHPackages                             innoweb/silverstripe-fastly - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Caching](/categories/caching)
4. /
5. innoweb/silverstripe-fastly

ActiveSilverstripe-vendormodule[Caching](/categories/caching)

innoweb/silverstripe-fastly
===========================

Integration of the Fastly CDN for Silverstripe CMS

3.3.1(1w ago)1245BSD-3-ClausePHP

Since Jan 7Pushed 1w ago3 watchersCompare

[ Source](https://github.com/xini/silverstripe-fastly)[ Packagist](https://packagist.org/packages/innoweb/silverstripe-fastly)[ Docs](https://github.com/xini/silverstripe-fastly)[ RSS](/packages/innoweb-silverstripe-fastly/feed)WikiDiscussions master Synced today

READMEChangelogDependencies (6)Versions (14)Used By (0)

Silverstripe Fastly integration
===============================

[](#silverstripe-fastly-integration)

[![Version](https://camo.githubusercontent.com/8c0c57a9dc7a1e1cb447455842486393b8f0efb7fac1ea0ade0bfbac7795c1e4/687474703a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f696e6e6f7765622f73696c7665727374726970652d666173746c792e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/innoweb/silverstripe-fastly)[![License](https://camo.githubusercontent.com/512c14505410dab53da59bb27f85f5398c3fc67425142758ea5a6fcc0fe33168/687474703a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f696e6e6f7765622f73696c7665727374726970652d666173746c792e7376673f7374796c653d666c61742d737175617265)](license.md)

Overview
--------

[](#overview)

Adds [Fastly CDN](https://www.fastly.com/) integration to a Silverstripe Site.

Requirements
------------

[](#requirements)

- Silverstripe CMS 5.x
- Guzzle 7

Note: this version is compatible with Silverstripe 5. For Silverstripe 4, please see the [2 release line](https://github.com/xini/silverstripe-fastly/tree/2).

Installation
------------

[](#installation)

Install the module using composer:

```
composer require innoweb/silverstripe-fastly

```

Then run dev/build.

Configuration
-------------

[](#configuration)

### Silverstripe

[](#silverstripe)

You need to add the following configuration to your environment:

```
Fastly:
  service_id: [your Fastly service ID]
  api_token: [your personal API token]

```

Additionally, the following configuration options are available on the `Fastly` class:

- `soft_purge`: `[true|false]` flag to enable Fastly soft purges, see . Defaults to `true`
- `verify_ssl`: `[true|false]` flag whether Guzzle should verify the SSL certificate. Useful for dev environments. Defaults to `true`
- `debug_log`: if you want to debug the Guzzle calls made to the Fastly API you can configure the path to a log file where all Guzzle requests are logged to. Defaults to `''`
- `flush_on_dev_build`: `[true|false]` flag whether all content (pages, images, css, js etc.) should be purged from Fastly. Does not use the `soft_purge` feature. Defaults to `true`
- `sitetree_flush_strategy`: `'[single|parents|all|smart|everything]'` lets you select the purge strategy used when a page is changed and published. Defaults to `'smart'`
    - `single`: only the current page URL is purged
    - `parents`: the current page as well as all its parent pages are purged
    - `all`: all pages are purged
    - `smart`: depending on what fields of the page have changed, `single`, `parents` or `all` is applied
    - `everything`: all content is purged from the fastly cache. That includes pages, images, css, js etc.
- `always_include_in_sitetree_flush`: array of page type classes that should always be purged when a page is changed, e.g. a sitemap. Defaults to `[]`

#### Image surrogate keys

[](#image-surrogate-keys)

Because in Silverstripe 4 and 5 we still have no way of getting all image variants (see [silverstripe/silverstripe-assets#109](https://github.com/silverstripe/silverstripe-assets/issues/109)), we need to mark all images and image variants with a Surrogate Key in order to purge them.

Because the filename of all image variants in SS 4.4+ have the variant hash to the original filename, e.g. `my-file__FitWzYwLDYwXQ.jpg`, we can extract the file name without hash and add it as a surrogate key header. The module then purges the original URL of the file as well as the Surrogate Key to clear the original image as well as all variants. (This might purge other images if there are multiple images with the same name in different folders, but I think we can live with that.)

For Apache, add the following snippet to your `.htaccess` file to add the surrogate key:

```
### FASTLY START ###

			SetEnvIfNoCase Request_URI "([^\/]*)__[^\.]*(\.[A-Za-z]*)|([^\/]*)(\.[A-Za-z]*)$" FASTLY_FILE_NAME=$1$3$2$4
			Header set Surrogate-Key %{FASTLY_FILE_NAME}e
			Header set Vary Accept-Encoding

		Header set Vary "Accept-Encoding, X-Forwarded-Proto" "expr=%{CONTENT_TYPE} =~ m#text\/html#"

### FASTLY END ###

```

### Fastly

[](#fastly)

#### Conditions

[](#conditions)

*type*: cache

*title*: admin, logged in or form

```
(req.url ~ "^/(Security|admin|dev)") || (req.http.cookie ~ "sslogin=") || (beresp.http.cache-control ~ "no-cache") || (req.url ~ "stage=Stage") || (req.url ~ "/ping$")

```

*type*: cache

*title*: not admin, logged in or form

```
!(req.url ~ "^/(Security|admin|dev)") && !(req.http.Cookie ~ "sslogin=") && !(beresp.http.Cache-Control ~ "no-cache") && !(req.url ~ "stage=Stage") && !(req.url ~ "/ping$")

```

*type*: request

*title*: admin or logged in

```
req.url ~ "^/(Security|admin|dev)" || req.http.Cookie ~ "sslogin=" || req.url ~ "stage=Stage"

```

#### Cache settings

[](#cache-settings)

```
condition: admin, logged in or form
name: pass to origin
action: pass
X-Forwarded-For: Append

```

#### Request settings

[](#request-settings)

```
condition: admin or logged in
name: pass if logged in
action: pass
X-Forwarded-For: Append

```

#### Headers

[](#headers)

```
condition: not admin, logged in or form
name: set stale while revalidate
type: Cache
action: set
destination: stale_while_revalidate
source: 86400s

```

```
condition: admin, logged in or form
name: force skip caching
type: Cache
action: set
destination: cacheable
source: false

```

#### VCL snippets

[](#vcl-snippets)

*type*: recv

*title*: block bots

*priority*: 5

```
# crawlers
if (req.http.User-Agent ~ "(?i)360Spider|80legs|Abonti|Aboundex|AcoonBot|Acunetix|adbeat_bot|adidxbot|ADmantX|AngloINFO|Antelope|BeetleBot|billigerbot|binlar|BlackWidow|BLP_bbot|BoardReader|casper|CazoodleBot|CCBot|checkprivacy|ChinaClaw|chromeframe|Clerkbot|Cliqzbot|clshttp|CommonCrawler|CPython|crawler4j|Crawlera|CRAZYWEBCRAWLER|Curious|Custo|diavol|DigExt|Digincore|DIIbot|discobot|DISCo|DoCoMo|DotBot|Download\ Demon|Download.Demon|Download.Devil|Download.Wonder|DTS.Agent|EasouSpider|eCatch|ecxi|EirGrabber|Elmer|EmailCollector|EmailSiphon|EmailWolf|Exabot|ExaleadCloudView|ExpertSearchSpider|ExpertSearch|Express|Extractor|extract|EyeNetIE|Ezooms|F2S|FastSeek|FHscan|finbot|FlappyBot|FlashGet|flicky|Flipboard|g00g1e|Genieo|GetRight|GetWeb|GigablastOpenSource|GozaikBot|GrabNet|Grafula|GrapeshotCrawler|GTB5|harvest|heritrix|HMView|HomePageBot|ia_archiver|icarus6|IDBot|IlseBot|Indigonet|Indy|integromedb|InterGET|InternetSeer|Ninja|IRLbot|jakarta|Java|JennyBot|JetCar|JobdiggerSpider|Jooblebot|kanagawa|KINGSpider|kmccrew|larbin|LeechFTP|libwww|Lingewoud|linkdexbot|LinksCrawler|linkwalker|LivelapBot|ltx71|LubbersBot|masscan|maverick|Maxthon$|Mediatoolkitbot|MegaIndex|MFC_Tear_Sample|miner|Missigua|msnbot|Navroad|NearSite|NetAnts|netEstate|NetSpider|NetZIP|Vampire|NextGenSearchBot|nutch|Octopus|Openfind|OutfoxBot|Offline|OrangeBot|Owlin|Pixray|probethenet|PageGrabber|panopta|panscient|Papa|pavuk|pcBrowser|PeoplePal|Photon|planetwork|PleaseCrawl|PNAMAIN.EXE|PodcastPartyBot|prijsbest|proximic|psbot|purebot|pycurl|QuerySeekerSpider|RealDownload|ReGet|Riddler|Ripper|rogerbot|RSSingBot|RyzeCrawler|SafeSearch|SBIder|Scrapy|Screaming|SeaMonkey|SemrushBot|SentiBot|SEOkicks|SeznamBot|ShowyouBot|SightupBot|SISTRIX|siteexplorer|SiteSnagger|skygrid|Slurp|SmartDownload|Snoopy|Sogou|Sosospider|spaumbot|Steeler|stripper|sucker|SuperBot|Superfeed|SuperHTTP|SurdotlyBot|Surfbot|tAkeOut|Teleport|TinEye|Toata|Toplistbot|trendictionbot|TurnitinBot|turnit|Vagabondo|vikspider|VoidEYE|VoilaBot|WBSearchBot|webalta|WebAuto|WebBandit|WebCollage|WebCopier|WebFetch|WebGo|WebLeacher|WebReaper|WebSauger|eXtractor|Quester|WebStripper|WebWhacker|WebZIP|WeSEE|Widow|WinInet|woobot|woopingbot|worldwebheritage|Wotbox|WPScan|WWWOFFLE|Mechanize|Xaldon|XoviBot|yacybot|zermelo|Zeus|ZmEu|ZumBot|ZyBorg") {
    error 403;
}
# optional: search engines (e.g Huawei, Magestic)
#if (req.http.User-Agent ~ "(?i)PetalBot|MJ12bot") {
#    error 403;
#}
# optional: AI bots
#if (req.http.User-Agent ~ "(?i)ClaudeBot|Bytespider|GPTBot|PerplexityBot|meta-externalagent|aiohttp") {
#    error 403;
#}

# block HEAD requests
if (req.method == "HEAD") {
    error 405;
}

# optional: send wordpress and other stuff to 404
#if (req.url.path ~ "(?i)/(wp-content|wp-includes|wp-admin|.git|.svn|administrator)/") {
#    error 404;
#}
# optional: send weird files to 404
#if (req.url.ext ~ "(?i)^(log|git|bak|sql|env|ini|conf|config|md|exe|dll|bat|cmd|asp)$") {
#    error 404;
#}
# optional: send all php files 404
#if (req.url.ext ~ "(?i)^(php)$") {
#    error 404;
#}

```

*type*: recv

*title*: clean up requests

*priority*: 50

```
# save requested range to cache streaming blocks
if (req.http.Range ~ "bytes=") {
  set req.http.x-range = req.http.Range;
}
# remove cookies for static content
if (req.http.Cookie && req.url ~ "^[^?]*\.(?:js|css|avif|webp|bmp|png|gif|jpg|jpeg|ico|pcx|tif|tiff|au|mid|midi|mpa|mp3|ogg|m4a|ra|wma|wav|cda|avi|mpg|mpeg|asf|wmv|m4v|mov|mkv|mp4|ogv|webm|swf|flv|ram|rm|doc|docx|txt|rtf|xls|xlsx|pages|ppt|pptx|pps|csv|cab|arj|tar|zip|zipx|sit|sitx|gz|tgz|bz2|ace|arc|pkg|dmg|hqx|jar|pdf|woff|woff2|eot|ttf|otf|svg)(\?.*)?$") {
	unset req.http.cookie;
}
# remove common cookies
if (req.http.Cookie) {
	# remove silverstripe cookies
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(cms-panel-collapsed-cms-menu)=[^;]*", "");
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(cms-menu-sticky)=[^;]*", "");

	# Remove any Google Analytics based cookies
	# (removes everything starting with an underscore, which also includes AddThis, DoubleClick and others)
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(_[_a-zA-Z0-9\-]+)=[^;]*", "");
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(utm[a-z]+)=[^;]*", "");

	# Remove the Avanser phone tracking cookies
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(AUA[0-9]+)=[^;]*", "");

	# Remove the StatCounter cookies
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(sc_is_visitor_unique)=[^;]*", "");

	# Remove Kickfire cookie
	set req.http.Cookie = regsuball(req.http.Cookie, "(^|;\s*)(kickfire_api_session_cookie)=[^;]*", "");

	# Remove a ";" prefix, if present.
	set req.http.Cookie = regsub(req.http.Cookie, "^;\s*", "");

	# remove empty cookie
	if (req.http.Cookie == "") {
		unset req.http.cookie;
	}
}

# remove tracking parameters
set req.url = querystring.filter(req.url, "dpuid");
set req.url = querystring.filter(req.url, "ttclid");
set req.url = querystring.filter(req.url, "fbclid");
set req.url = querystring.filter(req.url, "gclid");
set req.url = querystring.filter(req.url, "gbraid");
set req.url = querystring.filter(req.url, "wbraid");
set req.url = querystring.globfilter(req.url, "utm_*");
set req.url = querystring.globfilter(req.url, "gad_*");

# strip hash, server doesn't need it
if (req.url ~ "\#") {
	set req.url = regsub(req.url, "\#.*$", "");
}

# Strip a trailing questionsmark if it exists
if (req.url ~ "\?$") {
	set req.url = regsub(req.url, "\?$", "");
}

```

*type*: recv

*title*: add client IP header

*priority*: 100

```
if (fastly.ff.visits_this_service == 0 && req.restarts == 0) {
  set req.http.Fastly-Client-IP = client.ip;
}
set req.http.X-Forwarded-For = req.http.Fastly-Client-IP;

```

*type*: fetch

*title*: remove cookie header from static content

*priority*: 100

```
if (bereq.url ~ ".*\.(?:css|js)(?=\?|&|$)") {
	unset beresp.http.set-cookie;
}
if (bereq.url ~ ".*\.(?:avif|bmp|png|gif|jpg|jpeg|ico|pcx|tif|tiff|webp|au|mid|midi|mpa|mp3|ogg|m4a|ra|wma|wav|cda|avi|mpg|mpeg|asf|wmv|m4v|mov|mkv|mp4|ogv|webm|swf|flv|ram|rm)(?=\?|&|$)") {
	unset beresp.http.set-cookie;
}
if (bereq.url ~ ".*\.(?:doc|docx|txt|rtf|xls|xlsx|pages|ppt|pptx|pps|csv|cab|arj|tar|zip|zipx|sit|sitx|gz|tgz|bz2|ace|arc|pkg|dmg|hqx|jar|pdf)(?=\?|&|$)") {
	unset beresp.http.set-cookie;
}
if (bereq.url ~ ".*\.(?:woff|woff2|eot|ttf|otf|svg)(?=\?|&|$)") {
	unset beresp.http.set-cookie;
}

```

*type*: deliver

*title*: remove session cookie for non-form pages

*priority*: 100

```
if (
	(resp.http.Content-Type ~ "^text/html") &&
	(req.http.Cookie) &&
	!(req.url ~ "^/(Security|admin|dev)") &&
	!(req.url ~ "stage=") &&
	!(req.url ~ "/ping$") &&
	!(req.method == "POST") &&
	!(req.http.Cookie ~ "sslogin=") &&
	!(resp.http.Cache-Control ~ "no-store")
) {
	set resp.http.set-cookie = "PHPSESSID=deleted; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Path=/; HttpOnly";
}

```

### GEO fencing

[](#geo-fencing)

To serve different content based on the user's location add the following VCL snippet to your Fastly configuration:

```
# sub routine: recv(vcl_recv)
if (fastly.ff.visits_this_service == 0 && req.restarts == 0) {
  if (req.url ~ "(\?|\&)country=") {
  	# extract country parameter
  	set req.http.X-Country-Code = regsub(req.url, "^.*(\?|\&)country=([^&]*).*$" , "\2");
  	set req.http.client-geo-country = regsub(req.url, "^.*(\?|\&)country=([^&]*).*$" , "\2");
  	# strip country parameter from backend request
  	set req.url = regsuball(req.url,"\?country=[^&]+$","");
  } else if (client.geo.country_code) {
  	set req.http.X-Country-Code = client.geo.country_code;
  	set req.http.client-geo-country = client.geo.country_code;
  }

  set req.http.client-geo-latitude = client.geo.latitude;
  set req.http.client-geo-longitude = client.geo.longitude;
}

```

You can choose any or all of the lines above to add to your config, depending on what you need.

For continent and country codes, automatic VARY headers are added to all page requests. You can override this behaviour in your own page controllers using the `updateVaryHeader` method.

See  and  for further information.

License
-------

[](#license)

BSD 3-Clause License, see [License](license.md)

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance98

Actively maintained with recent releases

Popularity15

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~161 days

Recently: every ~121 days

Total

13

Last Release

9d ago

Major Versions

1.1.0 → 2.0.02021-07-09

2.1.0 → 3.0.02023-03-19

2.1.1 → 3.0.12023-11-21

1.0.x-dev → 2.x-dev2024-12-29

2.x-dev → 3.1.02025-01-30

### Community

Maintainers

![](https://www.gravatar.com/avatar/0d2e71d7787401a7bd4916062346163897f89f455d650ab32b5d60cd14825ad3?d=identicon)[xini](/maintainers/xini)

---

Top Contributors

[![xini](https://avatars.githubusercontent.com/u/1152403?v=4)](https://github.com/xini "xini (24 commits)")

---

Tags

silverstripecachingcdnfastly

### Embed Badge

![Health badge](/badges/innoweb-silverstripe-fastly/health.svg)

```
[![Health](https://phpackages.com/badges/innoweb-silverstripe-fastly/health.svg)](https://phpackages.com/packages/innoweb-silverstripe-fastly)
```

###  Alternatives

[silverstripe/staticpublishqueue

Static publishing queue to create static versions of pages for enhanced performance and security

45135.4k4](/packages/silverstripe-staticpublishqueue)[tractorcow/silverstripe-dynamiccache

FORK OF Silverstripe module for simple on the fly caching of dynamic content

3916.0k2](/packages/tractorcow-silverstripe-dynamiccache)[steadlane/silverstripe-cloudflare

This module aims to relieve the stress of using Cloudflare caching with any SilverStripe project. Adds extension hooks that clears Cloudflare's cache for a specific page when that page is published or unpublished.

243.7k](/packages/steadlane-silverstripe-cloudflare)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
