Notice that your catch-all match/handle pair is ordered before the one with path_regexp .
This is because Caddy uses the length of the path matchers from the Caddyfile to determine the order. Since path_regexp isn’t path, it doesn’t take part in that sorting. It’s hard to know how to sort a regexp because it’s a rule-based thing, not just a simple string to compare. The assumption here is that a longer path is a more specific matcher in general.
Omitting /* is the correct solution. Really, omitting the matcher is the same as having specified * which means “all requests”. It’s more correct for what you want anyways. After doing that, you’ll see that it gets correctly sorted with the least specific, i.e. the implicit * (no matcher) is ordered last.
I can also suggest another approach for your config to reduce duplication a bit - you can combine your first two reverse_proxy directives with one matcher, since they both have the same destination:
A named matcher definition constitutes a matcher set . Matchers in a set are AND’ed together; i.e. all must match. For example, if you have both a header and path matcher in the set, both must match.
For most matchers that accept multiple values, those values are OR’ed; i.e. one must match in order for the matcher to match.
I think it’ll have to remain the way it was rather than consolidated; regex and path matchers can’t be OR’d and neither can two regex matchers. To do this you’d need one regex that covers both use cases - doable, for sure, but less understandable than having them separate.