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.

DuckDBSQLite
the peak memory under load214 MB33 MB
the data on disk (3 years)372 MB3 086 MB
the summary of the last year145 ms17 452 ms
the summary of the last three years186 ms24 162 ms
the memory a 3-year summary needs322 MB54 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 limitthe reportthe peak memorythe spill on disk
512 MB133 ms571 MB0
256 MB187 ms344 MB27 MB
128 MB384 ms225 MB97 MB
64 MB734 ms154 MB140 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.

The limit trades memory for time and disk

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.js and sumimi.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 to https://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:

  1. 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.
  2. Does it return an error? A 400 unknown website: … response means the website id or domain does not match anything the server knows.
  3. Is the data committed? Reads only see data that has been flushed, which happens every SUMIMI_FLUSH_INTERVAL_SECS seconds — two by default. Wait a moment and reload.
  4. Are you looking at the right window? The statistics endpoints default to the last seven days.
  5. Is the proxy in the way? Check the server log for /api/send requests. 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.