caddyserver/caddy · error

directive '%s' is not an ordered HTTP handler, so it cannot

Error message

directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option

What it means

buildSubroute was asked to sort a list of directives, but one directive in the list is not in the global `directive_order`. Sorting is only possible for known, ordered HTTP handlers, so the directive must appear inside an explicit `route` block (which preserves order) or be registered in the order global option.

Source

Thrown at caddyconfig/httpcaddyfile/httptype.go:1428

			}
		}
		if len(route.MatcherSetsRaw) > 0 || len(route.HandlersRaw) > 0 {
			routeList = append(routeList, route)
		}
	} else {
		routeList = append(routeList, subroute.Routes...)
	}

	return routeList
}

// buildSubroute turns the config values, which are expected to be routes
// into a clean and orderly subroute that has all the routes within it.
func buildSubroute(routes []ConfigValue, groupCounter counter, needsSorting bool) (*caddyhttp.Subroute, error) {
	if needsSorting {
		for _, val := range routes {
			if !slices.Contains(directiveOrder, val.directive) {
				return nil, fmt.Errorf("directive '%s' is not an ordered HTTP handler, so it cannot be used here - try placing within a route block or using the order global option", val.directive)
			}
		}

		sortRoutes(routes)
	}

	subroute := new(caddyhttp.Subroute)

	// some directives are mutually exclusive (only first matching
	// instance should be evaluated); this is done by putting their
	// routes in the same group
	mutuallyExclusiveDirs := map[string]*struct {
		count     int
		groupName string
	}{
		// as a special case, group rewrite directives so that they are mutually exclusive;
		// this means that only the first matching rewrite will be evaluated, and that's
		// probably a good thing, since there should never be a need to do more than one

View on GitHub (pinned to 50e54ee279)

Solutions

  1. Wrap the directive in a `route { }` block so no ordering is inferred
  2. Add `order <directive> before <known>` (or after) to the global options block
  3. If the directive comes from a plugin, confirm the plugin registers itself in directiveOrder or check its docs for the required order line
  4. Verify spelling of the directive name — typos also surface here

Example fix

# before
example.com {
  my_plugin_directive
}
# after
{
  order my_plugin_directive before respond
}
example.com {
  my_plugin_directive
}
Defensive patterns

Strategy: validation

Validate before calling

// Before adapting plugin configs, verify directive is orderable
known := directiveOrderSet()
if !known[dir] && !insideRouteBlock(dir) {
    return fmt.Errorf("declare order for %s or wrap in route", dir)
}

Type guard

func isOrderedDirective(d string) bool {
    return slices.Contains(directiveOrder, d)
}

Prevention

When it happens

Trigger: Using a directive inside a context where its relative order matters and it is not in the default order list — commonly a third-party plugin directive used at site level, or a directive placed where the adapter sorts (e.g. directly in a site block) when it has no default ordering. The fix paths are exactly the message: wrap it in a `route` block or add `order <directive> before|after <other>` to the global options.

Common situations: Installing a plugin (e.g. a custom handler) and using its directive without declaring order; new directives added in newer Caddy versions while an old `order` global overrides defaults; using `handle_path`-style grouping with unknown directives.

Related errors


AI-assisted analysis of caddyserver/caddy@50e54ee279 (2026-08-15). Data as JSON: /api/errors/27dcce441a8c5b66. Report an issue: GitHub.