# How to install Mastodon from source on Ubuntu 24.04 or Debian 13

> Install a Mastodon server from source as the official docs describe: Ruby, Node.js, PostgreSQL, Redis, Nginx with Certbot, systemd units, backups and upgrades.

Difficulty: Advanced\
Tested on: Ubuntu 24.04 LTS, Debian 13

Mastodon is a decentralised social network: each server runs its own community and federates with thousands of others over ActivityPub. This guide installs Mastodon **from source**, the way the official documentation describes it, on Ubuntu 24.04 LTS or Debian 13: Node.js and PostgreSQL from their upstream repositories, Ruby through rbenv, Redis, Nginx with a Let's Encrypt certificate from Certbot, and the three systemd services that run the web app, the background workers and the streaming API. You then create the owner account, schedule media cleanup, and set up backups and upgrades.

> **Note**
>
> Mastodon's installation guide runs its commands as **root**. Log in as your sudo user, open a root shell with `sudo -i`, and follow the steps from there. Commands after `su - mastodon` run as the `mastodon` user until the matching `exit`.

## Prerequisites

- A fresh server running **Ubuntu 24.04 LTS** or **Debian 13**. These are the two releases the official guide targets; Ubuntu 26.04 and Debian 12 are not covered.
- A non-root user with `sudo` rights and SSH key login: see [Secure a new Linux server](/guides/secure-a-new-linux-server) and [Set up SSH keys](/guides/ssh-keys).
- A domain such as `social.example.com` with an A record (and AAAA record if you use IPv6) pointing to the server.
- SMTP credentials from an email provider: host, port, user name, password and a sender address.
- Optional: an S3-compatible bucket for media, for example from [Self-hosted S3 storage](/guides/self-hosted-s3-storage).

The Mastodon project does not publish minimum requirements. The figures below are a conservative starting point for a small instance, not official numbers:

| Resource | Minimum (official) | Suggested starting point |
|---|---|---|
| CPU | Not published | 2 vCPU |
| RAM | Not published | 4 GB, because asset compilation and Sidekiq need memory |
| Disk | Not published | 40 GB SSD plus media, or object storage for media |
| Software | Ruby 3.3+, PostgreSQL 14+, Redis 7.0+, Node.js 22+ (Mastodon 4.7) | The versions the steps below install |

> **Warning**
>
> Decide on your domain now. `LOCAL_DOMAIN` (the part after the second @ in handles) and the optional `WEB_DOMAIN` cannot be safely changed after the server has federated, and reinstalling does not fix a wrong choice.

## Step 1 — Update the server and open the firewall

Open a root shell, install updates and allow only SSH and web traffic. The official prerequisites use iptables rules for the same ports; ufw gives the same result with less typing:

```bash
sudo -i
apt update && apt upgrade -y
apt install -y ufw
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ufw status verbose
```

## Step 2 — Add the Node.js and PostgreSQL repositories

Mastodon's guide installs Node.js 24 from NodeSource and PostgreSQL from the PostgreSQL project's own apt repository:

```bash
apt install -y curl wget gnupg lsb-release ca-certificates
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_24.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
wget -O /usr/share/keyrings/postgresql.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc
echo "deb [signed-by=/usr/share/keyrings/postgresql.asc] http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/postgresql.list
```

## Step 3 — Install the system packages and create the mastodon user

Install the build tools, media libraries, Nginx, Node.js, Redis, PostgreSQL and Certbot in one command, enable corepack (which provides Yarn), and create the unprivileged `mastodon` user that will own the code:

```bash
apt update
apt install -y imagemagick ffmpeg libvips-tools libpq-dev libxslt1-dev file git \
  protobuf-compiler pkg-config autoconf bison build-essential \
  libssl-dev libyaml-dev libreadline-dev zlib1g-dev libffi-dev libgdbm-dev \
  nginx nodejs redis-server postgresql certbot python3-certbot-nginx \
  libidn-dev libicu-dev libjemalloc-dev
corepack enable
adduser --disabled-password mastodon
node -v
```

`node -v` should print a v24 version. `adduser` asks for optional user details; press Enter to skip them.

## Step 4 — Create the PostgreSQL user

Mastodon connects to PostgreSQL over the local socket as the Linux user of the same name, so the database user needs no password:

```bash
sudo -u postgres psql
```

```sql
CREATE USER mastodon CREATEDB;
\q
```

For larger instances the guide suggests tuning PostgreSQL with values from pgTune in `/etc/postgresql/18/main/postgresql.conf` and restarting PostgreSQL. A small server runs fine with the defaults.

## Step 5 — Get the code and install Ruby

Switch to the `mastodon` user, clone the repository, check out the latest stable release tag, and install the Ruby version the release asks for with rbenv:

```bash
su - mastodon
git clone https://github.com/mastodon/mastodon.git live && cd live
git checkout $(git tag -l | grep '^v[0-9.]*$' | sort -V | tail -n 1)
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
echo 'eval "$(rbenv init -)"' >> ~/.bashrc
source ~/.bashrc
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
ruby -v
```

`rbenv install` reads the version from the `.ruby-version` file in the repository and compiles Ruby with jemalloc, which takes several minutes. `ruby -v` should print the same version.

## Step 6 — Install the Ruby and JavaScript dependencies

Still as the `mastodon` user, in `~/live`:

```bash
bundle config deployment 'true'
bundle config without 'development test'
bundle install
yarn install
```

The two `bundle config` lines are only needed on the first install. Both installs take a while; errors here usually mean a system package from Step 3 is missing.

## Step 7 — Run the setup wizard

The interactive wizard writes `.env.production`, generates all secrets and encryption keys, creates the database schema, compiles the assets and can create the first admin account:

```bash
RAILS_ENV=production bin/rails mastodon:setup
```

Answer the questions like this:

| Question | Answer for this guide |
|---|---|
| Domain name | `social.example.com`; this becomes `LOCAL_DOMAIN` |
| Single user mode | No, unless the server is only for you |
| Are you using Docker | No |
| PostgreSQL host, port, database, user, password | Accept the defaults: `/var/run/postgresql`, `5432`, `mastodon_production`, `mastodon`, empty password |
| Redis host, port, password | Accept the defaults: `localhost`, `6379`, empty password |
| Store uploaded files on the cloud | No for local storage; Yes to enter your S3-compatible bucket |
| Send e-mails from localhost | No; then enter your SMTP server, for example `smtp.example.com`, port `587`, user name, password and sender address |
| Send a test e-mail | Yes, to check SMTP now |
| Save configuration, prepare the database, compile assets | Yes |
| Create an admin user | Yes; choose a user name and email, then copy the password the wizard prints |

> **Note**
>
> Outbound port 25 is closed by default on HyperDC VPS. For services bought for a term of 3 months or longer, it is opened on request: [open a support ticket](/guides/support-tickets). Until then, send mail through an SMTP relay on port 587.

When the wizard has finished, return to the root shell:

```bash
exit
```

> **Tip**
>
> To use handles such as `@alice@example.com` while Mastodon runs on `social.example.com`, enter `example.com` as the domain name in the wizard, then add `WEB_DOMAIN=social.example.com` to `/home/mastodon/live/.env.production` before Step 9. Use `social.example.com` for the certificate and Nginx, and on the `example.com` website redirect `/.well-known/webfinger` to `https://social.example.com` with a 301 and an `Access-Control-Allow-Origin` header, as Mastodon's configuration docs describe.

## Step 8 — Get a certificate and configure Nginx

Request a certificate with Certbot's Nginx plugin, then install Mastodon's Nginx configuration from the repository:

```bash
certbot certonly --nginx -d social.example.com
cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon
rm /etc/nginx/sites-enabled/default
sed -i 's/example\.com/social.example.com/g' /etc/nginx/sites-available/mastodon
nano /etc/nginx/sites-available/mastodon
```

The `sed` command replaces the placeholder domain in the template with yours; use your real domain in place of `social.example.com`. In the editor, find the two commented certificate lines and remove the leading `#` so they read:

```nginx
ssl_certificate     /etc/letsencrypt/live/social.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/social.example.com/privkey.pem;
```

Let Nginx read the asset files in the `mastodon` home directory, test the configuration and restart Nginx:

```bash
chmod o+x /home/mastodon
nginx -t
systemctl restart nginx
```

Opening `https://social.example.com` now shows Mastodon's error page with the elephant, because the Mastodon processes are not running yet. Certbot's package renews the certificate automatically; `certbot renew --dry-run` tests the renewal.

## Step 9 — Start the Mastodon services

The repository ships systemd units for the web app (Puma), the background workers (Sidekiq) and the streaming API. They assume the `mastodon` user and `/home/mastodon/live`, which this guide uses:

```bash
cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming
systemctl status mastodon-web mastodon-sidekiq mastodon-streaming --no-pager
```

All three should be `active (running)`. Open `https://social.example.com` and log in with the admin account from the wizard, then change its password under **Preferences > Account**.

## Step 10 — Finish the admin setup and schedule media cleanup

If you skipped the admin account in the wizard, create the owner account with `tootctl`. Mastodon's setup guide notes that browser registration is disabled by default, so this is the way to create the first account:

```bash
su - mastodon
cd live
RAILS_ENV=production bin/tootctl accounts create alice --email alice@example.com --confirmed --role Owner
RAILS_ENV=production bin/tootctl accounts modify alice --approve
exit
```

The first command prints a random password. Then go to **Preferences > Administration > Server Settings** and fill in the contact user name, a business email, the server description and the rules or code of conduct. Review the registration mode in the same area before you open sign-ups; requiring approval lets you check each new account.

Mastodon caches media and link previews from other servers, and the cache grows every day. `tootctl media remove` deletes cached remote attachments older than a number of days (7 by default), and `tootctl preview_cards remove` deletes link preview thumbnails (180 days by default). Schedule both as the `mastodon` user:

```bash
su - mastodon
crontab -e
```

```text
PATH=/home/mastodon/.rbenv/shims:/home/mastodon/.rbenv/bin:/usr/local/bin:/usr/bin:/bin
RAILS_ENV=production
15 3 * * * cd /home/mastodon/live && bin/tootctl media remove --days 7
45 3 * * 0 cd /home/mastodon/live && bin/tootctl preview_cards remove --days 180
```

Save, leave the editor and run `exit`. The `PATH` line lets cron find the Ruby version installed with rbenv. `RAILS_ENV=production bin/tootctl media usage` shows how much space media uses.

## Back up and restore

Mastodon's backup guide lists four things, in order of importance: the PostgreSQL database (losing it means losing the whole server), the `.env.production` file with the secrets (losing it logs everyone out and breaks two-factor authentication), user-uploaded files in `public/system` when you use local storage, and the Redis database, which is the least critical. Run these commands in the root shell:

```bash
mkdir -p /opt/backups
chmod 700 /opt/backups
sudo -u mastodon pg_dump -Fc mastodon_production > /opt/backups/mastodon-db-$(date +%F).dump
cp /home/mastodon/live/.env.production /opt/backups/mastodon-env-$(date +%F)
redis-cli SAVE
cp /var/lib/redis/dump.rdb /opt/backups/mastodon-redis-$(date +%F).rdb
tar czf /opt/backups/mastodon-system-$(date +%F).tar.gz --exclude=system/cache -C /home/mastodon/live public/system
ls -lh /opt/backups
```

The `--exclude=system/cache` option skips media cached from other servers, which Mastodon can fetch again. With object storage, back up the bucket with your provider's tools instead of `public/system`. The best backups are off-site, so copy these files to another machine.

To restore on a new server, follow Steps 1 to 6 with the **same version tag**, then Step 8 and the `cp` and `daemon-reload` lines of Step 9 without starting the services. Do not run the setup wizard. Then load the backups as root:

```bash
systemctl stop 'mastodon-*.service'
sudo -u mastodon createdb -T template0 mastodon_production
sudo -u mastodon pg_restore -Fc -U mastodon -n public --no-owner --role=mastodon -d mastodon_production < /opt/backups/mastodon-db-2026-10-09.dump
cp /opt/backups/mastodon-env-2026-10-09 /home/mastodon/live/.env.production
chown mastodon:mastodon /home/mastodon/live/.env.production
tar xzf /opt/backups/mastodon-system-2026-10-09.tar.gz -C /home/mastodon/live
chown -R mastodon:mastodon /home/mastodon/live/public/system
systemctl stop redis-server
cp /opt/backups/mastodon-redis-2026-10-09.rdb /var/lib/redis/dump.rdb
chown redis:redis /var/lib/redis/dump.rdb
systemctl start redis-server
```

Finally rebuild the assets and home feeds, start the services and restart Nginx:

```bash
su - mastodon
cd live
RAILS_ENV=production bundle exec rails assets:precompile
RAILS_ENV=production ./bin/tootctl feeds build
exit
systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming
systemctl restart nginx
```

## Update Mastodon

Every Mastodon release on GitHub has its own upgrade instructions, and the order of steps matters. Mastodon's upgrade guide says you may skip patch releases, but you should deploy at least one release from each minor series and run every instruction at least once. Read the notes for each version between yours and the target, and take a backup first.

The steps below follow the non-Docker instructions of the 4.7 releases. Replace `v4.7.3` with the release you are upgrading to:

```bash
su - mastodon
cd live
git fetch --tags
git checkout v4.7.3
bundle install
yarn install --immutable
RAILS_ENV=production bundle exec rails assets:precompile
SKIP_POST_DEPLOYMENT_MIGRATIONS=true RAILS_ENV=production bundle exec rails db:migrate
exit
```

Restart the processes, then run the remaining post-deployment migrations:

```bash
systemctl restart mastodon-sidekiq
systemctl reload mastodon-web
systemctl restart mastodon-streaming
su - mastodon
cd live
RAILS_ENV=production bundle exec rails db:migrate
exit
```

`systemctl reload mastodon-web` performs a phased restart without downtime; restarting the streaming service disconnects clients briefly. When the release notes ask for a new Ruby version, update ruby-build with `git -C ~/.rbenv/plugins/ruby-build pull` and run `RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install` in `~/live` before `bundle install`.

## Troubleshooting

### The browser shows the error page with the elephant

Nginx works, but Puma does not answer. Check `systemctl status mastodon-web` and `journalctl -u mastodon-web -n 50 --no-pager`. A wrong path or Ruby version in the unit file, or a failed asset compilation, are the usual causes.

### Pages load without styles or images

Nginx cannot read the compiled assets. Run `chmod o+x /home/mastodon` again, and if the files are missing, rebuild them as the `mastodon` user with `RAILS_ENV=production bundle exec rails assets:precompile`.

### Confirmation emails never arrive

Mail is sent by Sidekiq. Read `journalctl -u mastodon-sidekiq -n 100 --no-pager` for SMTP errors, check the `SMTP_` values in `.env.production`, and restart all three Mastodon services after every change to that file. `SMTP_PORT` should be `587`, the relay port from Step 7.

### The disk fills up

Run `RAILS_ENV=production bin/tootctl media usage` as the `mastodon` user to see where the space goes. Make sure the cron jobs from Step 10 run (`grep CRON /var/log/syslog` on Ubuntu, `journalctl -u cron` on Debian), lower `--days` if needed, or move media to object storage.

### bundle install fails after an upgrade with a Ruby version error

The release needs a Ruby version that is not installed yet. Update ruby-build, run `RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install` in `~/live`, then repeat `bundle install` and the remaining upgrade steps.

## Next steps

- Put object storage behind your media with [Self-hosted S3 storage](/guides/self-hosted-s3-storage).
- Learn more about the web server used here in [Nginx with Certbot](/guides/nginx-reverse-proxy-certbot).
- Add a community forum next to your instance with [Discourse](/guides/install-discourse), or a private chat with [Matrix Synapse](/guides/install-matrix-synapse).
- Read the official [Mastodon admin documentation](https://docs.joinmastodon.org/admin/prerequisites/) for Elasticsearch full-text search, scaling and moderation.
- Compare servers for community platforms on the [social network hosting](/social-network-hosting) page.

## Frequently asked questions

### Can I change my Mastodon domain later?

No. Mastodon's configuration docs say LOCAL_DOMAIN and WEB_DOMAIN cannot be safely changed once set: remote servers would treat your accounts as new ones and communication with them can break. Even a reinstall does not fix it, so decide before you run the setup wizard.

### Do I need an email server?

Yes. Mastodon sends confirmation links, password resets and notifications through the SMTP server you configure. Outbound port 25 is closed by default on HyperDC VPS. For services bought for a term of 3 months or longer, it is opened on request through a support ticket. Until then, send mail through an SMTP relay on port 587.

### Why does this guide use Nginx instead of Caddy?

The Mastodon repository ships an Nginx configuration for its web, streaming and static file routes, and the official installation guide uses it with Certbot. Staying with it keeps your server close to the documentation and to the release notes.

### Should I store media in object storage?

It is optional. Local storage keeps uploads and cached remote media in public/system on the server. S3-compatible object storage moves them off the machine; the setup wizard can configure it, and the bucket must support ACLs according to Mastodon's configuration docs.

### Can I run Mastodon with Docker instead?

The official installation guide documents the from-source install on Ubuntu 24.04 or Debian 13 and does not describe a Docker deployment, so this guide follows the source install. Its systemd services, tootctl commands and release notes all assume this layout.

### Which HyperDC servers can run Mastodon?

A HyperDC Linux VPS, VDS or dedicated server with root access running Ubuntu 24.04 LTS or Debian 13. Plan disk space for media, because files cached from other servers keep growing unless you clean them up.

---

Source: <https://hyperdc.com/guides/tutorials/install-mastodon>\
Updated: 2026-10-09
