Mastodon has some fun questionable interesting architectural choices. When you post a link, instead of your server sending metadata about the link (such as title, snippet, and image) out to other servers, it just shares the link raw, which triggers other servers to immediately hit the URL. If that URL includes an oembed link for rendering a preview of the post, that URL also gets a hit. Due to existence of relays, your post may be fanned out to servers that don’t have any of your followers, amplifying the traffic. Hello, distributed denial of service attack!

Here are a couple of ways to mitigate it.

#1: Caching plugin

I use W3 Total Cache. It acts as a pseudo static site generator by crawling your own WordPress instance, creating, and then serving static HTML files. Other WP caching plugins work similarly.

This solves half the problem, as hits against the post URL quickly start returning static data.

#2: Caching oembed URLs

The problem is that caching plugins don’t seem to account for the oembed URLs that look like <your-site.com>/wp-json/oembed/1.0/embed?url=https%3A%2F%2Fyour-site.com%2Fyour-page%2F. These do query (and take down) the database during the Mastodon traffic storm. For me, that means lots of HTTP 500 responses:

Access log file showing HTTP 500 errors for oembed URLs on my web server.

Caching to the rescue!

Thankfully, these oembed URLs don’t change unless your post does, so they’re safe to cache for at least a few minutes.

I use Caddy as a reverse proxy to a WordPress Docker container, so I added the following block to my site’s config, after rebuilding Caddy with the cache-handler plugin (docs):

glyphy.com {
  # ... other configs ...
  route /wp-json/oembed/* {
    cache {
      stale 10m
      ttl 5m
      badger {
        path /tmp/badger_glyphy.com
      }
    }
  }
}

This stores a copy of the oembed responses on disk and returns it for five minutes without actually hitting the WordPress installation itself, which is both quicker and avoids taking it down. I tested this out. The access log records a few hits against the oembed URL followed by complete silence as Caddy returns a cache response to the rest of the requests. It’s kept this tiny site up during the test.

You can check if it’s working by looking at response headers in your browser’s developer tools. There should be something like:

cache-status: Souin; hit; ttl=291; key=GET-https-yoursite.com-/wp-json/oembed/1.0/embed?url=https://yoursite.com/your-post-url/

I haven’t messed around with any of the more advanced settings of the cache system yet, but there’re some useful options there, such as one that reduces the maximum size of the cache file on disk.


If you’re not using Caddy, you may have another caching mechanism available. The idea is just to configure the cache for the /wp-json/oembed/* URLs for a few minutes, which is generally how long the Mastodon traffic flood lasts. If you’re using a CDN it may already do this for you.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.