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 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=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 or support me by becoming a member.
¹ [Affiliate Link: Following this link gives you $20 in Hetzner Cloud Credit when registering]