Intro
I’ve been running GoatCounter on my site using the script. The problem is that adblockers like uBlock Origin block it (understandably).

To get around this, I set up proxying so that the GoatCounter requests go to an endpoint under my domain nelson.cloud/gc/count, and then from there CloudFront handles it and sends it to GoatCounter. Most ad blockers work based on domain and GoatCounter is on the blocklists. Since the browser is now sending requests to the same domain as my site, it shouldn’t trigger any ad blockers. This post explains how I did it in case it’s useful for anyone else.
It’s possible to self-host GoatCounter, but my approach was easier to do and less infrastructure to maintain. Perhaps in the future.
On Analytics and Privacy
I know there are concerns around analytics being privacy-invasive. GoatCounter is privacy-respecting. I care about privacy. I am of the belief that GoatCounter is harmless. I just like to keep track of the visitors on my site.
Read the GoatCounter developer’s take if you want another opinion: Analytics on personal websites.
Managing Infrastructure with Pulumi
Clicking through the AWS console to configure CloudFront distributions is a pain in the ass. I took the time to finally get the infrastructure for my blog managed as infrastructure-as-code with Pulumi and Python. So while you can click around the console and do all of this, I will be showing how to configure everything with Pulumi.
If you don’t want to use IaC, you can still find all of these options/settings in AWS itself.
Setting it up
To set up GoatCounter proxying via CloudFront, we’ll need to
- Create a new CloudFront function resource
- Add a second origin to the distribution
- Add an ordered cache behavior to the distribution (which references the CloudFront function using its ARN)
- Update the GoatCounter script to point to this new endpoint
CloudFront Function
CloudFront functions are JavaScript scripts that run before a request reaches a CloudFront distribution’s origin. In this case, the function strips the /gc from nelson.cloud/gc/count.
We need to strip /gc for two reasons:
- I chose to proxy requests that hit the
/gc/countendpoint on my site to make sure there’s no collision with post titles/slugs. I’ll never use the/gc/*path for posts. - GoatCounter accepts requests under
/count, not/gc/count
Here is the code for the function:
| |
And here is the CloudFront function resource defined in Pulumi (using Python) that includes the JavaScript from above. This is a new resource defined in the same Python file where my existing distribution already exists:
| |
CloudFront Distribution Origin and Cache Behavior
Here is my existing CloudFront distribution being updated with a new origin and cache behavior in Pulumi code.
At the time of writing CloudFront only allows allowed_methods to be a list of HTTP methods in specific combinations. The value must be one of these:
[HEAD, GET][HEAD, GET, OPTIONS][HEAD, DELETE, POST, GET, OPTIONS, PUT, PATCH]
Since the GoatCounter JavaScript sends a POST request, and the third option is the only one that includes POST, we’re forced to use all HTTP verbs. It should be harmless though.
| |
Now that my Pulumi code has both the CloudFront function defined and the CloudFront distribution has been updated, I ran pulumi up to apply changes.
Update the GoatCounter Script
Finally, I updated goatcounter.js to use the new endpoint. So instead of goatcounter.com I changed it to my own domain nelson.cloud/gc/count at the very top of the snippet:
| |
After this, I built my site with Hugo and deployed it on S3/CloudFront by updating the freshly built HTML/CSS/JS in my S3 Bucket and then invalidating the existing CloudFront cache.
Verifying that it Works
Now, GoatCounter should no longer be blocked by uBlock Origin. I tested by loading my site on an incognito browser window and checked that uBlock Origin was no longer blocking anything on my domain.

Everything looks good!
Support GoatCounter
If you’re using GoatCounter you should consider sponsoring the developer. It’s a great project.
References
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cloudfront-functions.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistS3AndCustomOrigins.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/DownloadDistValuesCacheBehavior.html
- https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html
- https://www.goatcounter.com/help/js
- https://www.goatcounter.com/help/backend
- https://www.goatcounter.com/help/countjs-host