FAQ
Common questions about running Sumimi. If yours is not here, the issue tracker is the place to ask.
General
How is this different from Umami?
Sumimi is an independent open-source project that implements the Umami v2 HTTP API. It is an open-source alternative to Umami: the official Umami dashboard and the Umami tracker work against it unchanged. What differs is the packaging — Sumimi ships as a self-contained service whose instance is one directory (a DuckDB file for the events, a small SQLite file for the accounts) and needs no external database, no PostgreSQL and no ClickHouse — and the privacy defaults, which are cookieless by construction.
Sumimi is not affiliated with, endorsed by or sponsored by the Umami project, and no Umami assets — logo, artwork, code or copy — are used.
Do I need a domain?
No. You can run it on http://localhost:3999 and collect data from a local
site. A domain and TLS are only needed if you want to serve it publicly, which in turn
is needed if the sites you measure are not on localhost — a browser will
refuse a mixed-content request to an insecure origin.
What are the system requirements?
A 64-bit Linux box (or macOS), 1 vCPU, 512 MB of RAM and some disk for the programs and the database. The database grows with your traffic but stays small: a page view is a few dozen bytes. DuckDB runs with an explicit 256 MB memory limit by default so a 512 MB box survives.
Why DuckDB and not SQLite or PostgreSQL?
DuckDB is an analytical database: it is fast at the aggregations analytics is made of (grouping and counting over time ranges), it has first-class JSON, and it writes to a single file with no server to run. SQLite would be fine for many workloads but is weaker at the aggregate queries the statistics endpoints issue. PostgreSQL would need a second process to operate, which is exactly what this design is trying to avoid.
The trade has four axes, and they were measured on the same machine — the same three-year history (6.2 million page views) seeded into both, and the same load for the ingestion.
| DuckDB | SQLite | |
|---|---|---|
| the peak memory under load | 214 MB | 33 MB |
| the data on disk (3 years) | 372 MB | 3 086 MB |
| the summary of the last year | 145 ms | 17 452 ms |
| the summary of the last three years | 186 ms | 24 162 ms |
| the memory a 3-year summary needs | 322 MB | 54 MB |
DuckDB wins the disk and the latency by a wide margin and loses the
memory: its ingestion is the peak, and a long aggregate grows with the
range (73 MB for six months, 322 MB for three years) while SQLite's stays flat
at 54 MB. SQLite buys its small footprint with seconds instead of
milliseconds.
A query that does not fit is slow, not fatal: DuckDB spills to
disk, so the three-year summary answers at every engine limit — at a 64 MB
limit in 691 ms instead of 180 ms. What the limit decides is the speed and
the process's peak, and the peak is the limit plus about 60 MB. So the rule
for sizing a box is the engine's memory_limit plus about
60 MB, and not more than the tier.
The limit has a third cost, and it is the one a small box runs out of: disk. The same three-year history and the same report, at four engine limits:
| the engine's limit | the report | the peak memory | the spill on disk |
|---|---|---|---|
| 512 MB | 133 ms | 571 MB | 0 |
| 256 MB | 187 ms | 344 MB | 27 MB |
| 128 MB | 384 ms | 225 MB | 97 MB |
| 64 MB | 734 ms | 154 MB | 140 MB |
The answer is the same at every limit — the same page views, the same visitors, the same bounces. DuckDB spills to disk and answers anyway, so the limit is a trade: memory for time and disk. At 64 MB the report is 5.5× slower than at 512 MB, and it needs 140 MB of temporary disk to do it.
The spill is a peak, not growth — DuckDB deletes it when the
query ends. But it is disk, and the server's own ceiling on it is
max_temp_directory_size: 2 GB by default
(SUMIMI_DUCKDB_TEMP_LIMIT). On a 10 GB VPS that is a fifth of
the disk in one query — and a spill that cannot fit is a
failed report, not a slow one. Set the ceiling to what the
disk can spare.
Can I use the official Umami dashboard?
Yes — that is the point of the compatible API. Point the Umami dashboard at your Sumimi
host and log in with the admin account. Alternatively, Sumimi serves its own small
dashboard shell at /, loading the frontend from a CDN by default. Set
SUMIMI_APP_URL=/app to have the backend serve the frontend from its own
js/ directory instead — that is what you want on an air-gapped machine.
Does it support multiple websites?
Yes, without any extra configuration. Create one website per site in the dashboard and give each its own tracker snippet. Each website has its own retention window and its own country-header setting.
Where does the dashboard come from?
Two JavaScript files, served in two different ways:
- The tracker (
sumimi.js) — the script you put on your own website — is compiled into the server binary and always served by the server itself at/sumimi.js. It is always available, whatever you configure. - The dashboard frontend (
sumimi-app.jsandsumimi.css) — the pages you log into — is not in the binary. The backend serves a tiny HTML shell at/that points at the frontend's base URL, which defaults tohttps://cdn.jsdelivr.net/gh/sumimi-dev/sumimi@main/js. jsDelivr serves this public GitHub repository directly, so the frontend is always up to date and the server stays small.
On an air-gapped machine, set SUMIMI_APP_URL=/app: the backend then serves
the two frontend files from its own js/ directory instead. They are also
reachable directly at /app/sumimi-app.js and /app/sumimi.css
either way.
Installation
Do I need systemd, or root, to run it?
No. sumimi service start runs the server under a supervisor — a second
process that owns it and restarts it when it dies — with no systemd unit, no init
script and no root. Everything it owns lives inside the instance directory, so it runs
as the user who installed it.
sumimi service start --dir ~/sumimi \
--domain stats.example.com --email you@example.com
sumimi service status --dir ~/sumimi
A systemd unit works just as well — the server does not care which of the two you choose, and the self-hosting guide has one. There is also a container, if you would rather not install anything: the install guide has the command.
Do I need to install DuckDB separately?
No. DuckDB is compiled into the executable, so sumimi is a single
self-contained binary. There is no libduckdb.so to download, no
LD_LIBRARY_PATH to set, no rpath to fix and no ldconfig to
run. Copy the binary onto the machine and run it.
The trade-off is size: a statically linked binary is larger than a dynamically linked
one. If you build from source and prefer the smaller executable, use
make build-small. That build links the prebuilt DuckDB library
dynamically, which means libduckdb.so (or libduckdb.dylib on
macOS) has to sit next to the binary and the loader has to find it — either through the
default search path or via LD_LIBRARY_PATH. The published release archive is
always the self-contained build.
If you do see error while loading shared libraries: libduckdb.so, you are
running the dynamic build, not the release one.
Which binary do I need?
One: sumimi. The server and the analysis CLI are the same program, and the
subcommand decides which half runs.
| I want to… | Command |
|---|---|
| Run the server | sumimi serve --db … --port 3000 |
| Manage the admin account | sumimi admin --username admin |
| Print the resolved configuration | sumimi config |
| Analyse the data from the command line | sumimi summary <website-id>, sumimi pages, sumimi export, … |
| Fill a database with demo traffic | sumimi seed --db ./sumimi.duckdb |
The release archive also ships sumimi-server and sumimi-client
as symlinks to sumimi, for people who prefer the explicit names — the
program switches mode by the name it was invoked under. There is nothing extra to
download. The archive also contains js/ (the tracker and the dashboard
frontend) and docs/ (this documentation site).
How do I try it without a website?
Use sumimi seed. It fills a database with synthetic demo
traffic — invented pages, referrers, countries and browsers, returning visitors, weekend
dips and a few custom events — so you can evaluate the dashboard without pointing a real
site at it.
# the server must be stopped while the database is written
sumimi seed --db ./demo.duckdb --days 30 --visitors 140 \
--website "Example Blog" --domain example.com
sumimi serve --db ./demo.duckdb --port 3000
Open http://127.0.0.1:3000/ and sign in with the admin account. Everything
the dashboard shows is invented, so none of it says anything about a real site — it is
there so you can see what the numbers look like. The
CLI reference has the full list of flags.
Can I move the instance to another machine?
Yes. Stop the service and copy the instance directory — by default
sumimi/, holding data.duckdb (the events),
instance.sqlite (the accounts, the log, the cache) and
jwt-secret — then start the service on the new box. Copying the
jwt-secret file is what keeps the issued tokens working; because the
visitor identifier is derived from the server secret, changing the secret means visitor
identifiers change, which is a privacy feature rather than a bug.
Where did my admin password go?
On the very first start on an empty database, the server creates the admin user. If
SUMIMI_ADMIN_PASSWORD was not set, the generated password is printed to the
log once and never again.
sudo journalctl -u sumimi | grep 'password:'
If you have lost it, set a new one:
sumimi admin --dir /var/lib/sumimi \
--username admin --password 'a-new-long-passphrase'
How do I change the port?
The --port flag or the SUMIMI_PORT environment variable, or
sudo systemctl edit sumimi for a systemd deployment. The default is
3000.
Operating
My page views are not showing up. Where do I start?
Work through these in order:
-
Is the request being made? Open the developer tools on your site, go to
the Network tab and look for
POST /api/send. No request means the tracker script did not load, or the visitor's browser has DNT or GPC enabled and the tracker correctly sent nothing. -
Does it return an error? A
400 unknown website: …response means the website id or domain does not match anything the server knows. -
Is the data committed? Reads only see data that has been flushed, which
happens every
SUMIMI_FLUSH_INTERVAL_SECSseconds — two by default. Wait a moment and reload. - Are you looking at the right window? The statistics endpoints default to the last seven days.
-
Is the proxy in the way? Check the server log for
/api/sendrequests. If there are none, the request is not reaching the server — a Content Security Policy that blocks your analytics domain is the usual culprit.
What does "the 33rd kitty was too cute to count" mean?
It is the log line for the queue overflow handler. If more events arrive than the write
buffer can hold — SUMIMI_MAX_QUEUE, 100000 by default — the server drops the
oldest buffered events instead of growing until the OOM killer arrives.
Statistics do not need to be perfect; the server staying up matters more. The number of
dropped events is in /api/heartbeat as dropped.
If you see this regularly, raise SUMIMI_MAX_QUEUE and the DuckDB memory
limit, or check that the disk is not full.
Can I run two instances against the same database?
Do not. DuckDB takes an exclusive lock on the file. One server writes; the CLI may read
it when the server is stopped. The destructive commands — prune and
erase — work on the file too, so stop the service before running them.
To delete a website while the server is running, call
DELETE /api/websites/:id instead.
Does it need HTTPS?
Not technically, but practically yes for anything public: a browser will not send an
insecure request to an insecure API from an HTTPS page, and you should not run an
analytics endpoint over plain HTTP in public. There are two ways to get it. Put a
reverse proxy in front — Caddy or nginx — and it will obtain and renew a Let's Encrypt
certificate over ACME for you. Or let Sumimi manage the certificate itself with
--ssl example.com: it obtains and renews the certificate over ACME in the
background, with no certbot and no cron job. The self-hosting
guide has the configuration for both.
With a reverse proxy, the proxy terminates TLS and the server sees plain HTTP on
loopback. With --ssl, the server terminates TLS itself and port 443 has to
reach it directly.
How do I get a certificate?
Either from the server itself — the default — or through a reverse proxy.
From the server itself: sumimi service start --domain example.com --email
you@example.com, or sumimi serve --ssl example.com --ssl-email
you@example.com in the foreground. The certificate is issued by Let's Encrypt over
ACME and renewed in the background — no certbot, no renewal cron job. The challenge is
TLS-ALPN-01, so port 443 must reach the Sumimi process directly: no proxy in front of
it. Port 80 does not need to be open. Back up the cache directory (by default an
acme directory next to the database, e.g. /var/lib/sumimi/acme):
it holds the ACME account and the certificates, and losing it means re-issuing.
Through the reverse proxy: Caddy does it with three lines of configuration; nginx does it with certbot. Both renew automatically. You need port 80 reachable from the internet and the domain's DNS record pointing at the server — a DNS record that has not propagated yet is the most common cause of a failed first issuance. If port 80 is closed, use the DNS-01 challenge instead.
How do I back it up?
One directory. Stop the service, copy the instance directory (by default
sumimi/) somewhere safe, start the service again. The
self-hosting guide has a ready-made script and a cron entry.
The directory holds the events (data.duckdb), the accounts and the rest of
the control plane (instance.sqlite), and the signing secret
(jwt-secret) — copy the last one too if you want issued tokens to survive
the move.
It used to be called Sumami. What changed?
Everything except the name. The old tracker URL (/sumami.js), the old
environment variables (SUMAMI_*) and the old crates are all still
there — they just are not what the documentation uses any more.
The name is Sumimi(数咪咪): 数 is count, and 咪咪 is the sound a cat makes. It has nothing to do with Umami — which is also why the comparison tables in this documentation are the honest kind: they compare the numbers, not the names.
How do I move to a new version?
Stop the service, install the new binaries over the old ones, start the service. Schema changes are additive and need no migration step. Take a backup first anyway.
Where are the logs?
In the standard output, which systemd captures:
sudo journalctl -u sumimi -f
Access logs record the method, the path, the status and the duration. Query strings are stripped from logged paths and request bodies are never logged.
Privacy
Does Sumimi store IP addresses?
No. Not in the database, not in the logs, and not hashed into a stable identifier. The server uses a coarse IP prefix to derive a rotating, non-reversible id, and then discards the address. The full details are on the privacy page.
Does the tracker set cookies?
No cookies, and no localStorage either. The collection request is sent with
credentials: 'omit' so no cookie is attached to it. Nothing about a visit
persists in the visitor's browser.
Why is there no country data?
Because Sumimi has no geo-IP database and never looks one up. A country is recorded only
if a proxy in front of the server sets a header containing it — CF-IPCountry,
for example — and you name that header in SUMIMI_COUNTRY_HEADER. Without
that, no geography is recorded at all.
What happens if I put an email address in a custom event?
It is stored verbatim, and your installation becomes a system that processes personal
data. The privacy guarantees do not apply to it any more, and you take on the
obligations that come with that. Use coarse values such as plan: 'free'
instead. The privacy page says more.
Is there any telemetry in Sumimi itself?
No. The server makes no outbound requests of any kind — no analytics, no crash reporting, no update checks, no geo-IP lookups. It never talks to anything but its visitors and you.
The one thing that may leave the browser is the dashboard frontend, which is fetched
from a CDN by default (see the question above). That is your own administrator's browser
loading JavaScript, not your visitors' browsers — the tracker is served by your server
and talks to nothing but your server. If you would rather the dashboard load nothing
from a third party, set SUMIMI_APP_URL=/app and the backend serves the
frontend itself.
The CLI
Can I read the database without running the server?
Yes, that is what the CLI is for. Copy the .duckdb file to your laptop and
run sumimi summary --db ./sumimi.duckdb <website-id>. The file is
opened read-only, and the queries are the ones the API runs — so the numbers are the same
ones the server would have returned.
Why does the CLI need credentials in HTTP mode?
Because in HTTP mode it is a client of the API, and the API is default-deny. It logs in
with POST /api/login and caches the token in
~/.config/sumimi/credentials.json with mode 0600. Pass
--token to skip the cache.
Does the CLI write to my database? Does it modify anything?
Not unless you ask it to. The file is opened read-only, with three exceptions:
prune enforces the retention window, erase deletes a
website's data and seed writes synthetic demo traffic. prune
and erase ask for confirmation. None of them ever touches a database file
that the server currently has open.
Do the CLI and the API return the same numbers?
Yes, and not by coincidence. The server and the CLI call the same query code, so the numbers the CLI prints are the numbers the API returns — by construction, not by convention. The only thing that differs is where the queries run: the CLI's local mode runs them against the file, its HTTP mode asks the server to run them.
Still stuck?
Open an issue at github.com/sumimi-dev/sumimi/issues. If your problem is a privacy or security issue, please describe it privately to the maintainer instead of opening a public issue with a working exploit or a real leaked value in it.
Sumimi is an independent open-source project, an open-source alternative to Umami that implements the Umami v2 HTTP API. Apache-2.0 licensed.