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

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). For more posts like this subscribe to my new newsletter or support me by becoming a member.

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