[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host Mastodon without Docker on your own VPS Run Mastodon v4.7.3 on your own Ubuntu server with ox: Rails, Sidekiq, a Node streaming server, Postgres and Redis, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read On this page - [What you need](https://deploywithox.com/blog/self-host-mastodon#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-mastodon#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-mastodon#config) - [The services and the workers](https://deploywithox.com/blog/self-host-mastodon#services) - [Updates and backups](https://deploywithox.com/blog/self-host-mastodon#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-mastodon#faq) To self-host Mastodon you need an Ubuntu server, a fork of the Mastodon repo with one `ox.toml` file in it, and 14 variables, seven of them required before the first deploy starts. In our run the first deploy took about 9 minutes, and ox set up Postgres and Redis and ran Sidekiq and the streaming server beside the web app. **How we tested** On Oct 17, 2026, ox deployed Mastodon v4.7.3 from commit [`8ff11c2b`](https://github.com/mastodon/mastodon/commit/8ff11c2bb7b9f0714e947b10fa3629c6542c7121) 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, posted a status and saw it at the top of the home timeline after a reload, and checked the app's services and its backup. [Mastodon](https://github.com/mastodon/mastodon) is a free social network server built on ActivityPub, the open standard that lets servers talk to each other, Mastodon or not. People on your server post text, links, pictures and video and follow each other. The code is under the AGPLv3. It is a Ruby on Rails app with PostgreSQL as its main database, Redis and Sidekiq for caching and queues, a separate Node.js server for live updates, and a React interface. ox deployed Mastodon v4.7.3 (commit 8ff11c2b) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 9 minutes and a redeploy took 4 minutes. - **First deploy:** 9 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 4 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:** 2794 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:** `postgres`, `redis`. - **Background processes:** `sidekiq`, `streaming`, each its own systemd unit beside the web process. - **Source:** [github.com/mastodon/mastodon](https://github.com/mastodon/mastodon) at v4.7.3, commit [`8ff11c2b`](https://github.com/mastodon/mastodon/commit/8ff11c2bb7b9f0714e947b10fa3629c6542c7121). ## What you need - An Ubuntu LTS server. The run used the size in the facts above. The repo pins Ruby 4.0.6 in `.ruby-version` and Node 24.19 in `.nvmrc`, and ox installs those toolchains and sets up the two services. - A GitHub account to fork `mastodon/mastodon`, and an ox account, which you open by signing in with it. - A username, an email address and a password for the first account, the owner. A new Mastodon server has sign-ups closed. - A mail server, if you want real email. The run used a test mail catcher. Not tested: real email, federation with other servers, media uploads, search, the mobile apps, a custom domain and a restore. ## The steps In the ox dashboard, press Add a server. It shows one install command with a token that works once. Run it on the server as root. It installs only the ox agent and Caddy. Fork `mastodon/mastodon` on GitHub and take the tag `v4.7.3`, the release the run deployed. Put the `ox.toml` shown below at the root of your fork, commit it and push. The run added no other file. Next write a file named `mastodon.env` on your laptop with one line for each key in the variables list below, and set `LOCAL_DOMAIN` to `${PUBLIC_HOST}`, as the run did. Mastodon's sample file says to make `SECRET_KEY_BASE` with `bundle exec rails secret` and the three encryption values with `bin/rails db:encryption:init`. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /mastodon ox review mastodon --from-file mastodon.env --wait ``` The project takes the repo's name. The review saves the values and starts the first deploy, and `--wait` prints the run until it ends. In the browser the same two steps are New project and the review screen, as the [quickstart](https://deploywithox.com/docs/quickstart) shows. When the deploy is green, open the https address ox shows for the project and go to `/auth/sign_in`. Sign in with `OWNER_EMAIL` and `OWNER_PASSWORD`. On Home, type in the box that asks what is on your mind and press Post. The first page loads were slow while the server warmed up. The run served Mastodon 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 # Mastodon v4.7.3 (Rails 8.1, PostgreSQL, Redis, Sidekiq, a Node streaming # server, Vite), as its operator would write it: ox detects Ruby, Yarn, the # asset build, db:prepare, Puma and both services. # No domain of its own: Mastodon forces https and marks its session cookie # secure, and ox serves the preview address on https (its certificate is # ordered while the long build runs), so sign-in works there. # The server's name, which Mastodon says cannot change once it federates # (.env.production.sample), the keys it aborts without # (config/initializers/active_record_encryption.rb), and the owner, the only # way in: a new server's registrations are closed. required_variables = [ "LOCAL_DOMAIN", "ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY", "ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT", "ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY", "OWNER_USERNAME", "OWNER_EMAIL", "OWNER_PASSWORD", ] # idn-ruby and charlock_holmes build against these; ffmpeg makes the video # thumbnails. packages = ["libidn-dev", "libicu-dev", "ffmpeg"] [app] health = "/health" [build] # db:prepare, then the owner, made as the admin guide's `tootctl accounts # create NAME --email E --confirmed --role Owner` and `tootctl accounts # approve NAME` make it (lib/mastodon/cli/accounts.rb), but with the # password from OWNER_PASSWORD: tootctl makes up its own and prints it. # Approval matters: a closed server leaves a new user pending, and a pending # user cannot use the web app. Once: the runner prints which it did, and any # error stops the deploy. migrate = """\ RAILS_ENV=production bundle exec rails db:prepare && \ RAILS_ENV=production bundle exec rails runner '\ name = ENV.fetch("OWNER_USERNAME"); \ if Account.find_local(name)&.user then puts "Mastodon: the owner #{name} exists"; \ else user = User.new(email: ENV.fetch("OWNER_EMAIL"), password: ENV.fetch("OWNER_PASSWORD"), \ agreement: true, role: UserRole.find_by!(name: "Owner"), bypass_registration_checks: true, \ account: Account.new(username: name)); \ user.save!; user.mark_email_as_confirmed!; user.approve!; \ puts "Mastodon: made the owner #{name}"; end'""" [services] postgres = {} redis = {} [workers] # Sidekiq runs the federation, mail and media jobs. sidekiq = "RAILS_ENV=production bundle exec sidekiq" # The streaming server is a separate Node process on its own port (ox gives # each copy its own, so staging does not meet production's); the browser # reaches it at /api/v1/streaming on the site's own name. streaming = { run = "NODE_ENV=production BIND=127.0.0.1 node ./streaming", port = true, paths = ["/api/v1/streaming"] } [storage] # Media is kept under public/system. keep = ["public/system"] ``` - `required_variables` lists seven keys. `LOCAL_DOMAIN` is the server's name, which Mastodon's sample file says cannot be changed safely later. Mastodon's initializer aborts without the three `ACTIVE_RECORD_ENCRYPTION` keys. The three `OWNER` keys are the only way in while sign-ups are closed. A missing key stops the deploy before the long build. - `packages` adds three apt packages. The `idn-ruby` and `charlock_holmes` gems build against `libidn-dev` and `libicu-dev`, and `ffmpeg` makes thumbnails of uploaded video. - `health = "/health"` is Mastodon's own health route. ox moves traffic to a new release only once it answers. - There is no install, build or start line. ox reads Ruby from `.ruby-version`, installs the gems and Node packages, runs the asset build, and starts `rails server` on the port it gives the app. Mastodon serves the compiled files in `public/` only when `RAILS_SERVE_STATIC_FILES` is `true`, and ox's Rails start sets it for you. - There is no `domains` line. Mastodon forces https and marks its session cookie secure, so sign-in over plain http fails. ox serves the preview address on https and orders its certificate while the long build runs. - `migrate` runs `db:prepare`, then makes the owner as `tootctl accounts create` (with `--confirmed --role Owner`) and `tootctl accounts approve` do, but with the password from `OWNER_PASSWORD`, where tootctl makes one up. The account also needs approving: on a closed server a new account waits as pending and cannot use the web app. The step prints whether it made the owner or found one, so it is safe on every deploy, and any error stops the deploy. - `[services]` asks for Postgres and Redis. Redis runs as a unit only for this project. - `sidekiq` in `[workers]` runs the background jobs: federation, mail and media. `streaming` is Mastodon's Node server. `port = true` gives each copy a port of its own, so staging never meets production's. `BIND=127.0.0.1` keeps it on the loopback address, and `paths` sends `/api/v1/streaming`, websockets included, to it on the site's own name. - `[storage]` keeps `public/system` across deploys. Mastodon writes uploaded media there. Shell ```bash LOCAL_DOMAIN SECRET_KEY_BASE ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY ES_ENABLED S3_ENABLED OWNER_USERNAME OWNER_EMAIL OWNER_PASSWORD SMTP_SERVER SMTP_PORT SMTP_AUTH_METHOD SMTP_FROM_ADDRESS ``` None of the `DB_*` or `REDIS_*` keys from Mastodon's sample file is in this list, because ox answers them from the two services. `LOCAL_DOMAIN` is a `${PUBLIC_HOST}` reference, which ox fills in with the host name of the project's address. `SECRET_KEY_BASE` is the Rails secret that signs sessions. The sample file turns `ES_ENABLED` and `S3_ENABLED` on, and the run set both to false: it had no Elasticsearch and no S3 bucket. `OWNER_USERNAME`, `OWNER_EMAIL` and `OWNER_PASSWORD` are the first account. Mastodon blocks the usernames `admin` and `owner`, so the run used `siteowner`. It also checks that the email's domain has a mail server or an address record, so the run used an address at `${PUBLIC_HOST}`. The four `SMTP` keys send mail to the test catcher, which takes it with no login, so the run set `SMTP_AUTH_METHOD` to `none`. See [variables](https://deploywithox.com/docs/operate/variables) for how references work. ## The services and the workers Postgres holds accounts and posts, and Redis holds the cache and the job queues. The test agent posted a status, and Sidekiq logged its distribution job done right after. The streaming server logged a WebSocket upgrade and a started stream for the owner's timeline, which is how a new post reaches an open page. The run also deployed a staging copy beside production, and the memory figure above counts both. ## Updates and backups To move to a new release, merge its tag into your fork and push, or run `ox deploy mastodon --wait`. ox builds the new release beside the running one, runs `db:prepare`, and switches once `/health` answers. Before each migrate it dumps Postgres, and the run's redeploy left that dump. Read `CHANGELOG.md` in the repo before a major version. Backups need no setup. The run's backup folder held three Postgres dumps (a daily one, one from Back up now and one from before the migrate) and a daily Redis archive. Run `ox backups mastodon` to list them, or `ox backups mastodon now postgres --wait` to take one. A restore was not tested, and neither was a backup of uploaded media. 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 Mastodon? No. Mastodon's repo ships Docker files, and its README also points to a standalone install guide. With ox, the Rails app and the Node streaming server are built on your server and run as system services, with Postgres and Redis beside them. There is no image to pull, and an update is a push to your fork. ### How do I make the first Mastodon account? Set OWNER_USERNAME, OWNER_EMAIL and OWNER_PASSWORD before the first deploy. The migrate step makes that account, confirmed, approved and with the Owner role, and you sign in at /auth/sign_in. Sign-ups stay closed on a new server. Inviting people or opening them was not tested. ### Do I have to set the ACTIVE_RECORD_ENCRYPTION keys? Yes. Mastodon stops at boot when any of the three is empty, and ox lists them as required so a deploy stops before the build. Mastodon's sample file says not to change them once in use, since that can cause data loss. Make them with bin/rails db:encryption:init and keep a copy. ### How much memory does self-hosted Mastodon use? On the run's server, the whole machine used the memory the facts show after a reboot. That counts Mastodon, its staging copy, Postgres, Redis, Caddy and the ox agent. ox takes any server from 1 GB up, and says so when memory is why the app stopped. 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-mastodon#what-you-need) - [The steps](https://deploywithox.com/blog/self-host-mastodon#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-mastodon#config) - [The services and the workers](https://deploywithox.com/blog/self-host-mastodon#services) - [Updates and backups](https://deploywithox.com/blog/self-host-mastodon#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-mastodon#faq) - [Do I need Docker to self-host Mastodon?](https://deploywithox.com/blog/self-host-mastodon#faq-docker) - [How do I make the first Mastodon account?](https://deploywithox.com/blog/self-host-mastodon#faq-owner) - [Do I have to set the ACTIVE_RECORD_ENCRYPTION keys?](https://deploywithox.com/blog/self-host-mastodon#faq-keys) - [How much memory does self-hosted Mastodon use?](https://deploywithox.com/blog/self-host-mastodon#faq-memory) 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 Mautic on your own VPS without Docker](https://deploywithox.com/blog/self-host-mautic) [Newer post How to self-host Logto on your own VPS without Docker](https://deploywithox.com/blog/self-host-logto) ## 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)