Geofilter and blocked paths with white listed ip, static html returning zero bytes

1. The problem I’m having:

I have a simple geofilter to block any country other that AU (Australia).
I have 2 restricted folders /analytics and /help to everyone except 1 ip

Both /analytics and /help have static html pages and no errors are return, just a zero byte page.

These static pages were working today before I discovered I have incorrectly configured the geofilter (didn’t have the not maxmind_geolocation).

Reverted this change but still the static pages return zero size.

What is wrong with my config?

2. Error messages and/or full log output:

{
	"level": "info",
	"ts": 1728446643.680025,
	"logger": "http.log.access.log0",
	"msg": "NOP",
	"request": {
		"remote_ip": "139.99.231.218",
		"remote_port": "57365",
		"client_ip": "139.99.231.218",
		"proto": "HTTP/2.0",
		"method": "GET",
		"host": "opsdev.systems368.com",
		"uri": "/help/fault_type_help.html",
		"headers": {
			"Accept-Language": [
				"en;q=0.6"
			],
			"Sec-Ch-Ua-Mobile": [
				"?0"
			],
			"Sec-Ch-Ua-Platform": [
				"\"Windows\""
			],
			"Sec-Fetch-Site": [
				"none"
			],
			"Accept-Encoding": [
				"gzip, deflate, br, zstd"
			],
			"Cache-Control": [
				"max-age=0"
			],
			"Upgrade-Insecure-Requests": [
				"1"
			],
			"Sec-Fetch-Mode": [
				"navigate"
			],
			"Sec-Fetch-User": [
				"?1"
			],
			"Sec-Ch-Ua": [
				"\"Brave\";v=\"129\", \"Not=A?Brand\";v=\"8\", \"Chromium\";v=\"129\""
			],
			"Accept": [
				"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8"
			],
			"Sec-Fetch-Dest": [
				"document"
			],
			"Cookie": [
				"REDACTED"
			],
			"Priority": [
				"u=0, i"
			],
			"User-Agent": [
				"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36"
			],
			"Sec-Gpc": [
				"1"
			]
		},
		"tls": {
			"resumed": false,
			"version": 772,
			"cipher_suite": 4865,
			"proto": "h2",
			"server_name": "opsdev.systems368.com"
		}
	},
	"bytes_read": 0,
	"user_id": "",
	"duration": 0.000063194,
	"size": 0,
	"status": 0,
	"resp_headers": {
		"Server": [
			"Caddy"
		],
		"Alt-Svc": [
			"h3=\":443\"; ma=2592000"
		]
	}
}

3. Caddy version:

v2.8.4 h1:q3pe0wpBj1OcHFZ3n/1nl4V4bxBrYoSoab7rL9BMYNk=

4. How I installed and ran Caddy:

a. System environment:

Ubuntu 24.04.1 LTS

b. Command:

systemctl start caddy.service

c. Service/unit/compose file:

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=caddy
ExecStart=/usr/bin/caddy run --environ --config /etc/caddy/Caddyfile
ExecReload=/usr/bin/caddy reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

d. My complete Caddy config:

(common) {
        encode zstd gzip
}

opsdev.systems368.com {

         import common

        @geofilter {
            not maxmind_geolocation {
            db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
            allow_countries AU
             }
            }
          respond @geofilter "Whoopsie...."  403

        file_server @geofilter {
           root  /var/www/html
           }

        @blocked {
             path /analytics/* /help/*
             not remote_ip 139.99.231.218
            }
        respond @blocked "The Computer Says No..." 403


        reverse_proxy /rpc/* localhost:3000
        reverse_proxy /ws localhost:7890
        reverse_proxy /places/* localhost:5000
        reverse_proxy /authusers/* localhost:4000


        log {
                output file /var/log/caddy/caddy.log {
                        roll_size 100mb
                        roll_keep 2
                        roll_keep_for 48h
                      }
             }


}

5. Links to relevant resources:

Please run caddy fmt -w on your config. Your indentation is very messy, so it’s hard to follow what your config is trying to do.

This means that Caddy did not run any handlers to write a response for that request. Your matchers result in the request falling through without matching any handler.

Keep in mind, directives are sorted according to the directive order, which means all respond will be tried before all reverse_proxy, before all file_server.

1 Like

Okay, I’ve run that. the new config is
‘’’
(common) {
encode zstd gzip
}

opsdev.systems368.com {
import common

    @geofilter {
            not maxmind_geolocation {
                    db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
                    allow_countries AU
            }
    }
    respond @geofilter "Whoopsie...." 403

    file_server @geofilter {
            root /var/www/html
    }

    @blocked {
            path /analytics/* /help/*
            not remote_ip 139.99.231.218
    }
    respond @blocked "The Computer Says No..." 403

    reverse_proxy /rpc/* localhost:3000
    reverse_proxy /ws localhost:7890
    reverse_proxy /places/* localhost:5000
    reverse_proxy /authusers/* localhost:4000

    log {
            output file /var/log/caddy/caddy.log {
                    roll_size 100mb
                    roll_keep 2
                    roll_keep_for 48h
            }
    }

}

‘’’

Use triple backticks ``` (the key to the left of 1 on US keyboards), not triple quotes for code blocks. Or just click the </> button.

But like I said, you need to think through your matchers and the order in which your directives run. I don’t have a clear understanding of your goal, but that request clearly didn’t match any of your existing matchers so it fell through unhandled.

1 Like

apologies,

(common) {
        encode zstd gzip
}

opsdev.systems368.com {
        import common

        @geofilter {
                not maxmind_geolocation {
                        db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
                        allow_countries AU
                }
        }
        respond @geofilter "Whoopsie...." 403

        file_server @geofilter {
                root /var/www/html
        }

        @blocked {
                path /analytics/* /help/*
                not remote_ip 139.99.231.218
        }
        respond @blocked "The Computer Says No..." 403

        reverse_proxy /rpc/* localhost:3000
        reverse_proxy /ws localhost:7890
        reverse_proxy /places/* localhost:5000
        reverse_proxy /authusers/* localhost:4000

        log {
                output file /var/log/caddy/caddy.log {
                        roll_size 100mb
                        roll_keep 2
                        roll_keep_for 48h
                }
        }
}

Okay, to give you a better understanding:
I have a flutter app that is working well despite the issues I am having with the static html.
the /analytics is running goaccess so I can monitor who/what i accessing my api service.
the /help is the internal help information for users, and I don’t really want that exposed to the whole internet/siders/crawlers et al.

I absolutely love Caddy (been working with it for since it’s inception, and I am stretching my capabilities to get it to do what I want… and at the same time build out my app…)… happy to engage a professional to make it happen

Whew, this logic is a bit hard to follow.

You’re responding 403 to this matcher - and then file serving to the same matcher? Surely that can’t be the behaviour you want, right?

I wonder if you wouldn’t be better off, logically speaking, separating things into mutually exclusive handle blocks. Something like:

example.com {
  @australia maxmind_geolocation {
    db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
    allow_countries AU
  }
  handle @australia {
    # Do stuff in this section for Australian users
  }
  handle {
    # Do stuff in this section for everyone else
  }

  # Do stuff for all users outside the handles
}

Remember that the point of all code and config is not for the benefit of the machine to understand it; the purpose is for the human to be able to read and understand what you’re making the machine do. Structures like the above can help make it more obvious how you’re handling things.

3 Likes

Thanks for the advice and your approach is a good one. At the moment, I’ve restructured using snippets and it now seems to work.

config now looks likes this:

(geofilter) {
    @geofilter {
      not maxmind_geolocation {
        db_path "/usr/share/GeoIP/GeoLite2-Country.mmdb"
        allow_countries AU
      }
    }
    respond @geofilter "Whoopsie...." 403
}



(block_paths) {
    @blockedPaths {
        path  /analytics/* /help/*
        not remote_ip 139.99.231.218
    }
    respond @blockedPaths "The Computer Says No..." 403
}

(common) {
        encode gzip
}



opsdev.systems368.com {
    import common
    import geofilter
    import block_paths

    root * /var/www/html
	file_server
	
    reverse_proxy /rpc/* localhost:3000
    reverse_proxy /ws localhost:7890
    reverse_proxy /places/* localhost:5000
    reverse_proxy /authusers/* localhost:4000

    log {
         output file /var/log/caddy/caddy.log {
                roll_size 100mb
                roll_keep 2
                roll_keep_for 48h
                }
        }
}


1 Like

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