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