> ## Content Index
> Fetch the complete content index at: https://blog.gelbphoenix.de/llms.txt
> Use this file to discover other available public pages before exploring further.

# Admin’s Guide to Self-Hosting a Mastodon Instance
- URL: https://blog.gelbphoenix.de/admins-guide-to-self-hosting-a-mastodon-instance/
- Published: 2025-04-29T10:00:52.000Z
- Updated: 2026-07-29T18:44:08.000Z
- Author: Phoenix Paulina Schmid
- Tags: WtF?! - What the Fediverse?, Selfhosting

In this guide, you’ll learn how to deploy an own [**Mastodon**](https://blog.gelbphoenix.de/beginners-guide-to-mastodon/) instance with **Docker Compose**, set up **Caddy** as a reverse proxy for automatic HTTPS and configure regular cleanup jobs. Finally, we cover routine maintenance tasks (migrations, asset pre-compilation) and security hardening to keep your [**Fediverse**](https://blog.gelbphoenix.de/fedi-what/) node running smoothly and securely.

## **Prerequisites*

### Server Requirements

- **VPS**: ≥ 4 CPU cores, ≥ 50 GB disk (e.g. [Hetzner CX32](https://www.hetzner.com/de/cloud/?ref=blog.gelbphoenix.de))
- **OS**: Ubuntu 24.04 LTS or Debian 12 (64-bit)
- **Software**: Docker (v27.3.1+), Docker Compose (v2.29.7+), Git
- **Domain**: A DNS A and AAAA record pointing to your server’s IP
- **SMTP**: Access to an SMTP server for outgoing mail (e.g. Mailgun, SendGrid)

### Optional but Recommended

- A non-root system user (e.g. `mastodon`) with Docker group privileges
- Firewall configured (e.g. `ufw` allowing 80, 443, 22)
- Basic swap space for asset compilation

## **Step 1*

Clone the Boilerplate

We’ll leverage a ready-made Mastodon Docker setup to save time:

```
sudo git clone https://git.gelbphoenix.de/gelbphoenix/boilerplate.git
sudo mv boilerplate/mastodon/ /opt/
```

This repository includes a `compose.yml`, an `auto-cleanup.sh` script, and a Caddy site definition. 

> After moving the `mastodon` folder you can delete the rest of the boilerplate folder.

## Step 2

Initial Configuration

### 2.1 Pull the images and preparations

Before we can create the needed configuration variables in the next step do we have to pull the needed images (while being in `/opt/mastodon`).

```
cd /opt/mastodon
docker compose pull
```

After we have pulled the needed images do we also have to create the `public/` folder and change the ownership of that folder to the user and group ID of 991.

```
mkdir public
chown -R 991:991 public/*
```

If you also want to have the possibility to customize the `robots.txt` then move the included file into the just created folder.

### 2.2 Create `.env.production`

Run Mastodon’s setup task to generate environment defaults interactively:

`docker compose run --rm console rake mastodon:setup`

Copy the output into `.env.production` in `/opt/mastodon`.  
Mastodon reads these variables at runtime to configure DB, Redis, federation, SMTP, etc.

### 2.3 Optional Elasticsearch

To enable full-text search, uncomment the `es` service in `compose.yml`, then add to `.env.production`:

```
ES_ENABLED=true
ES_HOST=es
ES_PORT=9200
```

This boots `db`, `redis`, `web`, `streaming`, `sidekiq`, and (optionally) `es` [GitHub](https://git.gelbphoenix.de/gelbphoenix/boilerplate/src/commit/main/mastodon/compose.yml?ref=blog.gelbphoenix.de).  
Check logs with `docker compose logs -f web` and confirm `health` endpoints return OK.

## Step 3

Configure Caddy for HTTPS & Reverse Proxy

### 3.1 Caddyfile

Place this in `/etc/caddy/Caddyfile` (or under `sites-available`/`sites-enabled`):

```
[YOUR MASTODON DOMAIN] {
        root * /opt/mastodon/public

        encode gzip

        @static file

        handle @static {
                file_server
        }

        handle /api/v1/streaming* {
                reverse_proxy localhost:4000
        }

        handle {
                reverse_proxy localhost:3000
        }

        header {
                -Server
                Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
                X-XSS-Protection "1; mode=block"
                X-Robots-Tag "noindex,nofollow"
        }

        header /sw.js  Cache-Control "public, max-age=0";
        header /emoji* Cache-Control "public, max-age=31536000, immutable"
        header /packs* Cache-Control "public, max-age=31536000, immutable"
        header /system/accounts/avatars* Cache-Control "public, max-age=31536000, immutable"
        header /system/media_attachments/files* Cache-Control "public, max-age=31536000, immutable"

        handle_errors {
                @5xx expression `{http.error.status_code} >= 500 && {http.error.status_code} < 600`
                rewrite @5xx /500.html
                file_server
        }
}
```

- Caddy auto-obtains Let’s Encrypt certs and redirects HTTP → HTTPS.
- It forwards `/api/v1/streaming/*` to the streaming container and everything else to Puma.

### 3.2 Start/Reload Caddy

If not already enabled: `sudo systemctl enable --now caddy`  
Else: `sudo systemctl reload caddy`

***If you like this blog post you can support my work by either becoming a paid member or leaving a tip.**

[Donate ](#/portal/support) 

## **Step 4*

Media & Preview-Card Cleanup

To avoid unbounded disk usage, use `auto-cleanup.sh` (runs `tootctl media remove` and `tootctl preview_cards remove`):

``````
sudo chmod +x auto-cleanup.sh

# Into the crontab (sudo crontab -e):
0 0 * * * /opt/mastodon/auto-cleanup.sh
```

This frees old media and cards daily at 00:00.

## **Step 5*

Automated Updates

Ensure that you either update your server manually on a regular basis or automatically. You can follow Mastodon's engineering account at [@MastodonEngineering@mastodon.social](https://mastodon.social/@MastodonEngineering?ref=blog.gelbphoenix.de) for notifications when a new Mastodon version is released.

## **Step 6*

Maintenance & Upgrades

### 6.1 Database Migrations

After updating images, run: `docker compose exec --rm web rails db:migrate`   
If using PgBouncer, connect directly to Postgres for migrations.

### 6.2 Asset Pre-Compilation

`docker compose exec --rm web rails assets:precompile`  
Ensure sufficient RAM or swap during this step.

### 6.3 Restart Services

`docker compose down && docker compose up -d`

## **Step 7*

Security Hardening

- **Firewall**: Only allow ports 22, 80, 443.
- **Fail2Ban**: Protect SSH and Rails login endpoints. (Else you could also use CrowdSec.)
- **Regular OS updates**: `apt update && apt upgrade -y`.
- **Caddy**: Benefits from modern TLS defaults out of the box.
- **Backups**: Ensure that you have regular backups in the case that something breaks or your server is otherwise not easily rebooted. (No backup, no mercy!)

## **Step 8*

Alternatives & Federation

Although this guide focuses on Mastodon, the same pattern can applies to Pleroma, Friendica, etc. in a very general way – *please follow official guides for other services*. Federation seamlessly connects your instance with the entire Fediverse.

## **Conclusion*

You now have a production-ready, self-hosted Fediverse node with automated HTTPS, backups, cleanup, and updates. Monitor logs, stay on top of upgrades, and enjoy the decentralized social web!

***Happy federating!*** 🕸️

If you want to hear more from me you can find me in the Fediverse at [@gelbphoenix@social.gelbphoenix.de](https://gelbphoenix.link/mastodon?ref=blog.gelbphoenix.de) (Mastodon) or [@gelbphoenix@gram.social](https://gelbphoenix.link/pixelfed?ref=blog.gelbphoenix.de) (Pixelfed). For more posts like this subscribe to my newsletter or support me by [**becoming a member**](https://blog.gelbphoenix.de/#/portal/signup) or [donating](https://blog.gelbphoenix.de/#/portal/support).

Liked this post? Please share it with others via: [**Mastodon**](https://share.joinmastodon.org/?ref=blog.gelbphoenix.de#text=Look%20at%20this%20blog%20post%2C%20that%20I%20found!%20https%3A%2F%2Fblog.gelbphoenix.de%2Fadmins-guide-to-self-hosting-a-mastodon-instance), [**Bluesky**](https://bsky.app/intent/compose?text=Look%20at%20this%20blog%20post%20I%20found!%20https%3A%2F%2Fblog.gelbphoenix.de%2Fadmins-guide-to-self-hosting-a-mastodon-instance&ref=blog.gelbphoenix.de) or anywhere else by copying the link.