Mastodon instances store user-uploaded images, videos, and other media files locally on the server by default. As your instance grows, serving these files directly from the application server increases load times and bandwidth costs. A content delivery network, or CDN, caches media files at edge locations closer to your users, reducing latency and server strain. This article explains how to configure a CDN to serve media from your Mastodon instance while keeping the rest of the application traffic unchanged.
Key Takeaways: CDN Setup for Mastodon Media
- NGINX configuration: Isolate the /system/ location block to serve media files separately from the main Mastodon reverse proxy.
- CDN origin pull: Point your CDN to the instance domain or a dedicated media subdomain without exposing the backend IP directly.
- Content Security Policy update: Add the CDN domain to the img-src and media-src directives in Mastodon’s environment file.
Why a CDN Improves Mastodon Media Delivery
Mastodon stores media files in the public/system directory on the instance server. Every time a user views a post with an image or video, the Mastodon application server reads the file from disk and sends it over the network. This process consumes CPU and memory, especially when multiple users request the same file simultaneously. A CDN caches the file at geographically distributed edge servers. Subsequent requests for the same file are served from the edge cache, not from the origin server. This reduces server load, speeds up page loads for users far from the instance, and lowers bandwidth costs.
The CDN setup does not affect Mastodon’s API, streaming, or WebSocket connections. Only the media endpoint, typically served under /system/, is routed through the CDN. This separation requires careful NGINX configuration to ensure that the CDN pulls media from the correct location while the rest of the app remains direct.
Prerequisites Before Adding a CDN
Before you start, make sure you have the following:
- Root SSH access to your Mastodon instance server.
- NGINX installed and configured as a reverse proxy for Mastodon.
- A CDN provider account such as Cloudflare, Bunny CDN, Fastly, or AWS CloudFront.
- A custom domain or subdomain for the CDN endpoint, for example
media.yourinstance.social. - DNS access to add a CNAME record pointing the CDN subdomain to your CDN provider.
This guide assumes you are using NGINX as the web server. If you use Apache or Caddy, adapt the location block syntax accordingly.
Steps to Configure the CDN for Mastodon Media
Step 1: Create a Dedicated Subdomain for Media
Choose a subdomain that will be used exclusively for media delivery. Common choices are media.yourinstance.social or files.yourinstance.social. In your DNS provider, add a CNAME record for this subdomain pointing to the CDN provider’s endpoint. For example, with Cloudflare, you create a CNAME record for media pointing to yourcdn.yourcdnprovider.com. Wait for DNS propagation, which usually takes a few minutes.
Step 2: Configure NGINX to Serve Media Through the CDN Subdomain
Edit your Mastodon NGINX configuration file, typically located at /etc/nginx/sites-available/mastodon. You need to add a second server block that listens on the media subdomain and serves only the /system/ location. This server block will act as the origin for the CDN. Below is an example configuration:
server {
listen 443 ssl http2;
server_name media.yourinstance.social;
ssl_certificate /etc/letsencrypt/live/yourinstance.social/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourinstance.social/privkey.pem;
root /home/mastodon/live/public;
location /system/ {
alias /home/mastodon/live/public/system/;
expires 365d;
add_header Cache-Control "public, immutable";
add_header X-Content-Type-Options nosniff;
try_files $uri =404;
}
location / {
return 404;
}
}
Key points in this configuration:
- The
server_namematches your media subdomain. - The
rootpoints to the Mastodon public directory. - The
location /system/block usesaliasto map the URL path to the physicalsystemdirectory. - Cache headers are set to one year with
immutablebecause media files never change after upload. - All other requests to the subdomain return 404, preventing any non-media traffic from reaching this origin.
After saving the file, test the configuration and reload NGINX:
sudo nginx -t sudo systemctl reload nginx
Step 3: Configure the CDN to Pull from the Media Subdomain
In your CDN provider’s dashboard, create a new CDN endpoint or distribution. Set the origin server to https://media.yourinstance.social. Configure the following settings:
- Origin protocol: HTTPS (match your instance’s SSL setup).
- Cache behavior: Cache all static file extensions (jpg, png, gif, webp, mp4, webm, etc.) with a TTL of at least 24 hours.
- Query string forwarding: Disable or forward only standard cache keys to avoid cache fragmentation.
- SSL certificate: The CDN should provide a free SSL certificate for the media subdomain, or you can upload your own.
After the CDN distribution is active, note the CDN-provided hostname, for example d1234.cloudfront.net or yourinstance.bunnycdn.com. Then update the DNS CNAME record for media.yourinstance.social to point to this CDN hostname. This way, requests to the media subdomain are served by the CDN, which pulls content from your origin NGINX server as needed.
Step 4: Update Mastodon’s Environment Configuration
Mastodon uses the CDN_HOST environment variable to rewrite media URLs in the HTML and API responses. Edit the .env.production file in the Mastodon home directory (/home/mastodon/live). Add or update the following line:
CDN_HOST=https://media.yourinstance.social
If your CDN supports custom domains, this value is the same as your media subdomain. If your CDN requires a provider-specific hostname, use that hostname instead. After saving the file, restart Mastodon services:
cd /home/mastodon/live RAILS_ENV=production bundle exec rails assets:precompile sudo systemctl restart mastodon-web
Step 5: Update Content Security Policy
Mastodon’s Content Security Policy, or CSP, controls which domains can serve images, media, and scripts. If you do not update the CSP, browsers may block media loaded from the CDN domain. Add the CDN domain to the S3_ALIAS_HOST or directly to the CSP directives. In .env.production, add or modify:
S3_ALIAS_HOST=https://media.yourinstance.social
If you use a CDN provider hostname instead of a custom domain, set S3_ALIAS_HOST to that hostname. Then restart Mastodon again:
sudo systemctl restart mastodon-web
Common Issues After Adding a CDN
Media Files Return 404 Errors
If users see broken images, check the NGINX alias path. The alias directive must point to the exact directory where Mastodon stores media. Verify the path by running ls -la /home/mastodon/live/public/system/. Also confirm that the CDN origin URL matches the media subdomain exactly, including the trailing slash if required.
Mixed Content Warnings in the Browser
If the CDN serves media over HTTP while the main instance uses HTTPS, browsers block the content. Ensure that the CDN supports HTTPS and that the CDN_HOST value starts with https://. Most CDN providers offer free SSL certificates through Let’s Encrypt or their own certificate manager.
Cached Old Content After Media Update
Mastodon media files are immutable after upload, so caching old content is usually not a problem. However, if you replace a file manually, purge the CDN cache for that specific URL. Most CDN dashboards have a purge tool. You can also set a shorter TTL during testing, then increase it to 365 days once everything works.
| Item | Direct Server Serving | CDN Serving |
|---|---|---|
| Latency for distant users | High (server location dependent) | Low (edge cache) |
| Server CPU load per request | High (reads disk each time) | Low (most requests cached) |
| Bandwidth cost | Paid by instance owner | Often included or reduced |
| Configuration complexity | Minimal (default setup) | Moderate (DNS, NGINX, CDN) |
| Cache invalidation | Not applicable | Manual purge required |
Now your Mastodon instance serves media through a CDN, reducing server load and speeding up content delivery for users worldwide. To verify the setup, open your browser’s developer tools and inspect the network tab for a media URL. The response should show a cf-cache-status or similar header indicating a cache hit. For advanced optimization, consider enabling Brotli compression on the CDN or setting up a separate CDN for static assets like JavaScript and CSS files.