Skip to content

How to migrate Mastodon local file storage to Cloudflare R2

Published: December 28th, 2024 Mastodon A forklift loading a pallet of boxes photographed from inside a trailer

Self-hosting a Mastodon server is an excellent way to take control of your social networking experience, but managing storage for media files can quickly become a challenge. As your server grows, so does the demand for scalable, cost-effective, and reliable storage. Local storage can be limiting and expensive to expand, making it less ideal for long-term hosting.

That’s where Cloudflare R2 comes in (or any other preferred remote storage solution). With no egress fees and seamless integration via the S3 API, Cloudflare R2 offers a modern solution to store and serve your Mastodon media files efficiently. By moving your storage to the cloud, you can free up local resources while ensuring your server is ready to handle increasing traffic and media uploads.

In this guide, I’ll walk you through the entire process of how I migrated the toot.re media storage to Cloudflare R2 with the help of rclone, a powerful tool for syncing and managing files between local and cloud storage.

Whether you’re looking to reduce costs or future-proof your server, this guide has you covered and also let you learn of a things I can do better next time.

Prerequisites

You need the following to start doing the migration:

  • A running Mastodon instance on Linux

  • SSH access to the Mastodon server and its configuration files

  • A Cloudflare account with an R2 storage enabled

  • rclone on the server (used to sync files between local storage and R2).

Preparing Cloudflare R2

Let’s start with setting up a Cloudflare R2 bucket.

  • Log in to Cloudflare dashboard

  • Navigate to R2 Object Storage

  • Create a new bucket (e.g., mastodon-media)

When the bucket is created, you need to generate R2 API Tokens, so your Mastodon instance, and rclone, can access the bucket with the right permissions.

To generate R2 API Tokens go to the R2 Object Storage page and click on “Manage R2 API Tokens”. Then, follow these steps:

  • Click on “Create API Token”

  • Give the token a smart name, e.g. “R2 token for Mastodon server <url>”

  • Set the permissions to Object Read & Write

  • Specify the bucket if you want to limit this API token to a certain bucket

  • Specify how long you want to keep the token alive with the TTL.
    Be aware that if you do not choose “Forever” here, you might lose access to your bucket if you do not recreate a token and add that to your Mastodon .env.production config file.

  • If you want to limit access to this API token by IP address, you can either limit, or deny access per IP address.

  • Now click “Create API Token”

  • When the API Token creation was successful, record the Access Key ID and Secret Access Key, since you will not see these again when you navigate to another screen.

Screenshot with text:
Create API Token
Test token was successfully created
Summary:
Permissions: Allows the ability to read, write, and list objects in specific buckets.
Buckets: tootre.
The Cloudflare R2 API Token has been created

Since we now have created a way to access our R2 bucket on Cloudflare, it’s time to start using it. Before we hook up Mastodon with the bucket, let’s first install and/or configure rclone.

Installing and configuring rclone

When migrating from local storage to an R2 bucket, you need to be sure to move all local files to the R2 bucket. To do this, we cannot just simple copy/move files, we need a nice tool for this. Meet rclone.

To install rclone on Linux systems, run:

sudo -v ; curl https://rclone.org/install.sh | sudo bash

This script installs the latest stable release of rclone, after it checks if rclone is installed. It won't re-download if not needed.

Test if rclone is installed by running rclone --version and compare that to the latest stable version in the rclone website.

Now that rclone is installed, let’s move on and configure rclone to make use of the R2 API token we created.

To configure rclone we need to edit the config file which we can find by running rclone config file. This will output a path like /home/mastodon/.config/rclone/rclone.conf, which we can use to create/edit that file.

Open the file with your favorite CLI text editor (mine is nano) and make sure all info below is in that file before saving it. Make sure you use your rclone API token keys.

[r2]
type = s3
provider = Cloudflare
access_key_id = your_r2_api_access_id
secret_access_key = your_r2_api_secret_access_id
endpoint = your_cloudflare_api_endpoint
bucket_acl = private
region = auto
no_check_bucket = true

Your endpoint URL can be found in the Cloudflare R2 page. Open the bucket, click “Settings” and copy the S3 API URL in “Bucket Details”.

The no_check_bucket needs to be added if you are using Objectl-level Permissions as I have advised when creating the R2 API Token. Cloudflare rclone docs mention this.

Now that we have configure rclone, let’s continue using it.

Backup and sync Mastodon media files

Before doing anything with the local Mastodon media files, make a backup.

For a standard install, the storage is located at /home/mastodon/live/public/system/, please check this before continuing with making a backup. You want to be sure to backup the right data.

Now execute this command to create a compressed tar file with all of the Mastodon media files of your instance: tar -czvf mastodon_media.tar.gz /home/mastodon/live/public/system/.

This might take a long time depending on the amount of files stored locally.

If you need to free up some space, read this post on how to clean the cache.

Again, before doing anything with the local Mastodon media files, make a backup.

When the backup has completed, it’s time to sync the local files with the R2 bucket. To start this, execute: rclone sync /home/mastodon/live/public/system/ r2:mastodon-media -P
where r2 is the name of the configuration for rclone, mastodon-media is the name of the bucket, and -P let’s you see the progress.

Take some time for a break, because this can take a very long time, as in hours.

The next thing we need to do, is configure Mastodon to create our R2 bucket

Configuring Mastodon to use Cloudflare R2

To have Mastodon use the R2 bucket, there are just a few steps to take. Let’s go.

  • Make a backup of your .env.production file located in the Mastodon installation directory, typically /home/mastodon/live/

  • Edit the .env.production file and make sure the following is added:

S3_ENABLED=true
S3_BUCKET=your_bucket_name
S3_REGION=auto
AWS_ACCESS_KEY_ID=your_r2_api_access_id
AWS_SECRET_ACCESS_KEY=your_r2_api_secret_access_id
S3_ALIAS_HOST=your.r2.hostname
S3_HOSTNAME=your.r2.hostname
S3_ENDPOINT=your_cloudflare_api_endpoint
S3_PERMISSION=private

Carefully replace all data for S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, S3_ALIAS_HOST, S3_HOSTNAME and S3_ENDPOINT.

If you want to use a custom domain name for your bucket, you can add one by added a custom domain in the “Public access” settings of your bucket.

Screenshot with text: 

Public access
Specify how your bucket can be accessed.

Custom domains
When a custom domain is connected to your bucket, the contents of your bucket will be made publicly accessible through that domain. Websites connected can also benefit from Cloudflare features such as bot management, Access, and Cache. 
Domain Status	Access to Bucket	
r2.toot.re	Active  Active.
Screenshot of the custom domain for public access in the Cloudflare R2 settings

When the .env.production file is saved, it’s time to restart Mastodon.

You can choose to flip the switch while the `rclone sync` is running, or wait until the `rclone` job is done. I flipped the switch during the `rclone` job, and it results in images not being loaded, because they were not yet synced.

To restart the Mastodon processes, run:

$ sudo systemctl restart mastodon-web mastodon-sidekiq mastodon-streaming

Now post a test post, and upload an image, like the post I posted. This should be working, and if it is, congratulate yourself with a job well done! If it’s not working, please carefully go over all the steps in this guide.

Conclusion

This was a tough one for me, since I had no clue what to do before I started this process. After reading a lot of docs and posts by other Mastodon instance owners, I decided I was ready to do this. And yes, I learned a few things (see the Lessons learned paragraph).

I think embracing cloud storage for your Mastodon server not only enhances your infrastructure but also demonstrates a commitment to providing the best experience for your users. By investing in a robust and scalable solution like Cloudflare R2, you position your server to handle the demands of the decentralized social web.

Decentralized FTW!

Other optional things to do

Clean up local storage

When the migration has succeeded, and the rclone job is done, you can safely remove local Mastodon media files. Be sure that you have made a backup, and then execute: rm -rf /home/mastodon/live/public/system/*. If you are not sure to execute this right after the migration, then set a reminder to do this one month after the migration.

Monitor your bucket usage

Regularly open the Cloudflare dashboard and check the metrics of your R2 bucket. It’s advised to keep track of the resource usage, performance, and plan ahead the costs.

Backup R2 bucket offsite

It’s never a bad thing to have a backup. So, go ahead, and think of a way to backup your bucket to another place, in case Cloudflare pulls the plug. Next on my list is setting up an rclone sync job to sync the bucket contents to my Synology Diskstation.

Remove cached remote accounts that no longer exist

Mastodon keeps a cache for all accounts it ‘sees’. Now would be a good time to clean that up. You can use the Mastodon Admin CLI tool accounts cull command to that for you.

The result of this command on my server was this: Visited 298103 accounts, removed 3283. 🎉

Lessons learned (aka “things I can do better next time”)

  1. Make a backup of local files.

  2. Read the docs carefully. You might miss that you have to add no_check_bucket = true to the rclone config to prevent “Access Denied, 403” errors when using rclone and post a question on the Cloudflare Discord to find that out.

  3. Use rclone sync and not rclone move. Because, well, move deletes local files, and in case of failure and not doing what I said in bullet 1., you loose files.

Photo by Elevate on Unsplash

About Marcel Bootsman

Marcel discovered the web in 1995. Since then he has paid attention to and worked with lots of technologies and founded his own WordPress oriented business nostromo.nl in 2009.

Currently Marcel is Business Development Manager Dutch & DACH Markets at Kinsta where he helps Kinsta's client base grow with Managed WordPress, Application, Database and Static site hosting.

You can contact Marcel on a diverse range of online platforms. Please see the Connect section on the homepage for the details.