bug(ingress): host matching is case-sensitive, mixed-case Host/hostname misroutes (RFC 4343)

Author: winklemadCreated Aug 11, 2026Updated Aug 23, 2026

Describe the bug

Ingress rule matching is case-sensitive, but hostnames are case-insensitive (RFC 4343; RFC 3986 §3.2.2 — "the host subcomponent is case-insensitive"). matchHost in ingress/ingress.go compares with == and strings.HasSuffix without normalizing case, and there is no case-normalization anywhere else in the ingress package:

go
func matchHost(ruleHost, reqHost string) bool {
	if ruleHost == reqHost {
		return true
	}
	if strings.HasPrefix(ruleHost, "*.") {
		toMatch := strings.TrimPrefix(ruleHost, "*")
		return strings.HasSuffix(reqHost, toMatch)
	}
	return false
}

So any case difference between a rule's hostname and the request Host makes the request skip the intended rule and fall through to the catch-all (503 or the wrong origin).

This is reachable two ways:

  • proxy/proxy.go passes req.Host (which can legitimately be mixed-case) straight into FindMatchingRule un-lowercased.
  • validateHostname accepts mixed-case hostnames without lowercasing them, so a config such as hostname: MyApp.example.com is accepted but never matches myapp.example.com.

To Reproduce

cloudflared tunnel ingress rule (which passes url.Hostname() straight to FindMatchingRule) shows it, with no network needed. Given a config:

yaml
ingress:
  - hostname: myapp.example.com
    service: http://localhost:8080
  - service: http_status:404
$ cloudflared tunnel ingress rule https://MyApp.example.com/

reports the catch-all rule (http_status:404) instead of myapp.example.com. A unit test on matchHost/FindMatchingRule with a mixed-case host fails on main.

Expected behavior

Hostnames match case-insensitively, like every other HTTP router (nginx, Caddy, Envoy, Traefik).

Fix

Normalize case in matchHost (lower-case both sides before comparing). I have a small patch + tests ready and would like to open a PR.