Admin’s Guide to Self-Hosting a Mastodon Instance
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)
 
Optional but Recommended
- A non-root system user (e.g. 
mastodon) with Docker group privileges - Firewall configured (e.g. 
ufwallowing 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.gitThis 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=9200This 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.shThis 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.
¹ [Affiliate Link: Following this link gives you $20 in Hetzner Cloud Credit when registering]