[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Plausible Analytics on your own VPS Run Plausible Community Edition v3.2.1 on your own Ubuntu server with ox: Postgres, ClickHouse and an Elixir build, from the config that ran. Saurav Sharma Oct 17, 2026 9 min read On this page - [What you need](https://deploywithox.com/blog/self-host-plausible#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-plausible#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-plausible#config) - [Postgres and ClickHouse](https://deploywithox.com/blog/self-host-plausible#services) - [Updates and backups](https://deploywithox.com/blog/self-host-plausible#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-plausible#faq) To self-host Plausible Analytics you need an Ubuntu server, a fork of the Plausible repo with one `ox.toml` file in it, and five variables. In our run the first deploy took about 14 minutes, most of it the Elixir and asset build, and ox set up Postgres and ClickHouse for it. **How we tested** On Oct 17, 2026, ox deployed Plausible Analytics v3.2.1 from commit [`e4f5a87a`](https://github.com/plausible/analytics/commit/e4f5a87a5570bd61605217e7ceb376636db8eddb) on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, added a website, sent it one pageview, and saw the visit on its dashboard after a reload, and checked the app's services and its backup. [Plausible Analytics](https://github.com/plausible/analytics) counts the visitors, pages and sources of your websites without cookies. The company behind it sells a hosted service, and the same code is open source as Plausible Community Edition (CE), under the AGPL. It is an Elixir and Phoenix app. Accounts and site settings live in Postgres, and every pageview goes to ClickHouse, a database made for counting many events fast. ox deployed Plausible Analytics v3.2.1 (commit e4f5a87a) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 14 minutes and a redeploy took 9 minutes. - **First deploy:** 14 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 9 minutes, until the app passed the checks again. - **Server:** DigitalOcean, 8 vCPUs and 16 GB of memory (`s-8vcpu-16gb`), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - **Memory in use after the reboot:** 1888 MB of 15986 MB, for the whole server: Ubuntu, the app and its staging copy, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less. - **Services ox runs for it:** `clickhouse`, `postgres`. - **Source:** [github.com/plausible/analytics](https://github.com/plausible/analytics) at v3.2.1, commit [`e4f5a87a`](https://github.com/plausible/analytics/commit/e4f5a87a5570bd61605217e7ceb376636db8eddb). The Plausible Community Edition log-in page The Plausible dashboard for probe.example.org counting one visitor Top sources and top pages with the recorded visit to /hello ## What you need - An Ubuntu LTS server. The run used a DigitalOcean server with 8 vCPUs and 16 GB of memory, the one size every app we deployed ran on, and the facts above show what it measured there. Plausible's [community edition notes](https://github.com/plausible/community-edition) say ClickHouse needs a CPU with SSE 4.2 or NEON. - A GitHub account to fork `plausible/analytics`, and an ox account, which you open by signing in with GitHub. - An email address and a password for the first admin. Plausible sends invites and reports by email, but email was not tested. ## The steps In the ox dashboard, press Add a server. It shows one install command with a fresh token in it. Run that on the server as root. It installs only the ox agent and Caddy. Fork `plausible/analytics` on GitHub and check out the tag `v3.2.1`, the release the run deployed. Add the `ox.toml` and the `create_admin.exs` file shown below, at the paths shown, then commit and push. Next write a file named `plausible.env` on your laptop with the five values from the variables section. Plausible's docs make `SECRET_KEY_BASE` with `openssl rand -base64 48`, and the run used a random value of that kind. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /analytics ox review analytics --from-file plausible.env --wait ``` ox names the project after the repo, so it is `analytics` here. The review saves the values and starts the first deploy, and `--wait` prints the run until it ends. The same two steps in the browser are New project and the review screen, as the [quickstart](https://deploywithox.com/docs/quickstart) shows. When the deploy is green, open the address ox shows for the project and sign in with `ADMIN_EMAIL` and `ADMIN_PASSWORD`. Press Add a website, type your site's domain, and Plausible shows the script tag to paste into your site. The run served Plausible on that preview address only. A domain of your own was not part of it, and the [domains page](https://deploywithox.com/docs/operate/domains) covers how ox adds one. ## The ox.toml, line by line TOML ```toml # Plausible Community Edition v3.2.1 (Phoenix, postgres, ClickHouse), built # as its Dockerfile builds it. The Community Edition is the "ce" build # (MIX_ENV=ce), not the "prod" one Plausible's own cloud runs, so every mix # command and the release folder name it. Oban jobs and cron run inside the # web process, so there are no workers. # runtime.exs raises without BASE_URL and SECRET_KEY_BASE, and its # CLICKHOUSE_DATABASE_URL default is a docker host name. The Community # Edition closes sign-up once a user exists (DISABLE_REGISTRATION defaults to # invite_only), so the first admin comes from ADMIN_EMAIL and ADMIN_PASSWORD. required_variables = ["BASE_URL", "SECRET_KEY_BASE", "CLICKHOUSE_DATABASE_URL", "ADMIN_EMAIL", "ADMIN_PASSWORD"] [build] install = "mix local.hex --force && mix local.rebar --force && MIX_ENV=ce mix deps.get --only ce" commands = [ "MIX_ENV=ce mix deps.compile", "npm ci --include=dev --prefix assets", "npm ci --include=dev --prefix tracker", "NODE_ENV=production npm run deploy --prefix tracker", # the tracking script sites embed "MIX_ENV=ce NODE_ENV=production mix assets.deploy", # tailwind, esbuild, phx.digest "MIX_ENV=ce mix download_country_database", # the free IP to country database "MIX_ENV=ce mix sentry.package_source_code", "MIX_ENV=ce mix release plausible", ] # The image's migrate.sh (both databases in one ordered pass; ox made both # databases, so its createdb.sh has nothing to do), then the first admin # from ADMIN_EMAIL, ADMIN_NAME and ADMIN_PASSWORD (rel/overlays/ # create_admin.exs prints whether it made it or found it, and stops the # deploy on any other answer). migrate = "_build/ce/rel/plausible/migrate.sh && _build/ce/rel/plausible/bin/plausible eval 'Code.eval_file(Path.join(System.fetch_env!(\"RELEASE_ROOT\"), \"create_admin.exs\"))'" [app] # DEFAULT_DATA_DIR is the image's (its VOLUME): CSV imports and exports. start = "DEFAULT_DATA_DIR=\"$PWD/data\" PHX_SERVER=true RELEASE_DISTRIBUTION=none exec _build/ce/rel/plausible/bin/plausible start" health = "/api/system/health/live" [services] postgres = {} clickhouse = {} [storage] keep = ["data"] ``` Plausible builds two editions from one codebase. Its Dockerfile sets `MIX_ENV=ce` for the Community Edition, and the config does the same on every mix step, so the release lands in `_build/ce/rel/plausible`. The other edition is the one Plausible's paid cloud runs. - `required_variables` lists the five keys the app or the first admin cannot do without. Plausible's `runtime.exs` raises without `BASE_URL` and `SECRET_KEY_BASE`, and its ClickHouse default points at a Docker host name. A missing key stops the deploy in its first seconds, before the long build. - `install` fetches Hex and Rebar, then only the dependencies the CE edition uses. - The build `commands` follow the Dockerfile in order: compile the dependencies, install the npm packages for the dashboard and for the tracker, build the tracking script your sites embed, bundle and digest the assets, download the free IP to country database, package the source for error reports, and build the release. - `migrate` runs the release's own `migrate.sh`, which migrates Postgres and ClickHouse in one pass. Then it runs `create_admin.exs` to make the first admin. - `start` runs the release in the foreground. `PHX_SERVER=true` tells Phoenix to serve HTTP on the port ox gives it, `RELEASE_DISTRIBUTION=none` turns off Erlang clustering, and `DEFAULT_DATA_DIR` is the folder for CSV imports and exports. - `health` is Plausible's own liveness path. ox switches traffic to a new release only once it answers. - `[services]` asks for Postgres and ClickHouse, and `[storage]` keeps the `data` folder across deploys. There are no workers. Plausible runs its Oban jobs and its cron inside the web process, and the test agent saw them working in that process's log. `rel/overlays/create_admin.exs` Code ``` # The first admin, from ADMIN_EMAIL, ADMIN_NAME (default "Admin") and # ADMIN_PASSWORD, made the way the register page makes a user # (Plausible.Auth.User.new, then Repo.insert). Safe on every deploy: it prints # whether it made the admin or found it, and any other answer (no database, # a password the app refuses) raises, which stops the deploy. # # bin/plausible eval 'Code.eval_file(Path.join(System.fetch_env!("RELEASE_ROOT"), "create_admin.exs"))' :ok = Application.ensure_loaded(:plausible) Enum.each([:ssl, :postgrex, :ch, :ecto], &Application.ensure_all_started/1) {:ok, _} = Plausible.Repo.start_link(pool_size: 2) email = System.fetch_env!("ADMIN_EMAIL") name = System.get_env("ADMIN_NAME", "Admin") password = System.fetch_env!("ADMIN_PASSWORD") if Plausible.Repo.get_by(Plausible.Auth.User, email: email) do IO.puts("admin #{email} exists") else changeset = Plausible.Auth.User.new(%{ "email" => email, "name" => name, "password" => password, "password_confirmation" => password }) case Plausible.Repo.insert(changeset) do {:ok, user} -> IO.puts("admin #{user.email} created") {:error, changeset} -> errors = Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) raise "the admin #{email} was not created: #{inspect(errors)}" end end ``` CE closes sign-up as soon as one user exists: `DISABLE_REGISTRATION` defaults to `invite_only`. So the first account cannot wait for a visitor. This file makes it from the variables, the same way Plausible's register page makes a user. It prints whether it made the admin or found one, so it is safe on every deploy, and any other answer stops the deploy. Shell ```bash BASE_URL SECRET_KEY_BASE CLICKHOUSE_DATABASE_URL ADMIN_EMAIL ADMIN_NAME ADMIN_PASSWORD SMTP_HOST_ADDR SMTP_HOST_PORT ``` `BASE_URL` is `${PUBLIC_URL}`, a reference ox fills in with the project's address, so it follows the address when it changes. `CLICKHOUSE_DATABASE_URL` is `${CLICKHOUSE_URL}`, the address of the ClickHouse service ox set up. `SECRET_KEY_BASE` signs sessions, and Plausible derives its two-factor key from it unless `TOTP_VAULT_KEY` is set. `ADMIN_EMAIL`, `ADMIN_NAME` and `ADMIN_PASSWORD` are the first admin. The run pointed `SMTP_HOST_ADDR` and `SMTP_HOST_PORT` at a test mail catcher, so it sent no real mail. See [variables](https://deploywithox.com/docs/operate/variables) for how references work. ## Postgres and ClickHouse Postgres is shared with other apps on the server, each with its own database and user. ClickHouse runs only for this project. The run also deployed a staging copy beside production, and the memory figure above counts both. You can look inside each database from the Services tab, or with `ox explore analytics postgres`. Plausible drops events sent with a bot or tool User-Agent, so a pageview sent with curl's own User-Agent never shows. The test agent sent its pageview with a browser's, and the dashboard counted it. Events reach ClickHouse in small batches, so a visit takes a few seconds to appear. ## Updates and backups To move to a new release, merge its tag into your fork and push, or run `ox deploy analytics --wait`. ox builds the new release beside the running one, runs the migrations, and switches only when the new one answers. Before each migrate it dumps Postgres, and the run's redeploy left that dump. Read Plausible's changelog before a major version. Backups need no setup. The run's backups held a Postgres dump, a ClickHouse archive and the `data` folder, both daily and from Back up now. Run `ox backups analytics` to list them. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers keeping copies in an S3 bucket. ## Frequently asked questions ### Do I need Docker to self-host Plausible? No. Plausible's own setup uses Docker Compose, but the app is a normal Elixir release. ox builds it from your fork on the server and runs it as a system service, with Postgres and ClickHouse installed beside it. There is no image to pull, and an update is a push to your fork or one deploy command. ### How do I make the first Plausible account? Set ADMIN_EMAIL and ADMIN_PASSWORD before the first deploy. The migrate step runs create_admin.exs, which makes that user, and you sign in at /login. Community Edition then keeps sign-up closed and lets new people in by invite only. Inviting more people needs email, which was not tested. ### How much memory does self-hosted Plausible use? After a reboot, the run's whole server used 1888 MB of its 16 GB. That counts Ubuntu, Plausible and its staging copy, Postgres, ClickHouse, Caddy and the ox agent. The build compiles Elixir and runs npm on the server too. ox takes any server from 1 GB up, and says so when memory is why the app stopped. ### Can I change SECRET_KEY_BASE later? Changing it signs every user out, and Plausible derives its two-factor key from it unless TOTP_VAULT_KEY is set, so codes made with the old key stop working. Set it once before the first deploy and keep it. Our run never changed it, so a rotation of the key was not tested. New to running apps on your own server? [How ox self-hosts your apps](https://deploywithox.com/self-hosting) shows what ox sets up on your server. On this page - [What you need](https://deploywithox.com/blog/self-host-plausible#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-plausible#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-plausible#config) - [Postgres and ClickHouse](https://deploywithox.com/blog/self-host-plausible#services) - [Updates and backups](https://deploywithox.com/blog/self-host-plausible#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-plausible#faq) - [Do I need Docker to self-host Plausible?](https://deploywithox.com/blog/self-host-plausible#faq-docker) - [How do I make the first Plausible account?](https://deploywithox.com/blog/self-host-plausible#faq-signup) - [How much memory does self-hosted Plausible use?](https://deploywithox.com/blog/self-host-plausible#faq-memory) - [Can I change SECRET_KEY_BASE later?](https://deploywithox.com/blog/self-host-plausible#faq-secret) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post How to self-host Pleroma without Docker on your own VPS](https://deploywithox.com/blog/self-host-pleroma) [Newer post How to self-host Pixelfed without Docker on your own VPS](https://deploywithox.com/blog/self-host-pixelfed) ## Keep reading - Guides [Self-Hosted Open-Source Apps Deployed with ox: All 85](https://deploywithox.com/blog/self-hosted-open-source-apps-deployed-with-ox) ox deployed 85 open-source apps from pinned commits on fresh servers, with no Docker. The stacks, services, deploy times and a table of every app. Saurav Sharma Oct 17, 2026 16 min read - Guides [How to self-host AFFiNE on your own VPS without Docker](https://deploywithox.com/blog/self-host-affine) Run AFFiNE v0.27.4 on your own Ubuntu server with ox: Postgres with pgvector, Redis, a Rust and Node build, and an admin made from two variables. Saurav Sharma Oct 17, 2026 12 min read - Guides [How to self-host Akaunting on your own VPS without Docker](https://deploywithox.com/blog/self-host-akaunting) Run Akaunting 3.2.4 on your own Ubuntu server with ox: PHP, MariaDB, a scheduler and the first company and admin made on deploy, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://deploywithox.com/login/github) [Read the docs](https://deploywithox.com/docs)