DEV Community

우병수
우병수

Posted on • Originally published at techdigestor.com

5 Self-Hosted Analytics Tools Worth Running on Your Own Hardware in 2026

TL;DR: GA4's event model wasn't designed for people who want to understand their data — it was designed for people who want to feed Google's ad graph. The sampling kicks in the moment your traffic exceeds the free tier thresholds, which means the numbers you see in your dashboard are e

📖 Reading time: ~21 min

What's in this article

  1. The Real Problem: You're Flying Blind or Paying Google to Watch You
  2. How to Read This Comparison: Constraints That Force a Choice
  3. Plausible Analytics: Minimum Footprint, Maximum Readability
  4. Umami: When You Need Multi-Site Tracking Without the ClickHouse Tax
  5. Matomo: Full-Featured but Operationally Expensive
  6. PostHog: Product Analytics With a Real Event Pipeline
  7. Grafana + a Data Source: When You Already Have the Metrics
  8. When to Pick What: A Decision Matrix for Operators

The Real Problem: You're Flying Blind or Paying Google to Watch You

GA4's event model wasn't designed for people who want to understand their data — it was designed for people who want to feed Google's ad graph. The sampling kicks in the moment your traffic exceeds the free tier thresholds, which means the numbers you see in your dashboard are estimates dressed up as facts. Export to BigQuery is the official escape hatch, but it's throttled, requires a Google Cloud billing account, and you're still querying data that lives on infrastructure you don't control. For a small business operator, that's a fragile foundation: Google can deprecate the export format, change the schema, or restructure pricing, and your entire analytics workflow breaks overnight.

The self-hosted case isn't primarily a privacy argument, though that matters too. The real use is schema ownership. When you run your own analytics stack, you decide what an "event" means, how long raw data is retained, and whether you can run a GROUP BY at 2am without hitting a quota. You also control query latency — the difference between a ClickHouse instance on the same LAN as your dashboard and a round-trip to a managed SaaS API is measurable in seconds per page load when your queries get complex. That latency difference becomes load-bearing when you want to pipe analytics signals into automation workflows rather than just stare at charts.

The five tools covered here all have one thing in common: they run on commodity hardware without requiring a dedicated database cluster or a DevOps team. A single VPS with 2 vCPUs and 4GB RAM is enough to start with most of them. A home-lab Docker host is fine for lower-traffic sites. Each tool gets an honest resource cost — disk growth rate, RAM floor, CPU spikes during ingestion — and one dealbreaker that the README won't tell you upfront. No tool on this list is universally the right choice, and the honest answer is that the right pick depends on whether you need SQL access, funnel visualization, session replay, or lightweight script-tag deployment.

One angle that rarely gets covered in analytics tool comparisons: if you're already running automation pipelines, your analytics data shouldn't just sit in a dashboard — it should be a trigger source. A spike in 404 events, a drop in checkout completions below a threshold, a new referrer domain hitting your site — all of these are signals that can fire n8n workflows, send Slack alerts, or update a CRM record without a human checking a dashboard first. The infrastructure overlap between a self-hosted analytics backend and an event-driven automation stack is significant, and it's worth thinking about before you pick your schema. For the pipeline side of that equation, the Workflow Automation in 2026: n8n, Zapier, and Self-Hosted Pipelines guide covers how to wire those event sources into real workflows.

How to Read This Comparison: Constraints That Force a Choice

Most comparisons of analytics tools sort by feature count. That's the wrong axis. The real question is how much schema complexity and operational surface area you're willing to own. A page-view counter needs one table and a cron job. A full event pipeline needs a queue, a schema migration strategy, and someone to watch it at 2 AM when the ingestion worker silently backs up. Those are not the same class of problem, and picking the wrong tier will cost you more time than any missing feature ever would.

The hardware baseline throughout this comparison is a single Docker host: 2 vCPUs, 4 GB RAM, 20 GB SSD. That maps to a $6–12/month VPS (Hetzner CX22, DigitalOcean Basic, Vultr Regular) or a repurposed home-lab node running Debian or Ubuntu 22.04. If a tool routinely exceeds that envelope at idle — not under load, at idle — that will be flagged explicitly with numbers, not vague warnings. Some tools in this list are fine on that spec. One or two will push you toward 8 GB RAM before you've ingested a single real event.

The columns that actually drive the decision:

  • Minimum RAM at idle — what the process tree consumes after startup with no traffic. This is where most comparisons lie by omission.
  • Database backend — SQLite vs. Postgres vs. ClickHouse is not a detail, it's a maintenance contract. SQLite means zero ops and zero horizontal scale. ClickHouse means real performance on large datasets and real complexity on backup/restore.
  • Data export format — can you get your raw events out as CSV, JSON, or SQL dump without paying for a higher tier? Vendor lock-in in self-hosted tools usually lives here, not in the UI.
  • Self-hosted feature parity — some tools gate feature flags, funnels, or session replay behind their cloud plan even if you're running their Docker image. This is more common than the README implies.
  • Single biggest operational gotcha — the thing that doesn't appear until you've run it for a week: a migration that requires downtime, a default retention setting that fills your disk, a background job that pegs CPU on every page load.

The three tiers covered here map to real operational profiles. Lightweight page-view counters — Plausible and Umami — are single-binary or two-container deployments where the biggest decision is whether to use SQLite or Postgres. Full event pipelines — PostHog and Matomo — bring plugin ecosystems, session capture, and funnel analysis, but they also bring background workers, cache layers, and schema migrations you didn't ask for. Grafana sitting in front of a data source is a different category entirely: it doesn't collect events, it visualizes metrics you're already emitting, so its "analytics" story depends entirely on what's feeding it. Comparing those three tiers on the same feature grid produces misleading output. The sections below keep them in their lanes.

Plausible Analytics: Minimum Footprint, Maximum Readability

Plausible's Docker Compose stack is genuinely small — the official repo gives you a working docker-compose.yml on day one, and unlike most "self-hosted" analytics setups, you don't spend the first afternoon debugging missing env vars. The ClickHouse backend is pre-configured, the Elixir app boots cleanly, and you're ingesting hits within minutes. The catch that the README doesn't emphasize: that default ClickHouse config is a single-shard setup. Under roughly 10M monthly events it behaves fine. Above that, you're not dealing with a gradual slowdown — query times start spiking and you'll need to rethink the ClickHouse config before you hit the ceiling, not after.

Memory footprint at idle is honest for what it does. The Elixir app sits around 350 MB RSS, ClickHouse cold-starts near 600 MB. Both numbers are tolerable on a 2 GB VPS — until you get a traffic spike. ClickHouse will happily consume whatever RAM is available during a burst of aggregation queries. If you haven't dropped a max_memory_usage cap into your config.d/ override, expect it to balloon past 1.5 GB with no warning. The fix is straightforward but underdocumented:

<!-- /etc/clickhouse-server/config.d/memory.xml -->
<yandex>
  <max_memory_usage>1073741824</max_memory_usage>       <!-- 1 GB hard cap per query -->
  <max_memory_usage_for_all_queries>1610612736</max_memory_usage_for_all_queries>  <!-- 1.5 GB total -->
</yandex>
Enter fullscreen mode Exit fullscreen mode

Mount that file into the ClickHouse container via your compose override and restart. Without it, on a small VPS, you're one dashboard refresh from an OOM kill.

The comparison with Plausible's paid cloud is frequently misrepresented. Funnels, revenue goals, and the Sites API are all present in CE — the real difference is operational: you own the ingestion pipeline end-to-end. One botched deploy, one misconfigured reverse proxy dropping the X-Forwarded-For header, and you're silently losing events with no alerting from Plausible itself. The cloud version has buffer layers and retries you never see. On CE, you build your own monitoring or you fly blind. A basic healthcheck hitting the /api/health endpoint on a 60-second cron is the minimum viable safety net:

# quick health probe — alerts if the Elixir app stops accepting requests
curl -sf https://your-plausible-domain.com/api/health || \
  notify-send "Plausible ingestion down"
Enter fullscreen mode Exit fullscreen mode

The hardest limit for non-engineering users is raw event access. There is no "Export to CSV" button covering raw hits — the UI gives you aggregated views only. If you or anyone else needs row-level data, you go directly to ClickHouse via clickhouse-client or the HTTP interface. For an engineer that's fine; you can pull session-level data in seconds:

clickhouse-client --query \
  "SELECT session_id, pathname, country_code, timestamp
   FROM plausible_events_db.events
   WHERE domain = 'yoursite.com'
     AND toDate(timestamp) = today()
   LIMIT 500"
Enter fullscreen mode Exit fullscreen mode

For a marketing hire or a business owner who just wants a spreadsheet, this becomes your support burden immediately. If anyone outside engineering needs to self-serve on raw data, factor in either building a thin query UI on top of ClickHouse or routing aggregated exports through something like Metabase pointed at the same database.

Umami: When You Need Multi-Site Tracking Without the ClickHouse Tax

Most self-hosted analytics tools that want to be taken seriously these days ship with ClickHouse as a hard dependency. That's a reasonable call for high-volume traffic, but it also means you're suddenly maintaining a columnar database engine alongside everything else, your backup story involves unfamiliar tooling, and your RAM budget just jumped by 2–4 GB minimum. Umami skips all of that. It writes directly to PostgreSQL or MySQL, which means if you're already running Postgres for anything else, Umami is just another schema in a database you already know how to operate. Backup is literally pg_dump. Restore is psql. There's nothing novel to learn.

The Docker Compose setup reflects that simplicity. Three services: the Next.js frontend/API app, a Postgres container, and nothing else. No Redis, no queue workers, no separate ingest service. Idle RAM on my box lands around 250 MB for the whole stack — that's lighter than a single Plausible container on a busy day. If you're running this on a $6 VPS or a shared homelab node, it won't crowd out your other workloads.

# minimal docker-compose.yml — production-ready starting point
services:
  umami:
    image: ghcr.io/umami-software/umami:postgresql-latest
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgresql://umami:${DB_PASSWORD}@db:5432/umami
      APP_SECRET: ${APP_SECRET}   # random string, not optional — sessions break without it
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - umami_db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U umami"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  umami_db:
Enter fullscreen mode Exit fullscreen mode

The v2 REST API is where Umami earns its place in an automation stack. Pulling stats into n8n or a cron job is a single authenticated GET call once you have a bearer token from POST /api/auth/login. From there, GET /api/websites/:id/stats?startAt=&endAt= returns pageviews, sessions, bounce rate, and visit duration as a flat JSON object — no pagination, no cursors, nothing exotic. In my n8n flows I use an HTTP Request node with the bearer token stored as a credential, call that endpoint on a schedule, and pipe the numbers directly into a Postgres insert or a Slack summary. The whole flow is under 10 nodes.

# grab a bearer token, then pull 7-day stats
TOKEN=$(curl -s -X POST https://your-umami-host/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"'$UMAMI_PASS'"}' \
  | jq -r '.token')

NOW=$(date +%s%3N)
WEEK_AGO=$(( NOW - 604800000 ))

curl -s "https://your-umami-host/api/websites/$SITE_ID/stats?startAt=$WEEK_AGO&endAt=$NOW" \
  -H "Authorization: Bearer $TOKEN" | jq .
# returns: {"pageviews":{"value":4821},"sessions":{"value":1203},...}
Enter fullscreen mode Exit fullscreen mode

The performance ceiling is real and worth planning around before you commit. Once website_event crosses roughly 5 million rows, queries on the default schema start to drag — the dashboard date-range filters hit that table hard. The fix is a partial index on created_at scoped to your active website IDs, and ideally a monthly partitioning strategy if you're ingesting more than a few thousand events per day. Umami's migrations don't set this up for you, so it's manual work. And the dealbreaker is firm: there are no funnel views, no cohort analysis, no event sequencing. If your question is "how many users completed checkout within a session that started on the pricing page," Umami cannot answer that. It counts pageviews and custom events — cleanly, reliably, with low overhead — and that's the full scope of what it does.

Matomo: Full-Featured but Operationally Expensive

Matomo is the only self-hosted analytics tool that can walk into a GA4 feature comparison and not immediately lose. Goals, funnel reports, heatmaps, A/B testing, GDPR consent management — it's all there in the self-hosted version. That's a genuinely rare combination, and for a small business that migrated off Google Analytics and doesn't want to rebuild an analytics stack from scratch, Matomo is the obvious first stop.

The operational cost, though, is not small. Matomo runs on PHP-FPM + MySQL/MariaDB, and its database schema is wide. The two tables that will eventually cause you pain are log_visit and log_link_visit_action — they grow faster than you'd expect and don't self-prune without explicit configuration. Beyond storage, the bigger trap is the archiving cron. Matomo doesn't compute reports on the fly for historical data; it relies on a scheduled job to pre-aggregate everything:

# this must run reliably — add to crontab or a supervisor job
# if it times out silently, your reports will just show stale data with no error
*/5 * * * * /usr/bin/php /var/www/matomo/console core:archive --url=https://your-matomo-instance.com
Enter fullscreen mode Exit fullscreen mode

The silent timeout is the failure mode that bites people. If your site has accumulated a large date range and the archive job hits PHP's max_execution_time or MySQL's wait_timeout before finishing, it exits without completing — and your dashboard just quietly shows yesterday's numbers forever. Fix this by setting max_execution_time = 0 in the PHP CLI config (not the web config), and tuning interactive_timeout and wait_timeout in MySQL to something above your expected archive duration. Also worth setting in config/config.ini.php:

[General]
# prevent archiving from triggering on browser requests — force cron-only
enable_browser_archiving_triggering = 0

# archive segments with data older than this many seconds
time_before_today_archive_considered_outdated = 900
Enter fullscreen mode Exit fullscreen mode

RAM footprint at idle lands between 500 MB and 1 GB depending on your PHP-FPM pool size and MySQL buffer pool configuration. Once you cross roughly one million monthly pageviews, plan to dedicate 2 GB to this stack — and that's before you factor in the archiving job competing with live ingestion during peak hours. That's a non-trivial ask on a small VPS.

The dealbreaker for many operators is the plugin licensing model. Matomo's core is genuinely solid and open source, but the features most commonly cited as differentiators — heatmaps and session recording especially — require a paid license even on the self-hosted version. The pricing isn't obscene, but it fractures the "free GA4 replacement" argument immediately. If you need heatmaps on self-hosted Matomo, you're paying. That's a legitimate business model, but be clear-eyed about it before you migrate 30 properties onto this stack and then discover the plugin wall.

PostHog: Product Analytics With a Real Event Pipeline

PostHog occupies a different tier from Plausible or Umami — comparing them is like comparing a server access log analyzer to a full product intelligence platform. The self-hosted version ships with feature flags, session replay, heatmaps, funnel analysis, retention curves, path analysis, and an A/B testing engine, all wired into the same event pipeline. You're not stitching together four tools; it's one data model feeding every view. For a small product team that wants to run controlled rollouts and watch session replays without paying per-seat SaaS pricing, that consolidation is the entire argument.

The deployment story is where most people hit friction. PostHog is Kubernetes-first by architecture, and the official Helm chart makes that obvious. What saves smaller operators is the hobby deploy — a Docker Compose path that's actually maintained and documented, not an afterthought. The minimum spec is 4 GB RAM, but treat 8 GB as the real floor. ClickHouse is the hungry component here, same as in every other self-hosted analytics stack that prioritizes query speed. The hobby installer is a single curl-pipe-bash:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/PostHog/posthog/HEAD/bin/deploy-hobby)"
Enter fullscreen mode Exit fullscreen mode

That script prompts for a domain, writes a .env, and brings up the compose stack. What it won't tell you upfront is that you're launching roughly 10 containers: ClickHouse, Kafka, Zookeeper, Redis, the Django app server, Celery beat, Celery worker, the plugin server (Node.js), an Nginx proxy, and a periodic worker. At idle, expect the stack to consume 3–4 GB RAM on the host — measured, not estimated. This is not a workload for a $6 VPS. A $24–$40/month dedicated instance with a real SSD is closer to the minimum viable host if you want headroom for actual event ingestion.

The self-hosted changelog is required reading before you commit. PostHog is honest in its docs about the feature gap: the cloud version ships AI-powered analysis, some newer product analytics views, and LLM observability tooling that the self-hosted version lags on, sometimes by months, sometimes indefinitely. The CHANGELOG.md in the repo and the self-hosted-specific release notes at posthog.com/docs/self-host/runbook will tell you exactly which features are missing. The gap isn't fatal for most small business use cases — funnel analysis, retention, and session replay are all present and maintained — but if you're evaluating PostHog specifically for its newer AI query features, test the cloud trial first and verify those features exist in the current self-hosted release before building a deployment around them.

Where PostHog makes sense over the lighter tools: you're building a product (SaaS, app, internal tool) and you need behavioral cohorts, not just page view counts. The JavaScript snippet and the posthog-js SDK capture custom events with properties, which means you can track button_clicked with { plan: "pro", screen: "checkout" } and then filter session replays to only users who hit that event before churning. That's the capability gap. If your analytics need is "how many people visited the pricing page," Umami costs you nothing and runs in 256 MB. If your need is "show me session replays of users who started checkout, enabled the coupon field, and didn't convert," PostHog is the only self-hosted option in this list that actually answers that question.

Grafana + a Data Source: When You Already Have the Metrics

Most analytics tool roundups slot Grafana in as if it competes with Plausible or Umami. It doesn't. Grafana visualizes data that already exists somewhere else — it has zero event ingestion capability of its own. No tracking pixel, no SDK, no JS snippet you embed on your site. If you don't already have a time-series store running, Grafana on its own is a dashboard with nothing to display. That's the first thing to establish, because it determines exactly who should use it.

The case for including it here is specific: if you're already running Plausible (which uses ClickHouse) or Umami (PostgreSQL), both databases can be wired directly into Grafana as data sources. What that unlocks is correlation dashboards that no SaaS analytics product touches — overlay your Plausible pageview counts against Prometheus node CPU metrics, then drop in Loki annotations for deployment events. A traffic spike that coincides with a deploys and a CPU peg tells you something a standalone analytics UI never would. On my own setup I pull Umami's Postgres data alongside node_exporter metrics in the same Grafana instance, and seeing both on one timeline is genuinely useful for distinguishing "the site got traffic" from "the site got slow."

The resource story is split in two. Grafana itself is light — around 150 MB RSS at idle, negligible CPU unless you're rendering a lot of panels simultaneously. The real cost is the stack behind it:

  • Grafana + Prometheus + Loki together run roughly 1.5–2 GB before you add any exporters. That's meaningful on a small VPS.
  • Grafana + existing Plausible ClickHouse adds almost nothing — you're querying a store you already pay for.
  • Alerting via Grafana Alertmanager adds another moving part; if you only want dashboards, disable it and save the complexity.

The Docker Compose addition to an existing Plausible stack is minimal. You expose the ClickHouse port internally and point Grafana at it:

# add to your existing plausible docker-compose.yml
  grafana:
    image: grafana/grafana-oss:10.4.2
    ports:
      - "3001:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      # disable public signup — non-negotiable on a public host
      GF_AUTH_DISABLE_LOGIN_FORM: "false"
      GF_USERS_ALLOW_SIGN_UP: "false"
    depends_on:
      - plausible_db  # ensures ClickHouse is up first

volumes:
  grafana_data:
Enter fullscreen mode Exit fullscreen mode

Then in the Grafana UI, add a ClickHouse data source (requires the grafana-clickhouse-datasource plugin) pointed at plausible_events_db on the internal Docker network. You can query Plausible's events table directly with raw SQL panels — no intermediate ETL needed. The dealbreaker remains firm though: if you're starting from scratch with no existing metrics infrastructure and just need to know how many people visited your site, skip Grafana entirely and start with Umami or Plausible. Grafana earns its spot only when you're layering business observability on top of operational monitoring that's already running.

When to Pick What: A Decision Matrix for Operators

The mistake most operators make is picking analytics software based on feature lists rather than operational fit. The question isn't which tool has the most capabilities — it's which one you'll still be running cleanly six months from now without having touched the config. These five tools solve genuinely different problems, and using the wrong one costs you either maintenance overhead or missing data.

  • Pick Plausible CE if you want a production-ready, low-maintenance page analytics stack on a single VPS and you're comfortable dropping into ClickHouse when you need raw data. Plausible's Docker Compose setup is stable, the resource footprint is small, and the default dashboard covers 90% of what a content operator actually needs day-to-day. The trade-off: the API surface is narrow, and when you want something it doesn't expose in the UI, you're writing ClickHouse SQL directly — which is powerful but not a casual afternoon task.
  • Pick Umami if you need multi-site tracking under a single install, your infrastructure already runs PostgreSQL, and you want a clean REST API for pulling stats into automation pipelines. Of every tool in this list, Umami is the easiest to integrate with n8n or a TypeScript/Node publishing engine running on PM2 — the /api/websites/:id/stats endpoint returns structured JSON with no ceremony. Token auth works cleanly, response times are fast on even modest hardware, and the schema is simple enough to query directly if the API doesn't expose what you need.
  • Pick Matomo if you have a real compliance requirement — GDPR audit trail, configurable consent management, or a data processing agreement you need to document — or if you have non-technical stakeholders who need GA-equivalent reports without learning a new interface. Matomo is the heaviest of the five operationally, but it's the only one where a marketing person can sit down and feel at home immediately. The plugin ecosystem also means you can add heatmaps, A/B testing, and form analytics without bolting on separate tools.
  • Pick PostHog if you're operating a product rather than a content site and you need funnel analysis, feature flags, and session replay in one place. This is not a casual self-host — budget at least 8 GB RAM for the host, expect ClickHouse to be the memory floor, and read the resource requirements before committing. The payoff is that PostHog gives you product analytics depth that none of the others touch: cohort retention, event-level autocapture, and feature flag targeting that actually closes the loop between deploy and behavior data.
  • Layer Grafana on top of any of the above if you're already running a home-lab or server monitoring stack and want business metrics in the same dashboard as node exporter, Caddy access logs, or container health. Grafana doesn't replace any of these tools — it reads from their underlying databases (Postgres, ClickHouse, MySQL) via datasource plugins and lets you build unified panels. On my workstation setup, having a single Grafana board showing site traffic alongside GPU utilization and n8n workflow success rates is genuinely more useful than toggling between four separate UIs.

One cross-cutting signal worth flagging: if your stack is already PostgreSQL-heavy, Umami and Matomo both run on it natively, which means one fewer moving part in your backup and restore procedures. If you're already committed to ClickHouse for something else — or willing to learn it — Plausible and PostHog both use it as their event store, and the query power you get in return is significant. Don't introduce a second database engine just because a tool looks appealing; the operational cost compounds quietly until a disk fills or a migration breaks.


Disclaimer: This article is for informational purposes only. The views and opinions expressed are those of the author(s) and do not necessarily reflect the official policy or position of Sonic Rocket or its affiliates. Always consult with a certified professional before making any financial or technical decisions based on this content.


Originally published on techdigestor.com. Follow for more developer-focused tooling reviews and productivity guides.

Top comments (0)