Mastodon
Posted on Nov 3 - 2025
Some notes for self-host

I have been a heavy user of Mastodon since 2022, mainly after I abandoned Twitter due to the wave of layoffs following Elon Musk’s acquisition.

I loved from the start the idea of a decentralized social network, and for that reason I was always fascinated by the idea of hosting my own instance.

 

 

Index

Table of contents

 

 

Introduction

The idea of this post is to have a set of small notes to remember, especially for myself, about how I performed the process to create an instance with what I had at home.

So this is not a step-by-step guide showing every single detail to reach the result, there are plenty of those on the internet, but rather a quick set of notes to keep in mind.

 

 

Single instance

A single instance, a single user, nothing more. Although this may seem unusual to many, there are actually many instances that have only one user.

There are pros and cons to this choice. In my case, it was a sort of challenge, to put my skills into practice and see if I could build a fully functioning instance. Consequently, the instance had to have only me as the user.

For anyone planning something similar long-term, a single-user instance can also be an interesting choice if you want to own everything, but equally you have to do extra work to get the instance to start populating.

Being the only users, the posts section will be occupied only by your posts, hashtags will need to be searched and followed manually, the news section will remain empty. Searching for toots, hashtags, users will be heavily reduced and you may need to configure add-ons like Elasticsearch for full-text search.

Of course there are various other ways to populate that small instance, but it’s good to keep these things in mind.

In my opinion, the idea at most is to create and manage a single instance with a base of friends, family, relatives and people who share this passion with you.

 

 

Pre-requisites

The reference guide is the one on the Mastodon site; it shows step by step how to install and manage everything.

For our instance, we need:

  1. A machine running Ubuntu 24.04 or Debian 13 that you have root access to.
  2. A domain name or a subdomain for the Mastodon server, e.g. example.com.
  3. An email delivery service or other SMTP server.

In my case, I used a domain bought cheaply on Cloudflare and for email delivery I simply used the configuration of an old Gmail account.

Actually, the last point can be skipped if you are the only users of the instance, most operations can be done from the CLI using tootctl.

 

 

Set up

I decided to use an old ThinkPad L470, i5-6300U, 8GB RAM DDR4-2133, 256GB SSD, Intel HD Graphics 520, which I had at home and always used for small experiments.

I wiped the disk and installed a fresh copy of Ubuntu 24.04 LTS server. After running the usual package updates I installed openssh and changed the configuration to use keys instead of passwords:

    
    sudo apt install openssh-server


This is the only step not present in the Mastodon guide. After that I followed the guide step by step, that’s it!

 

 

Connection

You get to the point of obtaining the certificate:

    
    certbot certonly --nginx -d example.com


This wasn’t possible because my little ThinkPad is not exposed to the internet; it remains hidden behind the private network. The problem was then to find a way to avoid:

  • Having a fixed public IP address.
  • Configuring port forwarding on the router.
  • Opening firewall ports to the outside.


Since the domain was on Cloudflare I started looking for any modes or features that could help with this. Enter Cloudflare Tunnels.

Cloudflare Tunnels uses an outbound connection, completely avoiding the issue of incoming connections. In simple terms:

  • It creates an outgoing connection from the local server to the Cloudflare servers.
  • Because the connection is outbound, the firewall almost always allows it.
  • Once established, the tunnel stays open and persistent, allowing bidirectional communication.
  • When someone accesses the domain from the Internet, Cloudflare receives the request and forwards it through the tunnel to the local server.


The result is that there’s no need to open any incoming port on your firewall or router.

Consequently, Cloudflare handles the requests to the site and Cloudflare Tunnels routes the public traffic to the local Nginx service that Mastodon relies on.

Sidekiq metrics screenshot showing a total of 31,802 failed related to 301,613 processed

In short:

  • The laptop where Mastodon is installed and configured is not exposed to the Internet.
  • A small program, cloudflared, on the laptop creates an outbound tunnel to Cloudflare.
  • Cloudflare handles the public HTTPS.
  • Cloudflare then forwards the traffic, through the tunnel, to Nginx running on localhost.

 

 

Nginx

The problem is that, at least as far as I could understand, the Mastodon Nginx configuration expects an SSL certificate, so you have to give it one because Cloudflare Tunnels will connect to Nginx over HTTPS.

It’s not the most elegant solution, but I created a self-signed certificate valid for one year. I then created the folders Nginx expects:

    
    sudo mkdir -p /etc/letsencrypt/live/example.com/


And the certificate:

    
    sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/letsencrypt/live/example.com/privkey.pem \
  -out /etc/letsencrypt/live/example.com/fullchain.pem

Specifying the website domain for Mastodon in the Common Name field.

After that you can continue following the Mastodon guide again by copying the certificate:

    
    sudo cp /home/mastodon/live/dist/nginx.conf /etc/nginx/sites-available/mastodon
  sudo ln -s /etc/nginx/sites-available/mastodon /etc/nginx/sites-enabled/mastodon
  sudo rm /etc/nginx/sites-enabled/default


At this point, Nginx is running on https://localhost port 443, using a self-signed certificate.

 

 

Tunnel

Creating the communication tunnel was very simple because it is handled directly on Cloudflare. Once you start the Zero Trust guided setup, go to Networks > Tunnels and create a new Tunnel specifying Cloudflared. You then get the credentials to install and start the tunnel on the local machine.

Once the tunnel is connected, you can configure a new Published application routes with:

  1. Domain
    The domain for Mastodon that you chose.

  2. Type
    HTTPS.

  3. URL
    https://localhost:443.

The URL is where Nginx is listening; it will then route the request to the Mastodon components in charge.

 

 

TLS

After setting everything up, calling the site, nothing worked. 🥲

The solution became obvious after analyzing what happens when a visitor tries to access the site.

  1. The visitor’s request
    A user visits the domain. Their browser talks to Cloudflare’s servers. That connection is secure and uses a valid SSL certificate provided by Cloudflare. So far, so good.

  2. The Tunnel kicks in
    Cloudflare receives the request and forwards it through the tunnel to the cloudflared software running on the laptop.

  3. The problem
    Now cloudflared has to forward this request to Nginx, which is listening on https://localhost:
    • Just like a browser, cloudflared acts as a client: it opens a secure connection to Nginx.
    • Nginx answers: “Hi! I’m localhost, here’s my SSL certificate to encrypt the connection”.
    • Cloudflared inspects the certificate and performs a security check.

  4. Verification failure
    During the check, cloudflared notices that Nginx’s certificate is not trusted. This is because it’s self-signed and was not issued by a public Certification Authority like Let’s Encrypt, DigiCert, etc.

  5. Cloudflared thinks
    This certificate was signed by an unknown entity. It could be a man-in-the-middle attack, I refuse the connection for security reasons.

Ending up with a nice 502 Bad Gateway error.

So? The trivial fix is to enable the No TLS Verify option in the tunnel settings.

By enabling this option, you give cloudflared explicit instructions that when it connects to https://localhost, it should ignore the fact that the SSL certificate is not trusted.

So the flow becomes: the visitor’s request is handled with Cloudflare’s valid SSL, the HTTPS request is routed via the tunnel to https://localhost:443 where, with No TLS Verify enabled, cloudflared ignores the self-signed certificate and forwards the request to Nginx which serves the Mastodon components (Puma, Sidekiq, etc.).

 

 

Security

Is it okay to disable TLS verification? NO!
Disabling TLS verification is a terrible security practice.

But in this specific context, it is acceptable.

The traffic protected by that certificate travels only inside my laptop, from the cloudflared process to the Nginx process.It’s not traveling on my local network or the Internet. The risk that someone intercepts that communication on localhost is practically nil.

 

 

Mastodon

So how did it go in the end?

Browser screenshot showing the Home page of the instance created by following the Mastodon guide. The Home shows a user’s toot

Not bad, everything worked and everything ran without particular problems.

Reading online I saw many people suggesting various solutions to start populating your instance, which will initially be empty. Everyone discouraged using relays because they bring a high data load that you will mostly never consume if you’re just experimenting with your instance.

So, what did I do?

I decided to use relays.

In particular, the relay of the main instance mastodon.social.
In one day it reached 42 GB of media storage on average:

Admin dashboard screenshot showing storage used. In particular, there are 42 GB used just for media storage

Playing with relays, especially adding large ones to a new instance, and moreover on my small laptop, was like pouring gasoline on a fire.

A relay forwards to the instance all the posts from hundreds of other instances connected to it. When you add a relay, the server is suddenly flooded with thousands of posts and activities from users it has never seen before.

Consequently, for each single post the server must get to work. It creates a huge queue of jobs in Sidekiq, including:

  • This post is by @user.server. I need to fetch their profile > MentionResolveWorker
  • Oh, and this user has an avatar. I need to download it > RedownloadAvatarWorker
  • And there’s an image in the post. I need to download that too > RedownloadMediaWorker

The consequence was a rise in the failure rate of handling many requests:

Sidekiq metrics screenshot showing a total of 31,802 failed related to 301,613 processed

The poor laptop doesn’t have the resources to handle this deluge from one of the largest instances. I should add that, out of laziness, I didn’t even plug in the LAN cable and the laptop handled everything over Wi‑Fi.

So, indeed, unless you want to waste space and resources, using relays is not a good practice.

In the end, however, the experience was interesting and educational and taught me, even from a very small start, how demanding managing the entire Mastodon infrastructure can be with hundreds of thousands of people constantly connected.