{"id":47710833,"url":"https://github.com/fuomag9/caddy-blocker-plugin","last_synced_at":"2026-04-02T18:31:40.790Z","repository":{"id":340040608,"uuid":"1164278954","full_name":"fuomag9/caddy-blocker-plugin","owner":"fuomag9","description":"A Caddy v2 HTTP middleware that blocks or allows requests","archived":false,"fork":false,"pushed_at":"2026-03-19T16:52:53.000Z","size":144,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-03-20T00:20:46.888Z","etag":null,"topics":["blocker","caddy","caddy-plugin","middleware","plugin","requests"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/fuomag9.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-22T22:20:43.000Z","updated_at":"2026-03-19T16:52:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/fuomag9/caddy-blocker-plugin","commit_stats":null,"previous_names":["fuomag9/caddy-blocker-plugin"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/fuomag9/caddy-blocker-plugin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuomag9%2Fcaddy-blocker-plugin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuomag9%2Fcaddy-blocker-plugin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuomag9%2Fcaddy-blocker-plugin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuomag9%2Fcaddy-blocker-plugin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/fuomag9","download_url":"https://codeload.github.com/fuomag9/caddy-blocker-plugin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/fuomag9%2Fcaddy-blocker-plugin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31312916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-02T12:59:32.332Z","status":"ssl_error","status_checked_at":"2026-04-02T12:54:48.875Z","response_time":89,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blocker","caddy","caddy-plugin","middleware","plugin","requests"],"created_at":"2026-04-02T18:31:40.614Z","updated_at":"2026-04-02T18:31:40.772Z","avatar_url":"https://github.com/fuomag9.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# caddy-blocker-plugin\n\nA [Caddy v2](https://caddyserver.com) HTTP middleware that blocks or allows requests based on:\n\n- **IP address** — exact match\n- **CIDR range** — e.g. `10.0.0.0/8`\n- **ASN** — Autonomous System Number via MaxMind database\n- **Country** — ISO 3166-1 alpha-2 code via MaxMind database\n- **Continent** — AF, AN, AS, EU, NA, OC, SA via MaxMind database\n\nRules can be freely mixed. **Allow rules always win over block rules**, so you can block an entire country or ASN while explicitly whitelisting specific IPs from it.\n\nIPv4 and IPv6 are fully supported throughout.\n\n---\n\n## Installation\n\n### With xcaddy (recommended)\n\n```bash\nxcaddy build --with github.com/fuomag9/caddy-blocker-plugin\n```\n\n### Standalone binary\n\n```bash\ngit clone https://github.com/fuomag9/caddy-blocker-plugin\ncd caddy-blocker-plugin\ngo build -o caddy ./cmd/caddy\n```\n\nVerify the plugin is loaded:\n\n```bash\n./caddy list-modules | grep blocker\n# http.handlers.blocker\n```\n\n---\n\n## GeoIP / ASN databases\n\nCountry, continent, and ASN blocking require MaxMind `.mmdb` database files. The free\n[GeoLite2](https://dev.maxmind.com/geoip/geolite2-free-geolocation-data) databases work out of the box. Commercial [GeoIP2](https://www.maxmind.com/en/geoip2-databases) databases use the same format and are also supported.\n\nDownload the relevant databases from MaxMind and point the plugin at them with `geoip_db` and `asn_db`.\n\nIf a database file is missing or unreadable, the corresponding rule types (country/continent or ASN) are **silently skipped** and other rules continue to work (fail-open).\n\n---\n\n## Configuration\n\nThe plugin is configured as a JSON handler with the name `\"blocker\"` inside a Caddy route.\n\n### Full JSON reference\n\n```json\n{\n  \"handler\": \"blocker\",\n\n  \"geoip_db\": \"/usr/share/GeoIP/GeoLite2-Country.mmdb\",\n  \"asn_db\":   \"/usr/share/GeoIP/GeoLite2-ASN.mmdb\",\n\n  \"block_countries\":   [\"CN\", \"RU\", \"KP\"],\n  \"block_continents\":  [\"AF\"],\n  \"block_asns\":        [12345, 67890],\n  \"block_cidrs\":       [\"192.0.2.0/24\", \"198.51.100.0/24\"],\n  \"block_ips\":         [\"203.0.113.1\"],\n\n  \"allow_countries\":   [\"US\"],\n  \"allow_continents\":  [],\n  \"allow_asns\":        [11111],\n  \"allow_cidrs\":       [\"10.0.0.0/8\"],\n  \"allow_ips\":         [\"1.2.3.4\"],\n\n  \"trusted_proxies\":   [\"127.0.0.1\", \"10.0.0.0/8\"],\n  \"fail_closed\":       false,\n\n  \"response_status\":   403,\n  \"response_body\":     \"\u003ch1\u003eAccess Denied\u003c/h1\u003e\",\n  \"response_headers\":  { \"Content-Type\": \"text/html; charset=utf-8\" },\n  \"redirect_url\":      \"\"\n}\n```\n\n### Field reference\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `geoip_db` | string | — | Path to a GeoLite2/GeoIP2 Country or City `.mmdb` file |\n| `asn_db` | string | — | Path to a GeoLite2/GeoIP2 ASN `.mmdb` file |\n| `block_countries` | []string | — | ISO 3166-1 alpha-2 country codes to block (e.g. `\"CN\"`) |\n| `block_continents` | []string | — | Continent codes to block: `AF` `AN` `AS` `EU` `NA` `OC` `SA` |\n| `block_asns` | []uint | — | Autonomous System Numbers to block |\n| `block_cidrs` | []string | — | CIDR ranges to block |\n| `block_ips` | []string | — | Individual IP addresses to block |\n| `allow_countries` | []string | — | Country codes to allow (wins over block rules) |\n| `allow_continents` | []string | — | Continent codes to allow (wins over block rules) |\n| `allow_asns` | []uint | — | ASNs to allow (wins over block rules) |\n| `allow_cidrs` | []string | — | CIDR ranges to allow (wins over block rules) |\n| `allow_ips` | []string | — | Individual IPs to allow (wins over block rules) |\n| `trusted_proxies` | []string | — | IPs or CIDRs of trusted reverse proxies for `X-Forwarded-For` |\n| `fail_closed` | bool | `false` | If `true`, block requests when client IP cannot be determined from trusted proxy headers |\n| `response_status` | int | `403` | HTTP status code for blocked responses |\n| `response_body` | string | `\"Forbidden\"` | Response body for blocked responses (plain text or HTML) |\n| `response_headers` | map[string]string | — | Extra headers added to blocked responses |\n| `redirect_url` | string | — | If set, blocked requests receive a `302` redirect here instead of a body response |\n\n---\n\n## Rule evaluation\n\nFor every request:\n\n1. **Extract client IP** from `RemoteAddr`. If the direct connection comes from a `trusted_proxies` address, `X-Forwarded-For` is parsed from right to left and the nearest non-trusted IP is used.\n2. **Indeterminate client IP** — if no usable client IP is found, requests pass through by default; set `fail_closed: true` to block instead.\n3. **Check allow rules** — if any allow rule matches, the request passes immediately to the next handler (block rules are not evaluated).\n4. **Check block rules** — if any block rule matches, the configured block response is returned.\n5. **Default** — no rules matched, request passes through.\n\n---\n\n## Examples\n\n### Block a country, allow one IP from it\n\n```json\n{\n  \"handler\": \"blocker\",\n  \"geoip_db\": \"/usr/share/GeoIP/GeoLite2-Country.mmdb\",\n  \"block_countries\": [\"CN\"],\n  \"allow_ips\": [\"1.2.3.4\"],\n  \"response_status\": 403,\n  \"response_body\": \"Access denied.\"\n}\n```\n\n`1.2.3.4` passes even if its country resolves to `CN`.\n\n### Block an ASN but whitelist a CIDR from it\n\n```json\n{\n  \"handler\": \"blocker\",\n  \"asn_db\": \"/usr/share/GeoIP/GeoLite2-ASN.mmdb\",\n  \"block_asns\": [12345],\n  \"allow_cidrs\": [\"203.0.113.0/24\"],\n  \"response_status\": 403\n}\n```\n\n### Redirect blocked visitors\n\n```json\n{\n  \"handler\": \"blocker\",\n  \"geoip_db\": \"/usr/share/GeoIP/GeoLite2-Country.mmdb\",\n  \"block_continents\": [\"AF\", \"AS\"],\n  \"redirect_url\": \"https://example.com/not-available\"\n}\n```\n\n### Full Caddy JSON config\n\n```json\n{\n  \"apps\": {\n    \"http\": {\n      \"servers\": {\n        \"srv0\": {\n          \"listen\": [\":8080\"],\n          \"routes\": [\n            {\n              \"handle\": [\n                {\n                  \"handler\": \"blocker\",\n                  \"geoip_db\": \"/usr/share/GeoIP/GeoLite2-Country.mmdb\",\n                  \"asn_db\":   \"/usr/share/GeoIP/GeoLite2-ASN.mmdb\",\n                  \"block_countries\": [\"CN\", \"RU\", \"KP\"],\n                  \"block_asns\": [12345],\n                  \"allow_ips\": [\"1.2.3.4\"],\n                  \"trusted_proxies\": [\"127.0.0.1\"],\n                  \"response_status\": 403,\n                  \"response_body\": \"\u003ch1\u003eAccess Denied\u003c/h1\u003e\",\n                  \"response_headers\": {\n                    \"Content-Type\": \"text/html; charset=utf-8\"\n                  }\n                },\n                {\n                  \"handler\": \"reverse_proxy\",\n                  \"upstreams\": [{\"dial\": \"localhost:3000\"}]\n                }\n              ]\n            }\n          ]\n        }\n      }\n    }\n  }\n}\n```\n\n---\n\n## Behind a reverse proxy\n\nSet `trusted_proxies` to the IP(s) or CIDR(s) of your upstream proxy (e.g. nginx, Cloudflare, a load balancer). The plugin then resolves client IP from `X-Forwarded-For` by walking from right to left and picking the first non-trusted hop, which is safer when upstream proxies append to existing headers.\n\nBoth plain IPs and CIDR notation are accepted:\n\n```json\n\"trusted_proxies\": [\"127.0.0.1\", \"10.0.0.0/8\", \"172.16.0.0/12\"]\n```\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuomag9%2Fcaddy-blocker-plugin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffuomag9%2Fcaddy-blocker-plugin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuomag9%2Fcaddy-blocker-plugin/lists"}