SSD Nodes Learn 🎉 VPS from $5.50/mo
How to do am Matt ConnorBy Matt Connor · Updated 2026-08-16

Which self-hosted analytics fit run for small VPS?

Compare GoatCounter, Medama, Umami, Plausible, Rybbit, Matomo and GoAccess: 1 GB RAM options, ClickHouse needs, disk growth and ad blocker gaps.

Wich self-hosted web analytics tool you suppose run for VPS?

Self-hosted web analytics dey come for two groups, and if you choose the wrong group, e go cost you pass choosing the wrong product. One group dey run small script for visitor browser and store wetin the script report. The other group dey read the access log wey your web server don already write. Everything wey follow, including the database and memory wey e need, depend on that one choice.

The short answer for small box. GoatCounter and Medama fit run with 1 GB, because each one na single process wey dey use one file. Umami dey add Postgres container and give you dashboard wey person wey no technical fit read. Plausible Community Edition and Rybbit both dey run ClickHouse, so plan for 2 GB of RAM or more. Matomo na the complete product, and e need server wey size match your traffic. GoAccess no add anything to the page, because e dey read log wey don already exist.

Script tag or server log: wetin each one fit see

Script tag dey measure browsers. Page go load, script go run, then e go send one request go your collector. Anything wey break that chain no go visible to you: JavaScript wey dem turn off, filter list wey block the request, failed request go collector, or crawler wey no ever run scripts.

Log parser dey measure requests. Your web server go write one line for every request whether you install anything or not, so the data don already dey disk. E dey see every crawler and every hit on file wey no carry script tag. E no fit see wetin happen inside browser, and e no fit see page wey browser cache or CDN (content delivery network) serve for front of your box, because that request no reach your server.

The two numbers no go match, and neither one wrong. Matomo fit do both, and e document wetin log import give up compared with JavaScript tracker: screen resolution and page titles, events, content tracking, heatmaps, session recordings and form analytics. Na these things you lose when you count requests instead of browsers.

Bot traffic na the other part of the difference. Log-based counts include crawlers unless you filter dem, and for ordinary site, crawler share fit big enough to change your conclusions. GoAccess and Matomo's log import both filter known bots. Neither one fit filter crawler wey dey lie about user agent. Na good reason to pair any log-based counting with blocking AI crawlers for server and read the log after the block, no be before am.

GoAccess: log wey you already get fit give analytics

Install am from the project own Debian and Ubuntu repository, because distribution packages dey behind newer releases.

wget -O - https://deb.goaccess.io/gnugpg.key | gpg --dearmor | sudo tee /usr/share/keyrings/goaccess.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/goaccess.gpg arch=$(dpkg --print-architecture)] https://deb.goaccess.io/ $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/goaccess.list
sudo apt-get update
sudo apt-get install goaccess

Then point am to the log and write a static report.

goaccess /var/log/nginx/access.log -o ~/report.html --log-format=COMBINED

That command go fail with Permission denied for ordinary user, because for Ubuntu, root own the nginx log and group adm dey use am. Add yourself to that group with sudo usermod -aG adm $USER, then log out and log in again, because system dey read group membership when you log in. Run id and check say adm dey inside the list before you try again.

Report wey you generate from the live log only cover wetin logrotate never move yet. Yesterday requests dey inside access.log.1, and older ones don compress, so weekly view need read the rotated files too.

zcat /var/log/nginx/access.log.*.gz | goaccess - --log-format=COMBINED -o ~/last-week.html

Live mode dey available too, na --real-time-html, and e dey update the page through WebSocket. E need second port and separate proxy rule. For most sites, hourly report wey cron write dey enough, and e get fewer things to secure.

GoatCounter: one Go binary and one SQLite file

GoatCounter dey ship as statically compiled binary, so you no need install any runtime. Download one build from the release page and run am, or use the image.

docker run -p 8080:8080 -v goatcounter-data:/home/goatcounter/goatcounter-data arp242/goatcounter

When you run am as binary, goatcounter serve dey listen on port 8080 and create SQLite for ./goatcounter-data/db.sqlite3. Create the first site from command line instead of the web wizard when the instance don already dey behind proxy.

goatcounter db create site -vhost=stats.example.com -user.email=me@example.com

E fit manage e own certificate with goatcounter serve -listen=:443 -tls=tls,rdr,acme, using ACME (automatic certificate management environment). This one useful for server wey no dey run any other thing. If nginx or Caddy already dey control port 443, leave GoatCounter for 8080 and proxy traffic go am instead. The tracking script dey about 3.5K according to the project estimate, and tracking pixel dey available for pages wey no get JavaScript. If SQLite become the limit for busy site, the same binary fit use Postgres with goatcounter serve -db 'postgresql+dbname=goatcounter'. Backup na just copy the file, and na this be the main reason this kind tool dey useful.

Medama: one container wey claim 256 MB

Medama na the newest single binary option for here. E no use cookies by design, and the project talk say tracker dey under 1 KB. E still talk say small sites fit run for virtual machines wey get 256 MB memory. Na claims wey the project publish be these; this guide no measure the numbers.

docker volume create medama-data
docker run -d -p 127.0.0.1:8080:8080 -v medama-data:/app/data ghcr.io/medama-io/medama:latest

The official command publish the port as 8080:8080. The loopback prefix above na intentional, and the reverse proxy section explain why. For first login, use admin with password CHANGE_ME_ON_FIRST_LOGIN. The name of that password na the instruction.

One documented failure mode fit catch you. Login go work only over HTTPS or on localhost. So, if you configure the proxy before the certificate, the form go reject correct password, but e no go print the reason. Finish the TLS (transport layer security) setup first, then log in.

Umami: Postgres, and dashboard wey people recognise

git clone https://github.com/umami-software/umami.git
cd umami
docker compose up -d

That one dey start the application for port 3000, with one PostgreSQL container beside am. The docs specify PostgreSQL v12.14 as the minimum, and Node.js 18.18 or newer if you build from source instead. Prebuilt image dey available, docker.umami.is/umami-software/umami:postgresql-latest, but e need DATABASE_URL to point to database wey you don already run.

The first login na admin with password umami. Change am before you point DNS to the box, because the instance go become reachable from internet as soon as the record resolve and the proxy answer. For Compose details, environment files, and restart policy, see Docker Compose stack for VPS instead of copying stack wey you never read.

The resource footprint na one Node process plus Postgres. E heavier than one binary, but e much lighter than anything wey dey run ClickHouse.

Plausible Community Edition: ClickHouse dey set the minimum RAM

git clone -b v3.2.1 --single-branch https://github.com/plausible/community-edition plausible-ce
cd plausible-ce
touch .env
echo "BASE_URL=https://stats.example.com" >> .env
echo "SECRET_KEY_BASE=$(openssl rand -base64 48)" >> .env
docker compose up -d

Version v3.2.1 na the current one as of August 2026, and na on purpose dem pin am for the clone command. The stack get three parts: the application, Postgres for accounts and settings, and ClickHouse for event data. SECRET_KEY_BASE must be at least a 64 byte string. Na wetin the openssl call dey produce.

Plausible own requirements ask for at least 2 GB of RAM, so ClickHouse and the application no go meet the out of memory killer. E also need CPU wey support SSE 4.2 or NEON, because ClickHouse need am. You suppose check this second requirement before you buy. Na one practical difference be this when you dey choose between an ARM and an x86 VPS. ClickHouse go also use as much memory as e think say e get available. So for shared box, set a ceiling as e dey described for cap container memory in Compose.

BASE_URL must match the public URL exactly. If e no match, you go fit log in, but the app go redirect you to the wrong host. The session cookie go write for domain wey your browser no dey use. So you go return to the login form without any error message.

The compose file wey ship with am no publish any port, because the expectation be say proxy go dey in front. Add an override wey publishes the default application port for loopback only.

cat > compose.override.yml << EOF
services:
    plausible:
        ports:
            - 127.0.0.1:8000:8000
EOF

Matomo: the complete product, and the server wey e need

Matomo dey run on PHP with MySQL or MariaDB, so e follow the classic web stack instead of container stack. Na also the only tool for here wey publishes hardware guidance based on traffic volume.

ChartMatomo sizing guidance by monthly pageviews
The data behind this chart
[
  {
    "label": "100K/month",
    "cpu_cores": 2,
    "ram_gb": 2,
    "disk_gb": 50
  },
  {
    "label": "1M/month",
    "cpu_cores": 4,
    "ram_gb": 8,
    "disk_gb": 250
  },
  {
    "label": "10M/month",
    "cpu_cores": 8,
    "ram_gb": 16,
    "disk_gb": 400
  }
]

Na Matomo published minimums as of August 2026; dem no be measurements wey we take for this guide. Up to 100,000 pageviews per month, e need 2 CPU cores, 2 GB of RAM and 50 GB of SSD, and one server fit hold both the application and the database. For 1M/month, e becomes 8 GB of RAM and 250 GB of disk. For 10M/month, Matomo recommends two servers, and the last row shows the database server: 16 GB of RAM and 400 GB of disk. Compare those disk figures with the single-binary options, where the whole dataset dey inside one SQLite file.

Na archiving dey surprise people. By default, Matomo dey build its reports when person opens the dashboard. As the data dey grow, the dashboard dey slow down and eventually e go timeout. The documented fix na to disable browser-triggered archiving for the general settings and run the archiver from cron instead, as the user wey owns the Matomo files, from the Matomo directory.

php console core:archive --url=https://analytics.example.com

Matomo dey also keep raw log tables beside its processed report tables, and e fit delete old raw data and old reports based on schedule. Turn this on when you install am, no be when the disk don full. Matomo fit import server access logs too, so na the one product for here wey covers both families at once.

Rybbit and di newer stacks

git clone https://github.com/rybbit-io/rybbit.git
cd rybbit
chmod +x *.sh
./setup.sh your.domain.name

Rybbit na recent project wey get modern dashboard. The setup script dey write the environment file and start the stack with Docker Compose. E dey run ClickHouse, and e ship Caddy as im own web server. Caddy go take port 443 and request certificate for the domain wey you pass give am. If nginx already dey use 443 for the server, the script no go fit bind to the port. Use the project manual Compose method instead, then put am behind your existing proxy. The docs talk say e need at least 2 GB of RAM. Dem test am on Ubuntu 24 LTS. For ARM, e need ARMv8.2-A or newer because of ClickHouse.

Make we talk the honest limitation for any young project: features dey land quickly, and breaking changes fit come too. Pin a tag, read the release notes before you pull, and back up the database first.

Disk growth: measure am for your own box

How disk dey grow depend on wetin the tool dey store for each event. GoatCounter dey combine hits into counters, so the file dey grow more with different pages and days than with raw volume. Umami and Matomo dey store rows for each event, and Matomo dey store processed report tables on top the raw ones. ClickHouse dey store events for columns and compress dem well well, na why Plausible fit handle volume wey go put pressure on row store.

This guide no publish any megabytes per million pageviews figure, because e no measure one for your traffic. Take the reading by yourself. Change the service and user names to match your own Compose file.

du -h goatcounter-data/db.sqlite3
docker compose exec db psql -U umami -d umami -c "SELECT pg_size_pretty(pg_database_size('umami'));"
docker compose exec plausible_events_db clickhouse-client -q "SELECT formatReadableSize(sum(bytes_on_disk)) FROM system.parts WHERE active"

Write down the number, wait one week, write am down again, then divide the difference by the pageviews wey dashboard report for that week. That figure na about your site and your bot filtering, so e get more value than any published average. Then set a retention limit while the number still small. Full disk fit bring down every service for the VPS, no be analytics alone. Na this be the strongest reason to keep the database volume for somewhere df -h go warn you about. This risk need more attention for box wey already dey hold something bulky, because self-hosted photo server go exhaust the disk long before any analytics database come near that point.

How e dey behave behind reverse proxy for subdomain

Put the collector for a subdomain of the site wey e dey measure, like stats.example.com. This make the collector request first-party, so browser rules wey dey block third-party requests no go affect am.

Bind the application to loopback when you publish the container port. Docker dey write im own firewall rules before ufw, so container wey you publish as -p 3000:3000 fit dey reachable from internet even when ufw status dey show say the port deny access. Test am from another machine with curl http://SERVER_IP:3000 and you go see the dashboard. If you publish am as -p 127.0.0.1:3000:3000, the same test go return Connection refused, and na only the proxy fit reach am.

server {
    listen 443 ssl;
    server_name stats.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

The forwarding headers no be optional here. Without X-Forwarded-For, every visit go arrive from 127.0.0.1, so country report go empty and unique visitors go reduce toward one. Each project decide which header e trust and under which setting, so check the project proxy documentation once instead of assuming. Caddy set those headers by itself, and Caddyfile for the same work na two lines.

stats.example.com {
    reverse_proxy 127.0.0.1:3000
}

If you never choose proxy yet, the comparison of nginx, Caddy and Traefik explain which one fit single box wey get some subdomains.

Self-hosting dey change who dey hold the data. E no change wetin law talk about the data. Keep these two rules separate. The ePrivacy consent rule dey concern storing or reading anything for visitor device. So, tool wey no set cookie and no write anything to local storage dey outside that specific requirement. GDPR dey concern processing personal data. IP address count as personal data, so you still need lawful basis, retention limit, and answer when person ask wetin you dey hold about dem.

Plausible, Umami, GoatCounter and Medama no set cookie by default. Wetin each one derive instead dey differ by project and fit change between versions. So read the project own privacy documentation, instead of relying on summary. Matomo comes with IP anonymisation and opt out endpoint wey you enable for the admin interface.

Regulators fit reach different conclusions for different countries. For example, France CNIL publishes conditions wey audience measurement fit meet to qualify for exemption from consent. This section na factual summary, no be legal advice. For real site wey get real users, ask lawyer for your jurisdiction.

One point wey people dey miss: access log na personal data too. GoAccess no add script to the page, but e still process IP addresses. So log-based analytics no automatically dey outside the rules.

Ad blockers, and why your numbers go down

Filter lists dey match hostname and URL pattern. Hosted analytics product easy to match because everybody dey load am from the same known hostname. If you move the collector go your own subdomain, that hostname no go dey inside the request again. If you serve the script from path wey you choose, the known filename no go dey there again. Both changes the things wey list need match.

This post no dey claim any hit rate because e no measure one. The number of visitors wey block any particular setup depend on your audience. Developer audience dey block much more than general audience. Measure your own gap instead. For the same week, use GoAccess count requests for HTML pages inside the access log. Compare the result with the pageviews wey your script-based tool reports. The difference na blocked visits plus pages wey cache serve for your site.

Expect the totals to change on the day wey you switch from hosted product. Also expect say part of the change no get anything to do with blocking. Products no dey agree on wetin pageview mean, whether route change inside one single-page application count as one, and when session end. Compare trends across weeks before you conclude say traffic don drop.

Which one fit which site

  • Personal site or blog wey get roughly 50,000 pageviews for month: GoatCounter or Medama, for 1 GB VPS, with backups wey be file copy.
  • Site wey you no fit add script, or audience wey dey block scripts heavily: GoAccess over the existing log, for scheduled run.
  • Small business site wey another person dey read the dashboard: Umami, with im Postgres container.
  • Site wey you want goals and funnels, for machine wey get 2 GB RAM or more: Plausible Community Edition, or Rybbit if you want the newer dashboard and you accept say na younger project.
  • Plenty sites, plenty user accounts, or requirement to keep raw data under your own retention policy: Matomo, sized according to the guidance wey dem publish above.

Start with the smallest tool wey fit answer the real question wey you get. If you later move from GoatCounter to Plausible, na subdomain and some history you go lose. If you move from Matomo to another tool, na migration wey no go easy. If you still dey decide wetin else fit stay for the same machine, the wider self-hosting roundup explain wetin fit stay beside am. But if wetin you really want na request-level tracing for application instead of visitor counts, a self-hosted observability service na the correct tool for that work.

FAQ

No, and the two questions no be the same thing. The consent rule under ePrivacy cover storing or reading anything for visitor device, so tool wey no set cookie and no write anything to local storage no dey under that particular requirement. GDPR na different rule, and e cover processing personal data. IP address na personal data, so you still need lawful basis and retention limit even when no cookie dey. Self-hosting move the data go your server and make you the person responsible for am. Check guidance from your own regulator and ask lawyer about your case.

How much RAM self-hosted analytics need for VPS?

The datastore na wetin decide, no be the dashboard. GoatCounter and Medama dey run as one process over one file, and Medama documentation talk say small sites fit run for machines with 256 MB. Umami add Postgres container beside Node application. Plausible Community Edition and Rybbit both run ClickHouse, and both projects state at least 2 GB. Matomo own guidance start with 2 CPU cores and 2 GB of RAM for up to 100,000 pageviews per month.

Why my self-hosted numbers lower pass the analytics wey I replace?

Two causes dey, and both real. Filter lists block some collector requests, so every script based tool lose those visits. The products also count things differently, because wetin count as pageview and when session end dey vary between dem. Compare one week of HTML page requests from your access log with the same week of script based pageviews. That gap na blocked visits plus cached pages, measured for your own site instead of borrowed from somebody published rate.

I fit run Plausible or Rybbit for ARM VPS?

Both run ClickHouse, and ClickHouse need SSE 4.2 for x86 or NEON for ARM. Plausible requirements talk exactly that, and Rybbit docs say ARM systems need ARMv8.2-A or newer. Current ARM server cores meet that requirement, but older ones no meet am. The failure go show as ClickHouse refusing to start with instruction set error, instead of anything for application log. For small ARM box, the single file tools avoid this issue, because none of dem run ClickHouse.

I suppose parse server logs instead of using tracking script?

Use log parsing when you no fit add script, when your audience dey block scripts heavily, or when you want count wey include crawlers. GoAccess read log wey your server already dey write, so e add no page weight and no database. You lose everything wey happen inside browser, and you miss any page wey CDN or browser cache serve, because that request never reach your server. Plenty sites run both and treat dem as two different measurements.