This page provides examples of creating [WAF custom rules](https://developers.cloudflare.com/waf/custom-rules/) in a zone or account using Terraform. The examples cover the following scenarios:

* Zone-level configurations:

  * [Add a custom rule to a zone](#add-a-custom-rule-to-a-zone)
  * [Add a custom rule challenging requests with leaked credentials](#add-a-custom-rule-challenging-requests-with-leaked-credentials)
  * [Add a custom rule blocking malicious uploads](#add-a-custom-rule-blocking-malicious-uploads)

* Account-level configurations:

  * [Create and deploy a custom ruleset](#create-and-deploy-a-custom-ruleset)
  * [Add a custom rule checking for exposed credentials](#add-a-custom-rule-checking-for-exposed-credentials)

## Before you start

### Obtain the necessary account or zone IDs

The Terraform configurations provided in this page need the zone ID (or account ID) of the zone/account where you will deploy rulesets.

* To retrieve the list of accounts you have access to, including their IDs, use the [List accounts](https://developers.cloudflare.com/api/resources/accounts/methods/list/) operation.
* To retrieve the list of zones you have access to, including their IDs, use the [List zones](https://developers.cloudflare.com/api/resources/zones/methods/list/) operation.

### Import or delete existing rulesets

Terraform assumes that it has complete control over account and zone rulesets. If you already have rulesets configured in your account or zone, do one of the following:

* [Import existing rulesets to Terraform](https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/) using the `cf-terraforming` tool. Recent versions of the tool can generate resource definitions for existing rulesets and import their configuration to Terraform state.
* Start from scratch by [deleting existing rulesets](https://developers.cloudflare.com/ruleset-engine/rulesets-api/delete/#delete-ruleset) (account and zone rulesets with `"kind": "root"` and `"kind": "zone"`, respectively) and then defining your rulesets configuration in Terraform.

***

## Zone-level configurations

### Add a custom rule to a zone

The following example configures a custom rule in the zone entry point ruleset for the `http_request_firewall_custom` phase for zone with ID `<ZONE_ID>`. The rule will block all traffic on non-standard HTTP(S) ports:

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "zone_custom_firewall" {
  zone_id     = "<ZONE_ID>"
  name        = "Phase entry point ruleset for custom rules in my zone"
  description = ""
  kind        = "zone"
  phase       = "http_request_firewall_custom"


  rules {
    ref         = "block_non_default_ports"
    description = "Block ports other than 80 and 443"
    expression  = "(not cf.edge.server_port in {80 443})"
    action      = "block"
  }
}
```

To create another custom rule, add a new `rules` object to the same `cloudflare_ruleset` resource.



### Add a custom rule challenging requests with leaked credentials

Note

For more information on enabling leaked credentials detection using Terraform, refer to the [leaked credentials detection](https://developers.cloudflare.com/waf/detections/leaked-credentials/get-started/#1-turn-on-the-detection) documentation.

This example adds a custom rule that challenges requests with leaked credentials by using one of the [leaked credentials fields](https://developers.cloudflare.com/waf/detections/leaked-credentials/#leaked-credentials-fields) in the rule expression.

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "zone_custom_firewall_leaked_creds" {
  zone_id     = "<ZONE_ID>"
  name        = "Phase entry point ruleset for custom rules in my zone"
  description = ""
  kind        = "zone"
  phase       = "http_request_firewall_custom"


  rules {
    ref         = "challenge_leaked_username_password"
    description = "Challenge requests with a leaked username and password"
    expression  = "(cf.waf.credential_check.username_and_password_leaked)"
    action      = "managed_challenge"
  }
}
```

For more information on configuring custom detection locations, refer to the [Terraform example](https://developers.cloudflare.com/waf/detections/leaked-credentials/get-started/#4-optional-configure-a-custom-detection-location) in the WAF documentation.

### Add a custom rule blocking malicious uploads

Note

For more information on enabling malicious uploads detection using Terraform, refer to the [malicious uploads detection](https://developers.cloudflare.com/waf/detections/malicious-uploads/get-started/#1-turn-on-the-detection) documentation.

This example adds a custom rule that blocks requests with one or more content objects considered malicious by using one of the [content scanning fields](https://developers.cloudflare.com/waf/detections/malicious-uploads/#content-scanning-fields) in the rule expression.

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "zone_custom_firewall_malicious_uploads" {
  zone_id     = "<ZONE_ID>"
  name        = "Phase entry point ruleset for custom rules in my zone"
  description = ""
  kind        = "zone"
  phase       = "http_request_firewall_custom"


  rules {
    ref         = "block_malicious_uploads"
    description = "Block requests uploading malicious content objects"
    expression  = "(cf.waf.content_scan.has_malicious_obj and http.request.uri.path eq \"/upload.php\")"
    action      = "block"
  }
}
```

For more information on configuring custom scan expressions, refer to the [Terraform example](https://developers.cloudflare.com/waf/detections/malicious-uploads/get-started/#4-optional-configure-a-custom-scan-expression) in the WAF documentation.

## Account-level configurations

### Create and deploy a custom ruleset

The following example creates a [custom ruleset](https://developers.cloudflare.com/ruleset-engine/custom-rulesets/) in the account with ID `<ACCOUNT_ID>` containing a single custom rule. This custom ruleset is then deployed using a separate `cloudflare_ruleset` Terraform resource. If you do not deploy a custom ruleset, it will not execute.

Warning

You can only create and deploy custom rulesets at the account level.

The following configuration creates the custom ruleset with a single rule:

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "account_firewall_custom_ruleset" {
  account_id  = "<ACCOUNT_ID>"
  name        = "Custom ruleset blocking traffic in non-standard HTTP(S) ports"
  description = ""
  kind        = "custom"
  phase       = "http_request_firewall_custom"


  rules {
    ref         = "block_non_default_ports"
    description = "Block ports other than 80 and 443"
    expression  = "(not cf.edge.server_port in {80 443})"
    action      = "block"
  }
}
```

To create another custom rule in the custom ruleset, add a new `rules` object to the same `cloudflare_ruleset` resource.



The following configuration deploys the custom ruleset at the account level. It defines a dependency on the `account_firewall_custom_ruleset` resource and uses the ID of the created custom ruleset in `action_parameters`:

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "account_firewall_custom_entrypoint" {
  account_id  = "<ACCOUNT_ID>"
  name        = "Account-level entry point ruleset for the http_request_firewall_custom phase deploying a custom ruleset"
  description = ""
  kind        = "root"
  phase       = "http_request_firewall_custom"


  depends_on = [cloudflare_ruleset.account_firewall_custom_ruleset]


  rules {
    ref         = "deploy_custom_ruleset_example_com"
    description = "Deploy custom ruleset for example.com"
    expression  = "(cf.zone.name eq \"example.com\")"
    action      = "execute"
    action_parameters {
      id = cloudflare_ruleset.account_firewall_custom_ruleset.id
    }
  }
}
```

For more information on configuring and deploying custom rulesets, refer to [Work with custom rulesets](https://developers.cloudflare.com/ruleset-engine/custom-rulesets/) in the Ruleset Engine documentation.

### Add a custom rule checking for exposed credentials

Recommendation: Use leaked credentials detection instead

Cloudflare recommends that you use [leaked credentials detection](https://developers.cloudflare.com/waf/detections/leaked-credentials/) instead of Cloudflare Exposed Credentials Check, which refers to a previous implementation.\
For more information on upgrading your current Exposed Credentials Check configuration, refer to the [upgrade guide](https://developers.cloudflare.com/waf/managed-rules/check-for-exposed-credentials/upgrade-to-leaked-credentials-detection/).

The following configuration creates a custom ruleset with a single rule that [checks for exposed credentials](https://developers.cloudflare.com/waf/managed-rules/check-for-exposed-credentials/configure-api/#create-a-custom-rule-checking-for-exposed-credentials).

You can only add exposed credential checks to rules in a custom ruleset (that is, a ruleset with `kind = "custom"`).

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "account_firewall_custom_ruleset_exposed_creds" {
  account_id  = "<ACCOUNT_ID>"
  name        = "Custom ruleset checking for exposed credentials"
  description = ""
  kind        = "custom"
  phase       = "http_request_firewall_custom"


  rules {
    ref         = "check_for_exposed_creds_add_header"
    description = "Add header when there is a rule match and exposed credentials are detected"
    expression  = "http.request.method == \"POST\" && http.request.uri == \"/login.php\""
    action      = "rewrite"
    action_parameters {
      headers {
        name      = "Exposed-Credential-Check"
        operation = "set"
        value     = "1"
      }
    }
    exposed_credential_check {
      username_expression = "url_decode(http.request.body.form[\"username\"][0])"
      password_expression = "url_decode(http.request.body.form[\"password\"][0])"
    }
  }
}
```

To create another rule, add a new `rules` object to the same `cloudflare_ruleset` resource.



The following configuration deploys the custom ruleset. It defines a dependency on the `account_firewall_custom_ruleset_exposed_creds` resource and obtains the ID of the created custom ruleset:

Note

Terraform code snippets below refer to the v4 SDK only.

```tf
resource "cloudflare_ruleset" "account_firewall_custom_entrypoint" {
  account_id  = "<ACCOUNT_ID>"
  name        = "Account-level entry point ruleset for the http_request_firewall_custom phase deploying a custom ruleset checking for exposed credentials"
  description = ""
  kind        = "root"
  phase       = "http_request_firewall_custom"


  depends_on = [cloudflare_ruleset.account_firewall_custom_ruleset_exposed_creds]


  rules {
    ref         = "deploy_custom_ruleset_example_com"
    description = "Deploy custom ruleset for example.com"
    expression  = "(cf.zone.name eq \"example.com\")"
    action      = "execute"
    action_parameters {
      id = cloudflare_ruleset.account_firewall_custom_ruleset_exposed_creds.id
    }
  }
}
```
