Why would you want to do this? Let's assume you have configured your blog to aggressively cache your content. Thinking about it, a personal log is supposed to be a read-heavy workload. Unless you are a prolific author, your contributions will happen every few days (or weeks).
So, it makes sense to cache as much as possible and allow your visitors to reach your content very quickly. However, when you post new content or update existing article, you want to make sure that it is immediately visible to your users. You will need to bust your cache so that the latest content is pulled and stored again.
If you use Cloudflare, you can purge the content in many ways: from the dashboard or using the APIs. However, wouldn't it be convenient if all of this were to be done automatically at the right times? Let's figure out how to achieve this!
The Ghost content platform has a mechanism to issue webhooks calls on specific occurrences: looking at the documentation page, there is one that is pretty interesting for what we want to achieve:
site.changed : Triggered whenever any content changes in your site data or settings
I haven't found much more documentation or information other than what is noted on the page:
Configuring webhooks can be done through the Ghost Admin user interface. The only required fields to setup a new webhook are a trigger event and target URL to notify. This target URL is your application URL, the endpoint where the POST request will be sent. Of course, this URL must be reachable from the Internet.
If the server responds with 2xx HTTP response, the delivery is considered successful. Anything else is considered a failure of some kind, and anything returned in the body of the response will be discarded.
It doesn't tell exactly what data is being posted to the target URL, or whether there are retry on failure capabilities, or similar features. We will just assume that this is a "best-effort", fire and forget call.
Based on this, here is what we want to implement:
We will create a Ghost Webhook that triggers on site.changed.
We will configure a Cloudflare worker as the target URL: it will accept the incoming call from Ghost.
When configuring the Webhook URL, we will add HTTP Basic Authentication to it. In other words, we will include a username and password in the URL, and this will be converted into an Authorization: Basic header which will be shipped to our Worker in the HTTP request.
Once the request reaches the Cloudflare worker, we will decode the Authorization header and compare the username and password with values that we will have securely configure in the Worker environment. The username and password will be pre-configured on both sides.
If the authentication is successful, the worker will issue a call to the Cloudflare API, passing a Cloudflare API Authentication Token. The call will request a full cache purge for the given Cloudflare zone (our blog's).
The API Token above will be scoped to only allow cache purging on the intended zone.
Finally, the worker will read the response from the Cloudflare API call and return an HTTP 200 code to Ghost if successful, or HTTP 500 if not. This is merely for debugging purposes, as Ghost does not include any mechanism to act on the response from the target URL, other than logging it.
Sounds like a plan? Let's do it!
Before starting to play with the worker, let's create a suitable Cloudflare API Token that we will use to purge the cache. It is important to create a Token that is scoped to perform only the intended operation here.
Once you are in the Cloudflare Dashboard, go to your "Profile" and then pick the "API Tokens" tab. Hit the "Create Token" button. We will use a custom token here:
A couple of notes:
IP Address Filtering: Leave this blank
TTL: This is up to you. Ensure to include the correct start date from today. For the End date, I wouldn't go lower than 6 months. Whatever you choose, remember that you will need to update it when it expires!
Once you have created the token, note the value down somewhere as you will need it later. While you are at it, take also note of your Cloudflare Zone ID and Account ID which will also be needed: they can be found in the "Overview" tab of your zone.
It's worker time!
Luckily for you, I have already created the code of the worker: you can find it on Github, or pull it directly by running:
To configure and deploy it, you also need wrangler. If you are planning to tinker around with Cloudflare Workers you need this nifty tool on your machine. Recently, it has been significantly improved and it definitely helps while you are developing and testing your code. Here is how to install it:
npm i @cloudflare/wrangler -g
Great! The next step is to configure your wrangler.toml file. This is where you set up some of the values that you have noted earlier. Here is the example template that I have included in the repository. Please modify the placeholer values I have included:
Account ID and Zone ID are self-explanatory. You can also change the worker name to something else if you want - this is optional. Once published, the worker will be accepting requests at https://<name>.<your workers subdomain>.workers.dev, so remember this if you pick a different value. Finally, WEBHOOK_USER is the username that we will expect for our HTTP Basic Authentication. Choose one of your linking and note it down as well.
There are a couple of more configuration values that we need to set. However, these are considered sensitive data, so we don't want to include them as simple clear text values as shown above. The values listed in the vars section will be visible in the Cloudflare dashboard for your worker - something you wouldn't want for a password or an authentication token. Instead, we will set up two Worker Secrets.
Support for Secrets was introduced here: they are stored in encrypted form, and cannot be seen after they have been set. We will use this mechanism to store both our webhook password and the API token. Simply run each of the below commands, and add the value when asked:
wrangler secret put CF_AUTH_TOKEN
wrangler secret put WEBHOOK_PASSWORD
We are now done with the worker configuration. The next step is publishing it, which you can accomplish with a simple:
The command will take care of publishing and deploying the Worker code and configuration on the global Cloudflare network, ready to be executed in a few seconds. It will also tell you the URL where your worker can be invoked: you will configure it in Ghost shortly.
If you go in the Workers dashboard, you can see the configuration values we discussed above:
These variables will be available to the Worker code when it runs. You can notice that the two secrets are not visible even in the dashboard, I did not need to hide them in my screenshot.
The last piece of our puzzle is the Ghost webhook. Go to your "Ghost Admin", and open the "Integrations" link in the "Settings" section. There is a link to "Add a Custom Integration". Here's what mine looks like:
You can ignore the API Keys and API URL above as these will not be used. We only need to configure one Webhook on the Site Changed (rebuild) event. The URL to configure will be in this format:
That's it! You are ready for automated cache purge now. To check that all is working as planned, you can look at the logs on the machine where ghost is running. You can trigger the worker by opening one of your existing blog articles and hitting the button to update it.
If all goes well, you should see a log line telling you that the webhook was triggered. If something goes wrong, you will also see another log line warning you about this. You can also use wrangler to check the live logs of your worker when you do your test, by typing:
If you look in your Ghost log, you'll realize one of the reasons why the HTTP Basic Authentication method isn't so great. For starters, your username and password are being logged! This isn't so much of a concern for me, given that Ghost is running on a host where only I am supposed to have access to. "Not great, not terrible" - and you know how that one ended...
You have automated your cache purging with Ghost and Cloudflare. Congratulations!
Right now, the worker is very draconian, and just drops the entire cache for the zone on every event that has been issued. I can think of some improvements here, for example we could inspect the payload that Ghost sends to determine a more approprate cache purging strategy.
Let me know your thoughts, or if something didn't work for you, in the comments!