Install
openclaw skills install umami-setupAdd Umami self-hosted analytics to any website with adblocker-proof proxy. Covers: creating the website in Umami, setting up a same-domain proxy (Next.js, Astro/Vercel, Caddy, Nginx), and verifying tracking works.
openclaw skills install umami-setupSelf-hosted Umami analytics with a same-domain proxy to bypass adblockers. The script is served from the same domain as your site, so blockers see it as first-party.
analytics.casys.ai)# Login
TOKEN=$(curl -s -X POST "https://<UMAMI_HOST>/api/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<PASSWORD>"}' \
| python3 -c "import json,sys; print(json.load(sys.stdin)['token'])")
# Create website
curl -s -X POST "https://<UMAMI_HOST>/api/websites" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"<SITE_NAME>","domain":"<DOMAIN>"}' | python3 -m json.tool
Save the id from the response — that's your data-website-id.
The proxy serves the Umami script and send endpoint from your own domain. Adblockers can't distinguish it from your own assets.
Pick the method matching your stack:
// next.config.ts
const nextConfig: NextConfig = {
async rewrites() {
return [
{
source: "/u/script.js",
destination: "https://<UMAMI_HOST>/script.js",
},
{
source: "/u/api/send",
destination: "https://<UMAMI_HOST>/api/send",
},
];
},
};
Then add to your layout:
<script defer src="/u/script.js" data-website-id="<WEBSITE_ID>"></script>
{
"rewrites": [
{
"source": "/u/script.js",
"destination": "https://<UMAMI_HOST>/script.js"
},
{
"source": "/u/api/send",
"destination": "https://<UMAMI_HOST>/api/send"
}
]
}
Then add before </head> in your layout(s):
<script defer src="/u/script.js" data-website-id="<WEBSITE_ID>"></script>
example.com {
handle /u/script.js {
rewrite * /script.js
reverse_proxy https://<UMAMI_HOST> {
header_up Host <UMAMI_HOST>
}
}
handle /u/api/send {
rewrite * /api/send
reverse_proxy https://<UMAMI_HOST> {
header_up Host <UMAMI_HOST>
}
}
}
location /u/script.js {
proxy_pass https://<UMAMI_HOST>/script.js;
proxy_set_header Host <UMAMI_HOST>;
}
location /u/api/send {
proxy_pass https://<UMAMI_HOST>/api/send;
proxy_set_header Host <UMAMI_HOST>;
}
curl -sI https://<YOUR_DOMAIN>/u/script.js should return 200Use /u/ as the proxy prefix. It's short, non-obvious to blockers, and consistent across projects:
| Project | Proxy path | Umami host |
|---|---|---|
| thenocodeguy.com | /umami/script.js | analytics.casys.ai |
| casys.ai | /u/script.js | analytics.casys.ai |
# Get all websites
curl -s -H "Authorization: Bearer $TOKEN" "https://<UMAMI_HOST>/api/websites"
# Get stats for a website (last 24h)
START=$(($(date +%s) * 1000 - 86400000))
END=$(($(date +%s) * 1000))
curl -s -H "Authorization: Bearer $TOKEN" \
"https://<UMAMI_HOST>/api/websites/<WEBSITE_ID>/stats?startAt=$START&endAt=$END"
# Get pageviews
curl -s -H "Authorization: Bearer $TOKEN" \
"https://<UMAMI_HOST>/api/websites/<WEBSITE_ID>/pageviews?startAt=$START&endAt=$END&unit=day"
127.0.0.1 only — never expose Umami directly to the internet/u/ prefix can be anything — /stats/, /t/, etc. — as long as it doesn't conflict with existing routes