Admin’s Guide to Self-Hosting a Mastodon Instance

Admin’s Guide to Self-Hosting a Mastodon Instance
Photo by Rolf van Root / Unsplash

In this guide, you’ll learn how to deploy an own 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 node running smoothly and securely.


Prerequisites

Server Requirements

  • VPS: ≥ 4 CPU cores, ≥ 50 GB disk (e.g. Hetzner CX32¹)
  • 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)
  • 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

1. Clone the Gelbphoenix Boilerplate

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

sudo mkdir -p /opt/mastodon && cd mastodon
sudo git clone https://github.com/gelbphoenix/boilerplate.git

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


2. Initial Configuration

2.1 Create .env.production

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

docker compose exec --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.2 Optional Elasticsearch

To enable full-text search, uncomment the es service in docker-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.
Check logs with docker compose logs -f web and confirm health endpoints return OK.


4. Configure Caddy for HTTPS & Reverse Proxy

4.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.

4.2 Start/Reload Caddy

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


5. 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
0 0 * * * /opt/mastodon/auto-cleanup.sh

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


6. 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 for notifications when a new Mastodon version is released.


7. Maintenance & Upgrades

7.1 Database Migrations

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

7.2 Asset Pre-Compilation

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

7.3 Restart Services

docker compose down && docker compose up -d


8. 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!)

9. 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 (Mastodon) or @gelbphoenix@gram.social (Pixelfed).

¹ [Affiliate Link: Following this link gives you $20 in Hetzner Cloud Credit when registering]