Skip to content

Instantly share code, notes, and snippets.

@bruceharrison1984
Created June 6, 2025 02:33
Show Gist options
  • Save bruceharrison1984/a87e46821d85253a0a36a19e8aa686e0 to your computer and use it in GitHub Desktop.
Save bruceharrison1984/a87e46821d85253a0a36a19e8aa686e0 to your computer and use it in GitHub Desktop.
fail2ban - Cloudflare WAF
# Version 2025/6/4
# Author: leeharrison1984
#
# IMPORTANT
#
# This action utilizes the new WAF rulesets instead of the deprecated Firewall IP Access Rules
#
# This action depends on curl and jq being present on the system running fail2ban.
#
# To get your Cloudflare Account API token: Manage Account > Account API Tokens
# Cloudflare WAF Ruleset API: https://developers.cloudflare.com/api/resources/rulesets/
[Definition]
# Option: actionstart
# Notes.: command executed on demand at the first ban (or at the start of Fail2Ban if actionstart_on_demand is set to false).
# Values: CMD
#
actionstart =
# Option: actionstop
# Notes.: command executed at the stop of jail (or at the end of Fail2Ban)
# Values: CMD
#
actionstop =
# Option: actioncheck
# Notes.: command executed once before each actionban command
# Values: CMD
#
actioncheck =
# Option: actionban
# Notes.: command executed when banning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
actionban = curl -s -X POST <_cf_api_url_ruleset> <_cf_api_prms> \
--data '{"description":"<notes>","expression":"(ip.src eq <ip>)","action":"<cfmode>","enabled":true}'
# Option: actionunban
# Notes.: command executed when unbanning an IP. Take care that the
# command is executed with Fail2Ban user rights.
# Tags: <ip> IP address
# <failures> number of failures
# <time> unix timestamp of the ban time
# Values: CMD
#
actionunban = rule_id=$(curl -s -X GET <_cf_api_url_entrypoint> <_cf_api_prms> | <_rules_query_by_timestamp>)
if [ -z "$rule_id" ]; then echo "<name>: id for <ip> cannot be found using target <time>"; exit 0; fi; \
curl -s -X DELETE <_cf_api_url_ruleset>/$rule_id <_cf_api_prms>
_cf_api_url_base = https://api.cloudflare.com/client/v4/zones/<cfzone>/rulesets
_cf_api_prms = -H "Authorization: Bearer <cftoken>" -H "Content-Type: application/json"
_cf_api_url_entrypoint = <_cf_api_url_base>/phases/http_request_firewall_custom/entrypoint
## retrieve the id of the default ruleset for custom rules
_ruleset_id = $(curl -X GET <_cf_api_url_entrypoint> <_cf_api_prms> | jq .result.id -r)
## base url for queries including the ruleset id
_cf_api_url_ruleset = https://api.cloudflare.com/client/v4/zones/<cfzone>/rulesets/<_ruleset_id>/rules
## jq query used to find matching rules based on ban timestamp - for unbans
_rules_query_by_timestamp = jq '.result.rules.[] | select([ .description | contains("<time>") ] | any) .id' -r
[Init]
# Declare your Cloudflare Authorization Bearer Token in the [DEFAULT] section of your jail.local file.
# The Cloudflare <ZONE_ID> of the domain you want to manage.
# ie vbf47280fbceb034e842dd7631a484op
#
cfzone = >>CF_ALPHANUMERIC_ZONE_ID<<
# Cloudflare Account API token. Ideally restricted to just have "Zone.Zone WAF" permissions.
#
cftoken = >>CF_TOKEN_GOES_HERE<<
# The firewall mode Cloudflare should use. Default is "block" (deny access).
# Consider also "js_challenge" or other "allowed_modes" if you want.
#
cfmode = block
# The message to include in the firewall IP banning rule.
#
notes = Fail2Ban <name> - <time>
[Init?family=inet6]
cftarget = ip6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment