Is there a proper or better way to order multiple handle/handle_path directives?

I’m currently configuring caddy and need to utilize multiple handle and handle path directives. I believe (haven’t tested yet) both of these work, but curious which is better (if one is better than the other)

Option 1: default handle, then paths nested inside with a catchall at the end:

http://{{ domain }}, https://{{ domain }} {
        log {
                output stdout
        }
        @websockets {
                header Connection *Upgrade*
                header Upgrade websocket
        }
        handle {
                handle_path /rest* {
                        reverse_proxy 127.0.0.1:{{ rpc_port }} {
                                header_up Host localhost
                        }
                }
                handle_path /grpc* {
                        reverse_proxy 127.0.0.1:{{ rpc_port }} {
                                header_up Host localhost
                        }
                }
                handle /websocket {
                        reverse_proxy @websockets 127.0.0.1:{{ ws_port }} {
                                header_up Host localhost
                        }
                }
                handle {
                        reverse_proxy 127.0.0.1:{{ rpc_port }} {
                                header_up Host localhost
                        }
                        reverse_proxy @websockets 127.0.0.1:{{ ws_port }} {
                                header_up Host localhost
                        }
                }
        }
}

Option 2: individual handle/handle_paths and the catchall at the end:

http://{{ domain }}, https://{{ domain }} {
        log {
                output stdout
        }
        @websockets {
                header Connection *Upgrade*
                header Upgrade websocket
        }
        handle_path /rest* {
                reverse_proxy 127.0.0.1:{{ rpc_port }} {
                        header_up Host localhost
                }
        }
        handle_path /grpc* {
                reverse_proxy 127.0.0.1:{{ rpc_port }} {
                        header_up Host localhost
                }
        }
        handle /websocket {
                reverse_proxy @websockets 127.0.0.1:{{ ws_port }} {
                        header_up Host localhost
                }
        }
        handle {
                reverse_proxy 127.0.0.1:{{ rpc_port }} {
                        header_up Host localhost
                }
                reverse_proxy @websockets 127.0.0.1:{{ ws_port }} {
                        header_up Host localhost
                }
        }
}

This is actually a bit of a trick question! The order of your handle and handle_path directives actually (mostly) doesn’t matter. Only the nesting does.

Your first option simply groups all your handles under one first level handle, which isn’t strictly necessary, but doesn’t cause any harm and you might prefer for config organisation purposes.

Generally speaking, the order you write most Caddy config doesn’t actually matter because the Caddyfile directive order dictates how modules are laid out when the config is adapted to Caddy’s native JSON.

Check out specifically the part about the Caddyfile sorting algorithm where it goes into detail about exactly how things get processed by default.

In your case, with respect to handle and handle_path (which as a unique case are two “directives” which are evaluated with identical priority), these are sorted by path specificity - which is to say that that the longest path is evaluated first. After all specific path matchers are exhausted from longest to shortest, any non-path (i.e. named) matchers are checked - amusingly as the exception to the rule of thumb above, in the order they’re specified in the Caddyfile. The handle without a matcher goes last.

If you want to have manual control over the ordering of directives, which can be useful sometimes, you would want a route, and you can refer to the sorting algorithm section of the Directives doc to see the full logic used for all the above.

1 Like

Thanks and good to know a bit more about this. In this case, I don’t think ordering will really matter since everything will actually have a request matcher but I’ll keep route in mind for future use cases where order matters.