kataras/iris · error

redirect match: loop detected: pattern: %s vs target: %s

Error message

redirect match: loop detected: pattern: %s vs target: %s

What it means

The rewrite parser rejects redirect rules whose regex pattern also matches its own target, because that creates an infinite redirect loop: once the client is sent to the target, the rule would match it again. The check compiles the pattern and tests it against the target string, failing with this error when they overlap.

Source

Thrown at middleware/rewrite/rewrite.go:304

	codeStr, pattern, target := parts[0], parts[1], parts[2]

	for i, ch := range codeStr {
		if !isDigit(ch) {
			return nil, fmt.Errorf("redirect match: status code digits: %s [%d:%c]", codeStr, i, ch)
		}
	}

	code, err := strconv.Atoi(codeStr)
	if err != nil {
		// this should not happen, we check abt digit
		// and correctly position the error too but handle it.
		return nil, fmt.Errorf("redirect match: status code digits: %s: %v", codeStr, err)
	}

	regex := regexp.MustCompile(pattern)
	if regex.MatchString(target) {
		return nil, fmt.Errorf("redirect match: loop detected: pattern: %s vs target: %s", pattern, target)
	}

	v := &redirectMatch{
		code:              code,
		pattern:           regex,
		target:            target,
		noRedirect:        code <= 0,
		isRelativePattern: pattern[0] == '/', // search by path.
	}

	return v, nil
}

func isDigit(ch rune) bool {
	return '0' <= ch && ch <= '9'
}

func getPort(hostport string) string { // returns :port, note that this is only called on non-loopbacks.

View on GitHub (pinned to 7bedaf55a0)

Solutions

  1. Make the pattern more specific so it cannot match the target, e.g. anchor it: '^/old/(.*)$'.
  2. Change the target to a path outside the pattern's domain (different prefix or TLD).
  3. Use a negative-construct or narrower regex that excludes the destination path.
  4. Split broad rules into several narrow rules whose targets do not self-match.

Example fix

// before
"301 /(.*) /new/$1" // pattern matches /new/... target -> loop
// after
"301 ^/old/(.*)$ /new/$1"
Defensive patterns

Strategy: validation

Validate before calling

re := regexp.MustCompile(pattern)
if re.MatchString(target) {
    return fmt.Errorf("redirect rule %q would loop: target %q matches pattern", pattern, target)
}

Try / catch

if err := rewrite.New(lines); err != nil {
    if strings.Contains(err.Error(), "loop detected") {
        log.Fatalf("fix redirect pattern %v", err)
    }
}

Prevention

When it happens

Trigger: Calling rewrite.New with a line like '301 /(.*) /$1' or any pattern that matches the replacement target, e.g. pattern '/blog' with target '/blog/new'.

Common situations: Broad catch-all redirect patterns (match everything) combined with targets that retain the matched path; migrating a site where old and new URLs share a prefix.

Related errors


AI-assisted analysis of kataras/iris@7bedaf55a0 (2026-08-30). Data as JSON: /api/errors/9dfa2996809a7335. Report an issue: GitHub.