Caddy module

Caddy module to block bots, VPNs and Tor

Add one ipscanner directive to a site block and every visitor is checked before reverse_proxy. It starts in monitor mode and blocks nothing until you switch.

Free, MIT licenseCaddy 2.8+

Traffic classes

What it can block

Every visitor gets one class. Block a class from the dashboard, or list it in block_classes.

  • Humanhuman
  • Verified botverified_botAlways passes
  • AI agentai_agent
  • Malicious automationmalicious_automationEnforce default
  • Tortor
  • VPNvpn
  • Proxyproxy
  • Hostinghosting
  • Private relayrelay

Setup

Live in four steps

  1. 1

    Get an API key

    Caddy reads it from IPSCANNER_API_KEY, or from api_key in the Caddyfile.

    Get a free API key
    shell
    export IPSCANNER_API_KEY=<your API key>
  2. 2

    Build Caddy with the module

    With xcaddy or in Docker. Needs Caddy 2.8 or later.

    xcaddy build --with github.com/ipscanner/ipscanner-caddy
    ./caddy list-modules | grep ipscanner
  3. 3

    Add a site in the dashboard

    The site holds the policy and shows the traffic. Copy its Site ID.

    Add a site
    example.comCaddyMonitor

    Site ID

    site_4fQ8nZ2kLm7xR1vT9cBw

  4. 4

    Add the directive

    No global order line: it runs before basic_auth and reverse_proxy.

    Caddyfile
    example.com {
    	ipscanner {
    		site_id site_4fQ8nZ2kLm7xR1vT9cBw
    	}
    	reverse_proxy localhost:8080
    }

Without a site

Caddyfile or JSON

Set the mode and blocked classes in the config. A matcher such as ipscanner /app/* limits the check to some paths.

example.com {
	ipscanner {
		mode enforce
		block_classes malicious_automation tor
	}
	reverse_proxy localhost:8080
}

Placeholders

Use the verdict in logs and matchers

Set by the directive for log_append, vars matchers and headers.

Placeholder
{http.vars.ipscanner.status}ok, error, timeout, backoff, skipped
{http.vars.ipscanner.class}Traffic class, set when the status is ok
{http.vars.ipscanner.action}allow, flag, block, would_flag, would_block
Caddyfile
log_append ipscanner_class {http.vars.ipscanner.class}
log_append ipscanner_action {http.vars.ipscanner.action}

@flagged vars ipscanner.action flag
request_header @flagged X-Review 1

Client IP

Behind a load balancer or CDN

The module checks the client IP Caddy resolved. Trust your proxies, and trusted_proxies_strict stops a visitor from adding a fake hop.

Caddyfile
{
	servers {
		trusted_proxies static 10.0.0.0/8
		trusted_proxies_strict
	}
}

Settings

Subdirectives

All optional. The JSON config uses the same names.

SubdirectiveDefault
api_keyAPI key. Without one, every request passes.{env.IPSCANNER_API_KEY}
site_idDashboard site that sets mode and policy.Empty
modeMonitor or enforce. With a site, only monitor applies.monitor
block_classesBlocked in enforce mode without a site.malicious_automation
timeoutBudget for one check.1.5s
cache_ttlHow long a verdict is reused per visitor.10m
cache_sizeVerdict cache entries.10000
policy_ttlHow long the site policy is cached.30s
skip_pathsRegex of paths that are never checked.Static assets
block_messageText of the 403 page.Blocked by IPScanner edge guard.
debugDebug headers and one log line per decision.Off
api_urlAPI base URL.https://ipscanner.io

Always passes

What it never blocks

  • Verified search and AI crawlers.
  • Private and loopback addresses, and OPTIONS requests.
  • Static assets and anything in skip_paths.
  • Every request, when IPScanner is slow or down.

Questions

Caddy module FAQ

No. Monitor mode marks a request would_block and lets it through. Switch the site or the Caddyfile to enforce when the log looks right.

Start in monitor mode

Build Caddy with the module, read a week of decisions, then block.