<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Cal Merritt</title>
    <description>The latest articles on Forem by Cal Merritt (@calmerritt).</description>
    <link>https://forem.com/calmerritt</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3832861%2F72fc7130-daa5-4d12-a182-096a268ef259.png</url>
      <title>Forem: Cal Merritt</title>
      <link>https://forem.com/calmerritt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/calmerritt"/>
    <language>en</language>
    <item>
      <title>I ran GA and Umami side by side for a month. GA was missing half my visitors.</title>
      <dc:creator>Cal Merritt</dc:creator>
      <pubDate>Thu, 19 Mar 2026 01:56:13 +0000</pubDate>
      <link>https://forem.com/calmerritt/i-ran-ga-and-umami-side-by-side-for-a-month-ga-was-missing-half-my-visitors-1b0g</link>
      <guid>https://forem.com/calmerritt/i-ran-ga-and-umami-side-by-side-for-a-month-ga-was-missing-half-my-visitors-1b0g</guid>
      <description>&lt;p&gt;I didn't switch off Google Analytics because of privacy principles. I switched because the numbers stopped making sense.&lt;/p&gt;

&lt;p&gt;A post I wrote got popular, and a friend texted me saying it was getting passed around. I opened GA and saw a modest uptick. Nothing dramatic. But comments kept coming in, people kept mentioning it. Something felt off. So I did something I should have done a long time ago: I installed a second analytics tool and ran them both simultaneously.&lt;/p&gt;

&lt;p&gt;For a month, Umami and Google Analytics ran on the same site, tracking the same pages. At the end I compared them.&lt;/p&gt;

&lt;p&gt;GA showed about 40% fewer visitors.&lt;/p&gt;

&lt;p&gt;Not a rounding error. Not a misconfiguration. Just... gone. The gap was biggest on posts that did well on Hacker News or got shared in developer circles. Which makes sense once you think about it — that audience runs uBlock Origin the way most people run antivirus. It's just on. Always. And GA is one of the first things it kills.&lt;/p&gt;

&lt;p&gt;That was the moment I stopped treating this as a privacy debate and started treating it as an accuracy problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  What self-hosting actually looks like
&lt;/h2&gt;

&lt;p&gt;I know "self hosted" sounds like it implies a homelab and a weekend of suffering. It doesn't. The short version: you rent a small Linux server, point a subdomain at it, and run a Docker Compose file. The whole thing took me one afternoon including the time I spent reading documentation.&lt;/p&gt;

&lt;p&gt;Here's the &lt;a href="https://umami.is/" rel="noopener noreferrer"&gt;Umami&lt;/a&gt; setup I use. This is the entire &lt;code&gt;docker-compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;umami&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/umami-software/umami:postgresql-latest&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql://umami:yourpassword@db:5432/umami&lt;/span&gt;
      &lt;span class="na"&gt;DATABASE_TYPE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgresql&lt;/span&gt;
      &lt;span class="na"&gt;APP_SECRET&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;generate-this-with-openssl-rand-hex-32&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres:15-alpine&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;umami&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yourpassword&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;umami-db-data:/var/lib/postgresql/data&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;umami-db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;docker compose up -d&lt;/code&gt;, wait 30 seconds, open a browser. That's most of it. You still need Caddy or nginx in front of it for HTTPS, but that's trivial.&lt;/p&gt;

&lt;p&gt;The tracking snippet you drop into your site:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;async&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://your-umami.yourdomain.com/script.js"&lt;/span&gt;
  &lt;span class="na"&gt;data-website-id=&lt;/span&gt;&lt;span class="s"&gt;"the-id-from-your-dashboard"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No cookies. No consent banner needed. The script is about 2KB. Your visitors who run blockers will mostly still be counted because the request comes from your domain, not a known tracking domain.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually look at
&lt;/h2&gt;

&lt;p&gt;The Umami dashboard is one page. Visitors, pageviews, referrers, top pages, countries, devices. Everything at once. I check it the same way I check the weather: quickly, without much thought, when I'm curious. I don't have goals set up, I don't have funnels, I don't have conversion tracking. I just want to know if people are reading what I write and where they came from.&lt;/p&gt;

&lt;p&gt;If that's also what you want, Umami is probably all you need. It runs on the same server as three other things I self host and uses barely any resources.&lt;/p&gt;

&lt;p&gt;If you want something more, maybe proper funnels, session recordings, heatmaps: &lt;a href="https://matomo.org/" rel="noopener noreferrer"&gt;Matomo&lt;/a&gt; does all of that. It's heavier to run and the interface is busier, but it's been around since 2007 under its old name Piwik and it's genuinely good software.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stuff that actually matters
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;You own the data.&lt;/strong&gt; When Google shut down Universal Analytics in 2023, everyone scrambled to export their history before the window closed. That doesn't happen when the database is on your server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It's cheaper than you think.&lt;/strong&gt; $6/month for a server that runs analytics for every site you have, forever, with no per-site fees or pageview caps. I have four sites pointed at the same Umami instance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The GDPR situation.&lt;/strong&gt; Multiple EU data protection authorities have ruled that standard Google Analytics implementations are non-compliant because of data transfers to US servers. I'm not a lawyer, I'm not going to tell you what to do. But if any of your users are in Europe, and they probably are, this is a real thing worth knowing about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The accuracy is real.&lt;/strong&gt; On a general audience site the difference might be 10-15%. On a developer-focused site or anything that gets traction on HN or Reddit, it can easily be 50%. The Plausible team &lt;a href="https://plausible.io/blog/google-analytics-adblockers-missing-data" rel="noopener noreferrer"&gt;published a study&lt;/a&gt; comparing their stats against GA on a site that went viral on HN and found GA was missing 58% of visitors. That's not a small rounding error, that's a completely different picture of your traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which tool
&lt;/h2&gt;

&lt;p&gt;I've run Umami for a couple of years now and it's been completely uneventful, which is exactly what I want from infrastructure. Plausible Community Edition is what I'd reach for if I wanted something with more polish. The dashboard is genuinely beautiful and the single-page layout is exactly right. GoatCounter is worth knowing about if you want the absolute minimum: single binary, SQLite database, runs on a $3 server.&lt;/p&gt;

&lt;p&gt;If you want a more thorough breakdown of what each tool actually feels like to use, resource requirements, who each one is for, and step by step installs, I put all of that in a guide: &lt;a href="https://syslog.gumroad.com/l/your-data-actually" rel="noopener noreferrer"&gt;Your Data, Actually&lt;/a&gt;. Nine tools covered. Three full installs. Written for people who aren't sysadmins.&lt;/p&gt;

&lt;p&gt;But honestly, even without the guide, pick Umami, follow the Docker Compose setup above, spend an afternoon on it. Your traffic numbers will be more accurate, you won't owe anyone a cookie banner, and you'll stop subsidizing Google's ad business with your visitors' data.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
