A maintenance script for a self-hosted Mastodon instance. It reclaims disk
space by running the standard tootctl cleanup tasks — pruning dead remote
accounts, unreferenced statuses, cached remote media, link-preview thumbnails,
and orphaned files — in a form that is safe to schedule and easy to monitor.
It wraps the same commands you were already running, and adds the things that matter for an unattended job: single-instance locking, timestamped logging, per-step error handling, a non-zero exit code on failure, a dry-run mode, and tunable retention windows.
Each run executes these steps in order. The orphan sweep runs last on purpose, so that files dereferenced by the earlier steps get cleaned up too.
| # | Command | What it removes |
|---|---|---|
| 1 | accounts prune |
Remote accounts that never interacted with a local user |
| 2 | statuses remove |
Unreferenced statuses (from relays, unfollowed remotes, no replies/interactions) older than STATUSES_DAYS |
| 3 | media remove |
Cached remote media attachments older than MEDIA_DAYS |
| 4 | media remove --prune-profiles |
Cached remote avatars (optional, off by default) |
| 5 | media remove --remove-headers |
Cached remote headers/banners (optional, off by default) |
| 6 | preview_cards remove |
Local link-preview thumbnails older than PREVIEW_CARDS_DAYS |
| 7 | media remove-orphans |
Files on disk that no longer belong to any attachment |
Only flags that each subcommand actually supports are passed, so nothing errors out on an unknown option.
- A working Mastodon install with
tootctlatMASTODON_HOME/bin/tootctl(default/home/mastodon/live/bin/tootctl). bash, andflock(fromutil-linux, present on essentially all Linux distros) for single-instance locking. Withoutflockthe script still runs, just without overlap protection.- Run it as the
mastodonsystem user (the one that owns the install), not as root.
# Place it somewhere the mastodon user can execute, e.g. the home dir
sudo -u mastodon cp mastodon-cleanup.sh /home/mastodon/mastodon-cleanup.sh
sudo -u mastodon chmod +x /home/mastodon/mastodon-cleanup.sh
# Make sure the log directory exists and is writable by the mastodon user
sudo mkdir -p /var/log/mastodon
sudo chown mastodon:mastodon /var/log/mastodonEverything is configured through environment variables, so you don't have to
edit the script. Defaults match tootctl's own defaults, so out of the box the
script behaves like plain tootctl.
| Variable | Default | Description |
|---|---|---|
MASTODON_HOME |
/home/mastodon/live |
Path to the Mastodon checkout |
TOOTCTL |
$MASTODON_HOME/bin/tootctl |
Full path to the tootctl binstub |
RAILS_ENV |
production |
Rails environment |
STATUSES_DAYS |
90 |
Age (days) before unreferenced statuses are removed |
MEDIA_DAYS |
7 |
Age (days) before cached remote media/avatars/headers are removed |
PREVIEW_CARDS_DAYS |
180 |
Age (days) before preview-card thumbnails are removed (keep ≥ 14) |
CONCURRENCY |
5 |
Worker threads for tasks that support --concurrency |
MEDIA_REMOVE_PROFILES |
0 |
Set 1 to also drop cached remote avatars |
MEDIA_REMOVE_HEADERS |
0 |
Set 1 to also drop cached remote headers/banners |
LOG_FILE |
/var/log/mastodon/cleanup.log |
Log destination (falls back to stdout if unwritable) |
LOCK_FILE |
/tmp/mastodon-cleanup.lock |
Lock file for single-instance protection |
ENV_FILE |
(none) | Optional file sourced before running (see rbenv/rvm note) |
DRY_RUN |
0 |
Set 1 (or pass --dry-run) to preview without deleting |
VERBOSE |
0 |
Set 1 (or pass --verbose) for extra tootctl output |
Command-line flags override the env vars: --dry-run, --verbose, --help.
If your goal is aggressive disk savings on a small instance, lower the windows,
e.g. STATUSES_DAYS=14 MEDIA_DAYS=3. Do not set PREVIEW_CARDS_DAYS below
14 — Mastodon won't re-fetch a preview card until the link is posted again after
two weeks, so deleting recent cards just leaves blank previews.
# Dry run first — see what would be removed without deleting anything.
# (The 'statuses remove' step is skipped in dry-run: tootctl has no dry run for it.)
./mastodon-cleanup.sh --dry-run
# Normal run
./mastodon-cleanup.sh
# Tighter retention + also prune avatars and headers, just for this run
STATUSES_DAYS=14 MEDIA_DAYS=3 MEDIA_REMOVE_PROFILES=1 MEDIA_REMOVE_HEADERS=1 \
./mastodon-cleanup.shsudo crontab -u mastodon -e# Optional: email cron output/errors to you
MAILTO=you@example.com
# Run every Sunday at 04:00. The script writes its own timestamped log.
0 4 * * 0 /home/mastodon/mastodon-cleanup.sh/etc/systemd/system/mastodon-cleanup.service:
[Unit]
Description=Mastodon cleanup (tootctl maintenance)
After=network-online.target
[Service]
Type=oneshot
User=mastodon
Group=mastodon
# Optionally tune retention here:
# Environment=STATUSES_DAYS=14
# Environment=MEDIA_DAYS=3
ExecStart=/home/mastodon/mastodon-cleanup.sh/etc/systemd/system/mastodon-cleanup.timer:
[Unit]
Description=Run Mastodon cleanup weekly
[Timer]
OnCalendar=Sun *-*-* 04:00:00
Persistent=true
[Install]
WantedBy=timers.targetEnable it:
sudo systemctl daemon-reload
sudo systemctl enable --now mastodon-cleanup.timer
systemctl list-timers mastodon-cleanup.timer # check next run
journalctl -u mastodon-cleanup.service # view output- Every step is logged with a timestamp, the exact command run, and its
duration, to both the console and
LOG_FILE. - The run ends with a summary line: number of steps, number of failures, and total time.
- Exit
0if all steps succeeded; exit1if any step failed (so cronMAILTOor a systemdOnFailure=handler can alert you). A run skipped because another is already in progress also exits0.
-
First run can take a long time.
statuses removeis a computationally heavy operation that builds temporary database indices; on a large instance the first cleanup can run for hours. Consider running it once by hand insidetmux/screenbefore trusting it to a scheduler. -
media remove-orphansis slow, and on S3-compatible object storage it lists every object, which can incur API request charges. -
rbenv/rvm and cron
PATH. Cron runs with a minimal environment. If your Ruby is managed by rbenv/rvm,tootctlmay fail to find Ruby. PointENV_FILEat a small script that sets things up, e.g.:# /home/mastodon/cleanup.env export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)"
then run with
ENV_FILE=/home/mastodon/cleanup.env. -
Run as
mastodon, not root. The script warns if it detects it is running as root. -
Related command not included:
tootctl accounts cull(remove remote accounts that no longer exist) is a heavier, network-dependent task that queries every remote server. It's intentionally left out of the routine job; run it manually now and then if you want.
- Mastodon admin CLI (
tootctl) documentation: https://docs.joinmastodon.org/admin/tootctl/