Can't get CF-Connecting-IP to matcher to work

1. The problem I’m having:

CF-Connecting-IP headers matcher is not matching or sending request to reverse_proxy. I get a “Forbidden” response from the allowed IP (124.xx.xx.146 in the logs).

Even though the debug JSON request log shows the correct IP in the CF-Connecting-IP header the Caddy matcher is not matching on it and responds with a 403 “Forbidden” message.

2. Error messages and/or full log output:

The client_ip, X-Forwarded-For and CF-Connecting-IP all match the Caddy config header matcher IP

{
  "level": "info",
  "ts": 1744337723.1501834,
  "logger": "http.log.access.log0",
  "msg": "handled request",
  "request": {
    "remote_ip": "162.158.162.32",
    "remote_port": "28382",
    "client_ip": "124.xx.xx.146",
    "proto": "HTTP/2.0",
    "method": "GET",
    "host": "monitor.xxxx.com.au",
    "uri": "/",
    "headers": {
      "Cf-Visitor": [
        {
          "scheme": "https"
        }
      ],
      "Accept": [
        "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
      ],
      "Sec-Fetch-Site": [
        "none"
      ],
      "Accept-Encoding": [
        "gzip, br"
      ],
      "Cdn-Loop": [
        "cloudflare; loops=1"
      ],
      "Sec-Fetch-Dest": [
        "document"
      ],
      "Cf-Ray": [
        "92e6f9d01f0c5f59-SIN"
      ],
      "Accept-Language": [
        "en-GB,en;q=0.5"
      ],
      "User-Agent": [
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0"
      ],
      "X-Forwarded-Proto": [
        "https"
      ],
      "Sec-Fetch-User": [
        "?1"
      ],
      "Priority": [
        "u=0, i"
      ],
      "X-Forwarded-For": [
        "124.xx.xx.146"
      ],
      "Sec-Fetch-Mode": [
        "navigate"
      ],
      "Cf-Ipcountry": [
        "AU"
      ],
      "Cf-Connecting-Ip": [
        "124.xx.xx.146"
      ],
      "Alt-Used": [
        "monitor.xxxx.com.au"
      ],
      "Upgrade-Insecure-Requests": [
        "1"
      ]
    },
    "tls": {
      "resumed": false,
      "version": 772,
      "cipher_suite": 4865,
      "proto": "h2",
      "server_name": "monitor.xxxx.com.au"
    }
  },
  "bytes_read": 0,
  "user_id": "",
  "duration": 0.000025471,
  "size": 9,
  "status": 403,
  "resp_headers": {
    "Server": [
      "Caddy"
    ],
    "Alt-Svc": [
      "h3=\":443\"; ma=2592000"
    ],
    "Content-Type": [
      "text/plain; charset=utf-8"
    ]
  }
}

3. Caddy version:

v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=

4. How I installed and ran Caddy:

Docker compose

a. System environment:

Debian GNU/Linux 12 (bookworm)

b. Command:

docker compose up -d

c. Service/unit/compose file:

---

services:
  caddy:
    container_name: caddy
    image: ghcr.io/ilium007/caddy-microtech:latest
    ports:
      - 80:80
      - 443:443
    volumes:
      - ./caddy/data:/data
      - ./caddy/config:/config
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - ./caddy/usr/share/caddy:/usr/share/caddy
    environment:
      - ACME_AGREE=true
      - TZ=Australia/Sydney
    networks:
       - fp-digitalocean
    restart: unless-stopped

d. My complete Caddy config:

{
      #debug
      email support@xxxx.com.au

      servers {
        trusted_proxies cloudflare {
	  interval 12h
	  timeout 15s
        }
      }
}


  monitor.xxxx.com.au {
    tls {
        dns cloudflare xxxx
    }

    log {
        output stdout
        level DEBUG
        format json
    }

    @allowOnly {
      header CF-Connecting-IP 124.xx.xx.146
    }

    reverse_proxy @allowOnly uptime-kuma:3001

    respond "Forbidden" 403
  }

5. Links to relevant resources:

I also tried the client_ip matcher on the IP 124.xx.xx.146 and it fails as well.

The directive order of respond is higher than reverse_proxy, regardless of the matcher, so it’ll be executed first.

You need to use the handle directive to make mutually exclusive execution paths.

Also, I recommend reading this.

1 Like

That worked - thats for the pointer! I added the handler and it fixed the directive ordering.

monitor.xxxx.com.au {
        tls {
                dns cloudflare xxxx
        }

        log {
                output stdout
                level DEBUG
                format json
        }

        @allowOnly {
          header CF-Connecting-IP 124.xx.xx.146
        }

        handle @allowOnly {
                reverse_proxy @allowOnly uptime-kuma:3001
        }

        respond "Forbidden" 403
}