I asked a simple question about my own sites. What does traffic look like? The honest answer was nothing. My reverse proxy had run for months with zero access logging. Here is how I closed that gap with a self-hosted analytics stack, start to finish.
It is easy to assume a reverse proxy logs requests just because it is healthy and serving traffic fine. Mine
did not. A quick check of the container's own logs showed only internal housekeeping. TLS storage cleanup,
certificate maintenance. Never a single HTTP request. No log directive had ever been added to
any site block. Check this directly instead of assuming, especially on infrastructure stable long enough that
you stopped thinking about it.
Three real options, roughly in order of weight:
I went with Umami. A real dashboard without Plausible's operational overhead.
postgresql-latest and mysql-latest are published now. A small Postgres sidecar
container turned out to be the actual lightweight option, not the extra step it looked like on paper.
A single small container or VM running both services via Docker Compose:
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_DB: umami
POSTGRES_USER: umami
POSTGRES_PASSWORD: "<generated>"
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U umami"]
umami:
image: ghcr.io/umami-software/umami:postgresql-latest
ports:
- "3050:3000"
environment:
APP_SECRET: "<generated>"
DATABASE_URL: "postgresql://umami:<password>@db:5432/umami"
depends_on:
db:
condition: service_healthy
admin and umami account. Log in once via the API. Set a new password
immediately. Confirm the old credentials are rejected. Do all of that before you add any public DNS record
or reverse proxy rule. A newly public admin panel on default credentials, even briefly, is a real exposure
window.
If you already run other *.yourdomain.com subdomains behind Cloudflare with an Origin CA
certificate, a new subdomain needs zero new certificate work. Just a new site block reusing the existing
wildcard cert. The reverse proxy migration guide covers how that is set
up:
analytics.example.com {
tls /config/origin-cert.pem /config/origin-key.pem
reverse_proxy 192.168.1.X:3050
}
Reload the reverse proxy after adding it. Reload, not restart. A graceful reload validates the config and applies it without dropping any of your other live domains' connections. Confirm a handful of your existing domains still respond right after, not just the new one.
Umami gives each site you register a small script tag with a unique website ID:
<script defer src="https://analytics.example.com/script.js" data-website-id="..."></script>
Drop it before </head> on every page you want tracked. On a static multi-page site, run a
one-line script across all your HTML files. Faster and less error-prone than editing each by hand, and it
lets you confirm none were silently missed.
A running container proves nothing about whether analytics work end to end. DNS, the reverse proxy route and the tracking script all have to line up first. Hit Umami's collect API directly to simulate a real pageview:
curl -X POST http://localhost:3050/api/send \
-H "Content-Type: application/json" \
-d '{"type":"event","payload":{"website":"<website-id>","url":"/","hostname":"example.com"}}'
Then check the stats endpoint, or the dashboard, actually shows it. The pipeline is confirmed working when a real pageview shows up as a real number. Not before.
None of the above makes the analytics domain publicly reachable. On Cloudflare, every subdomain needs its
own explicit DNS record. There is usually no wildcard record silently covering new ones. An A record
pointing at the same origin your other subdomains use, proxied the same way, is enough. It is also easy to
build and verify everything else and forget this last unglamorous step. Confirm public resolution and a real
200 from the script URL before you call it done.