Trying to use Caddy for Mastodon - Having trouble translating Caddyfile from v1 to v2

1. Caddy version (caddy version):

v2.0.0

a. System environment:

Running Debian 10 on a Vultr VPS.

Current Caddyfile

toomanycooks.social {
        root * /home/mastodon/live/public

        encode gzip

        header / {
                Strict-Transport-Security "max-age=31536000;"
        }

        header /emoji Cache-Control "public, max-age=31536000, immutable"
        header /packs Cache-Control "public, max-age=31536000, immutable"
        header /system/accounts/avatars Cache-Control "public, max-age=31536000, immutable"
        header /system/media_attachments/files Cache-Control "public, max-age=31536000, immutable"

        handle_errors {
                root * 500.html
        }

        reverse_proxy / localhost:3000

        reverse_proxy /api/v1/streaming localhost:4000
}

3. The problem I’m having:

I’m trying to use Caddy for running a Mastodon instance. The resources I’ve found include Mastodon’s nginx config file as well as a Caddy config - but that was done for Caddy v1 and I’m having some trouble translating it over. Specifcally, it seems to use the / path for both static files and proxying to the app at localhost:3000 and I’m unable to figure out how to make this work in Caddy v2. The v1 Caddyfile seems to use some kind of rewrite hacking to get this to work, which I’m unable to figure out the exact replacement for in this case.

I’m also not 100% sure that the handle_errors block that I’ve written is correct, so any advice on that would be good too.

Mastodon nginx Config File

map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

upstream backend {
    server 127.0.0.1:3000 fail_timeout=0;
}

upstream streaming {
    server 127.0.0.1:4000 fail_timeout=0;
}

proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;

server {
  listen 80;
  listen [::]:80;
  server_name example.com;
  root /home/mastodon/live/public;
  location /.well-known/acme-challenge/ { allow all; }
  location / { return 301 https://$host$request_uri; }
}

server {
  listen 443 ssl http2;
  listen [::]:443 ssl http2;
  server_name example.com;

  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA;
  ssl_prefer_server_ciphers on;
  ssl_session_cache shared:SSL:10m;

  # Uncomment these lines once you acquire a certificate:
  # ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
  # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 80m;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
    add_header Cache-Control "public, max-age=31536000, immutable";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
  }

  location /sw.js {
    add_header Cache-Control "public, max-age=0";
    add_header Strict-Transport-Security "max-age=31536000";
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://backend;
    proxy_buffering on;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_cache CACHE;
    proxy_cache_valid 200 7d;
    proxy_cache_valid 410 24h;
    proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
    add_header X-Cached $upstream_cache_status;
    add_header Strict-Transport-Security "max-age=31536000";

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://streaming;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}

v1 Caddyfile for Mastodon I had found:

https://social.amnesiak.org {
    log  /var/lib/caddy/data/social.amnesiak.org-access.log
    errors /var/lib/caddy/data/social.amnesiak.org-access.err
    
    root /data/mastodon/public

    gzip

    header / {
        Strict-Transport-Security "max-age=31536000;"
    }

    header /emoji Cache-Control "public, max-age=31536000, immutable"
    header /packs Cache-Control "public, max-age=31536000, immutable"
    header /system/accounts/avatars Cache-Control "public, max-age=31536000, immutable"
    header /system/media_attachments/files Cache-Control "public, max-age=31536000, immutable"

    errors {
        * 500.html
    }

    rewrite {
    if {path} is /
    to /proxy{path}
    }

    rewrite {
        if {path} not_has /api/v1/streaming
        to {path} /proxy{path}
    }

    proxy /proxy localhost:3000 {
        without /proxy

        transparent
        websocket
    }

    proxy /api/v1/streaming localhost:4000 {
        transparent
        websocket
    }

    tls {
        protocols tls1.2
    }
}

5. What I already tried:

Like I showed above, I already have most of the Caddyfile translated over to V2.

6. Links to relevant resources:

Links to where I got the files. Mastodon’s nginx.conf and the v1 Caddyfile

Any help would be greatly appreciated, let me know if there’s any more information needed.

Have you read through the Upgrade Guide?

Read through it carefully, most of your answers should be answered by it.

Yes I did. I mentioned I already translated most of the file over. The part in the upgrade guide about rewrite hacking does not really help for my situation and I’m stuck on it.

From the upgrade guide:

  • Where you matched requests by path prefix in Caddy 1, path matching is now exact by default in Caddy 2. If you want to match a prefix like /foo/ , you’ll need /foo/* in Caddy 2.

Please read it more carefully! All the information you need is there.

Nevermind. If I’m just going to be condescendingly told to read the guide and have snippets from it copy-pasted at me without any further guidance as to how I need to apply it because the guide is extremely general and I’m confused, I’m not going to continue trying to get help here.

I’ve got it fixed. It was certainly not as simple as reading the guide and applying that condescendingly-given “advice”. I have no interest in sharing my solution with a community that treats me like this, though.

Call me spiteful if you want. Have a good one.

After reading this thread, I can see why both @mdszy and @francislavoie are a bit miffed. :confused:

I think the original post is very well-written and mostly clearly spells out the questions/problems.

I also think Francis is right about the answers being in the documentation, although the specific rewrite you’re looking for isn’t in the upgrade guide – you have to go to the rewrite documentation to find it.

Breaking it down:

  • It seems like you figured out how path matchers work in v2. This is where Francis was right, in that the change is described in the Upgrade Guide: they are no longer prefix-only, they are exact matches (with prefix/suffix matches being optional via *).

  • Apparently, the other question is about rewriting, or “rewrite hacks”, this is where you’re right @mdszy in that the Upgrade Guide doesn’t go into detail about your specific use case. This rewrite:

rewrite {
    if {path} is /
    to /proxy{path}
}

is basically this in v2: rewrite / /proxy{path} - much easier. :slight_smile: rewrite (Caddyfile directive) — Caddy Documentation

  • And possibly one other question about handle_errors. I will chime in here: handle_errors does not directly map requests to error pages; it is simply a group of handlers that are invoked if any of the primary handlers return an error value (i.e. 5xx usually). You need to set up anything that will happen in that case, including a file server or reverse proxy or whatever else.

In the future, let’s try to be more specific when we point people to documentation—which I honestly prefer since I would rather that our users learn the software rather than copy+paste spoon-fed answers—but we can also help them more quickly find where the answer lies (even if they need to apply a little synthesis).

@mdszy, I hope you’ll post your working solution. I don’t personally have anything invested in your answer, but it will only irritate others in your boat who searched and found the exact question they have, only to realize that the answer has been withheld.

3 Likes

Thanks @matt

I apologize @mdszy, it wasn’t my intention to frustrate you. I was trying to be as quick and to the point as I could to save time, I read the title, skimmed your post, saw that you were missing * in your v2 Caddyfile path matchers, and pointed you to the relevant docs.

I didn’t scroll down in your v1 Caddyfile (I went too fast and missed that there was more of it hidden by the scroll box), so I missed what you were referring to about rewrite hacks.

We try not to hand-hold everyone who asks questions here, we want people to have some initiative in figuring out the problem for themselves, because that’s how learning is best done. That’s why we usually point to the docs first. I was a bit too quick on the trigger this time and I’m sorry for that.

2 Likes

This is the config that worked in the end.

toomanycooks.social {
        @try_masto {
                file
        }

        root * /home/mastodon/live/public

        handle @try_masto {
                file_server *
        }

        handle /api/v1/streaming* {
                reverse_proxy localhost:4000
        }

        handle {
                reverse_proxy localhost:3000
        }

        encode gzip

        header / {
                Strict-Transport-Security "max-age=31536000;"
        }

        header /emoji Cache-Control "public, max-age=31536000, immutable"
        header /packs Cache-Control "public, max-age=31536000, immutable"
        header /system/accounts/avatars Cache-Control "public, max-age=31536000, immutable"
        header /system/media_attachments/files Cache-Control "public, max-age=31536000, immutable"
}

I omitted the handle_errors part because it certainly isn’t right. I’ll figure that out later.

3 Likes

FYI all of these are using exact paths, I think you probably meant to append a * at the end of each path (or for the first one, i.e. /, just omit the / entirely).

The closest equivalent of this to v2 is something like this:

handle_errors {
    root * /data/mastodon/public
    rewrite 500.html
    file_server
}

Might not work exactly the same way but it should at least serve 500.html if there’s any 4xx or 5xx error.

1 Like

Thank you.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.