Nextcloud seems to need http/1.1?

The tool you wrote, and the goal behind it (block whole ranges like Google), is exactly what fail2ban already does, and it covers the range part too, which is the piece you are hand-rolling.

Beyond the sorting bug @tflidd already caught, two things about emitting iptables -A INPUT -s IP -j DROP will bite you:

  • The threshold of “more than 9 requests” is far below normal traffic. A single browser opening one page pulls dozens of assets at once, a Nextcloud desktop or mobile sync is many requests in a burst, and a legitimate crawler passes 9 easily. At > 9 you ban yourself and your real clients, not just scanners. fail2ban’s findtime/maxretry let you say “9 hits in 10 seconds”, which is an actual abuse signal instead of a raw total.
  • The rules never expire. -A INPUT -s IP -j DROP stays forever and the list only grows, with no unban when that address turns out to be a dynamic IP a real user gets next week. fail2ban ages bans out (bantime) and persists them across restarts for you.

On blocking ranges specifically: you do not want one DROP rule per IP for a /16, you want the whole range as a single entry. With nftables that is nft add element ... { 8.8.0.0/16 } into a named set with interval flags, one atomic rule for the entire block. I wrote up a working fail2ban setup that does precisely this, parameterising the CIDR mask at the jail level, so you feed it a plain list of ranges in CIDR notation and each is blocked as one unit: Exception HMAC does not match - #39 by ernolf

That is the by-hand range-blocking you are building, with expiry and persistence for free.

One caveat on Google: if those sites should stay findable, blocking Googlebot’s ranges deindexes them. If they are throwaway, that is fine, but then not serving them at all is simpler than curating a Google blocklist.

And for the scanner noise on Nextcloud itself, fail2ban on the access log (ban on request rate or on known-bad probe paths) is still the right tool, as covered above.