Combining two filter

1. The problem I’m having:

I have some services which I proxy using caddy. Some of these services should only be accessible from within my network, some should be exposed to the internet. I have set up a working localSubnets filter for the first one:

(localSubnets) {
	@localSubnets remote_ip private_ranges

Now I want to harden the security for the exposed services by implementing geoblocking and only allowing traffic from Germany. I was able to create a filter for that using the maxmind module:

(DEgeofilter) {
	@DEgeofilter {
		maxmind_geolocation {
			db_path "/etc/caddy/GeoLite2-Country.mmdb"
			allow_countries DE

However, this denies access from within my local network, probably because these IPs are not “German” IPs. Because of that, I currently use this redundant setup: {
	import DEgeofilter
	import localSubnets

	handle @DEgeofilter {

	handle @localSubnets {

	respond 403

Now my question: How can I combine these two into one filter?

I would like to have something like this (which sadly does not work):

(combinedFilter) {
	@combinedFilter {
		remote_ip private_ranges
		maxmind_geolocation {
			db_path "/etc/caddy/GeoLite2-Country.mmdb"
			allow_countries DE
} {
	import combinedFilter

	handle @combinedFilter {

	respond 403

2. Error messages and/or full log output:


3. Caddy version:

/srv # caddy version
v2.7.6 h1:w0NymbG2m9PcvKWsrXO6EEkY9Ru4FJK8uQbYcev1p3A=

4. How I installed and ran Caddy:

a. System environment:

Docker compose on debian 12

b. Command:


c. Service/unit/compose file:


FROM caddy:2.7.6-builder AS builder

RUN xcaddy build --with

FROM caddy:2.7.6

COPY --from=builder /usr/bin/caddy /usr/bin/caddy

Docker compose file:

version: "3.7"

    build: .
    container_name: caddy
    restart: unless-stopped
      - NET_ADMIN
      - "80:80"
      - "443:443"
      - "443:443/udp"
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./site:/srv
      - ./data:/data
      - ./config:/config
      - ./GeoLite2-Country.mmdb:/etc/caddy/GeoLite2-Country.mmdb

d. My complete Caddy config:

See above

5. Links to relevant resources:


When putting two matchers inside of a named matcher, matchers of a different type are ANDed together. There’s no way to OR them together traditionally.

It is possible to OR them abusing De Morgan’s Laws with the not matcher, but this is super not nice to use.

We have the expression matcher which can allow you to write more complex boolean expressions, but the problem is you’re using a matcher plugin, and I don’t think that plugin implements the interfaces necessary to allow it to work within CEL expressions. If it did though, it would be possible to write something like this:

@isDEorLocal `remote_ip('','','','','fd00::/8','::1') || maxmind_geolocation({'db_path': '/etc/caddy/GeoLite2-Country.mmdb', 'allow_countries': ['DE'])`

All that said, I think the approach using two handle is correct. The first matching handle block will handle the request. If it doesn’t match, it just falls through to the next one.

Are you sure it’s not working correctly? How are you testing? What IP address does the request have when it “does the wrong thing”?

Thanks for the reply.

It’s not that my current config isn’t working, I just thought there would be a nicer and / or simpler way of doing it. But I’ll just keep using the two handle then.

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.