Umami on Vercel + Neon: Free Self-Hosted Analytics in 5 Minutes
Goal: a fully working Umami install in under 5 minutes, deployed to Vercel’s free tier with a Postgres database from Neon (also free), wired to your custom subdomain. Total cost at any reasonable indie scale: €0/mo.
This is the absolute lowest-friction self-host setup in 2026. Zero servers, zero Docker, zero cron, zero TLS config — Vercel handles all of it. The catch: you’re trusting two SaaS providers (Vercel + Neon) for hosting, but the data and code remain yours. If either disappears, you redeploy elsewhere in 5 minutes from the same git repo. Tested 2026-05-02 on Vercel Hobby + Neon Free.
Why Umami (and the trade-offs)
Umami is the smallest cookieless tracker in the field — a 21KB-or-less script, a clean React dashboard, MIT licensed, written in TypeScript by a single maintainer. It’s perfect for personal sites, side projects, and indie SaaS that want pageviews + sources + browser/OS/country breakdowns without any of the enterprise baggage.
What you give up vs Plausible:
- No EU-only hosting guarantee (Vercel + Neon US-default unless you pay for EU edge)
- Less polished docs / smaller community
- No event funnels (just custom events as raw counts)
What you give up vs Matomo: basically everything except pageviews. If you need goals, ecommerce, attribution — go straight to Matomo.
Step 1 — Create a Neon Postgres database (1 minute)
- Sign up at neon.tech with email or GitHub.
- Create new project → Postgres 16 → region: pick EU Frankfurt if you want EU data residency (GDPR), US East for cheaper Vercel routing.
- Project name:
umami. Default branch:main. - Once created, go to Connection Details → copy the
postgres://connection string. It looks like:
postgres://username:[email protected]/neondb?sslmode=require
Save it somewhere — we’ll paste into Vercel in 30 seconds.
Free tier: 0.5 GB storage, 191 compute hours/month — enough for any indie site with <1M events/mo. Above that, $19/mo Pro plan or migrate to a CX22 + self-hosted Postgres.
Step 2 — Fork Umami on GitHub
Go to github.com/umami-software/umami → click Fork. You’ll get github.com/your-username/umami.
(Why fork instead of clone? You’ll want to pull upstream updates and re-deploy. Forking gives you a controllable git history.)
Step 3 — Deploy to Vercel (90 seconds)
- Go to vercel.com/new, log in with GitHub.
- Import your forked
umamirepo. - Framework preset: Vercel auto-detects Next.js. Don’t change anything.
- Environment Variables — add two:
DATABASE_URL =HASH_SALT = - Click Deploy.
Vercel builds (Next.js Prisma migrate + Next build) for ~2 minutes. When it finishes, you have a working Umami at your-umami-xxxx.vercel.app.
Step 4 — First login + add your custom domain
Visit the Vercel URL. Default credentials:
Username: admin
Password: umami
Log in immediately. Settings → Profile → change password. Don’t skip this — those default creds are publicly known.
Now wire your subdomain (so events look like first-party):
- Vercel Project → Settings → Domains → Add →
analytics.your-site.com. - Vercel shows the DNS records you need. Two options:
- CNAME (preferred for subdomain):
analytics→cname.vercel-dns.com - A record (apex domain):
76.76.21.21
- CNAME (preferred for subdomain):
- Add the record at your DNS provider. TLS is auto-issued (Vercel uses Let’s Encrypt under the hood).
Within 1–2 minutes, https://analytics.your-site.com serves your Umami dashboard. The Vercel URL keeps working too — both point at the same deployment.
Step 5 — Add a website + paste the snippet
- Umami dashboard → Settings → Websites → Add website.
- Name + domain. Save.
- Click the website → Edit → Tracking code → copy the
<script>snippet. - Paste into the
<head>of your tracked site, OR if you want first-party tracking (avoids ad-blockers), use:<script defer src="https://analytics.your-site.com/script.js" data-website-id="YOUR-UUID-HERE"></script>
First pageview should appear in the dashboard within 30 seconds.
What just happened (mechanism)
- Umami app — a Next.js 14 server-rendered app deployed as Vercel Functions. The dashboard is React; the
/api/sendingest endpoint is a serverless function that writes to Postgres. - Neon Postgres — your event store. Two tables you’ll care about:
website_event(every event) andsession(deduplicated sessions, salted-hash-based). Auto-suspends after 5 min idle to save free-tier compute. - Vercel Edge Network — serves the static dashboard JS/CSS from CDN, routes
/api/sendrequests to the Functions runtime in your selected region. - HASH_SALT — server-side salt for the daily-rotating visitor hash. Unique sessions are computed as
hash(IP + User-Agent + website-id + salt + day). Salt rotation is tied to the env var; we don’t rotate ours daily, so your “unique visitor” count is stable per HASH_SALT lifetime.
Cookies set on visitors: zero. Same legitimate-interest GDPR basis as Plausible — no consent banner needed.
The free-tier limits (real numbers)
| Service | Free includes | You’ll hit limit at |
|---|---|---|
| Vercel Hobby | 100 GB bandwidth, 100k function invocations | ~3M events/mo (each event is one function call) |
| Neon Free | 0.5 GB storage, 191 compute hrs | ~1M events stored (then need to prune) |
| Combined practical limit | — | ~500k events/mo before you start watching |
Above that point, you have three escalation paths:
- Vercel Pro ($20/mo) + Neon Pro ($19/mo) = $39/mo and ride the SaaS curve.
- Move just the database to Hetzner ($5/mo VPS with self-hosted Postgres) and keep Vercel Hobby. Total: $5/mo.
- Move both to a self-hosted box. Same Hetzner CX22 can run Umami too.
Step 6 — Auto-prune old data (so you stay free)
The cheapest way to stay under Neon’s 0.5 GB: prune events older than N days. Umami doesn’t ship a cron — but Vercel Cron Jobs are free and can call any endpoint.
Add a file to your fork: app/api/cron/prune/route.ts:
import { headers } from 'next/headers';
import prisma from '@/lib/prisma';
export async function GET() {
const auth = (await headers()).get('authorization');
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('unauthorized', { status: 401 });
}
const cutoff = new Date(Date.now() - 365 * 86400 * 1000);
await prisma.websiteEvent.deleteMany({ where: { createdAt: { lt: cutoff } } });
return Response.json({ pruned_before: cutoff.toISOString() });
}
Add a vercel.json:
{
"crons": [{ "path": "/api/cron/prune", "schedule": "0 4 * * 0" }]
}
Add env var CRON_SECRET in Vercel (any random string). Push, redeploy. Now every Sunday at 04:00 UTC, events older than 365 days are deleted. Adjust cutoff to your retention policy.
Updating Umami (every 4–6 weeks)
Umami releases roughly monthly:
- In your fork on GitHub: Sync fork → Update from upstream → Discard my changes if not customizing.
- Vercel auto-deploys when main branch updates. Watch the deployment for migration errors.
- Check Umami changelog for breaking schema changes.
Total time per update: ~1 minute.
3-year cost
| Item | 3-year total |
|---|---|
| Vercel Hobby | €0 |
| Neon Free | €0 |
| Domain (subdomain — no extra) | €0 |
| Total | €0 |
Yes, really €0 if you stay under ~500k events/mo. The closest commercial alternative (Plausible Cloud Starter at €9/mo) is €324 over 3 years. Umami on Vercel is the price-per-feature king for indie scale.
When NOT to use this setup
- You exceed 1M events/mo regularly. The free tiers won’t sustain you and the Vercel+Neon Pro stack ($39/mo) is now more than Plausible Cloud Starter. Switch.
- You need goals/funnels/attribution. Umami has custom events but no funnel builder. Matomo is a better fit.
- EU-only data residency required. Vercel’s free tier doesn’t pin to EU regions. Pick Neon EU Frankfurt at minimum, but Vercel Functions still execute globally on Hobby. Self-host on Hetzner instead.
- You’re skeptical of vendor lock. While Umami code is portable, your specific deployment depends on Vercel + Neon staying in business. Self-hosting on a VPS removes both dependencies.
Troubleshooting
Build fails with “P3009: migrate found failed migrations”. Neon went into “branch was reset” state. In Neon Console → Branches → Reset branch → main. Then redeploy on Vercel.
Default password “umami” still works after I changed it. The login page caches credentials per-deployment. Hard refresh (Ctrl+Shift+R), incognito, then re-login.
Events not appearing in dashboard but script loads. Browser ad-blocker. Open DevTools → Network → look for send request. If blocked, deploy under your own subdomain (analytics.your-site.com) so the request is first-party — uBlock Origin won’t block it.
Compute hours warning from Neon. Auto-suspend isn’t kicking in. Settings → Compute → enable auto-suspend at 5 min. Reduces idle hours dramatically.
Next
- Stack Picker: confirm Umami is your right pick.
- TCO calculator: see when free tier starts losing to a $5 VPS.
- Plausible recipe: if you outgrow Umami, this is the natural step up.
Found this useful?
Try the Stack Picker to get a personal recommendation, or browse the install recipe library.