---
title: Cloudflare Terraform provider
description: Manage your Cloudflare configuration as infrastructure as code using the Terraform provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Cloudflare Terraform provider

Configure Cloudflare using HashiCorp's “Infrastructure as Code” tool, Terraform. With [Cloudflare’s Terraform provider ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs), you can manage the Cloudflare global network using the same familiar tools you use to automate the rest of your infrastructure. Define and store configuration in source code repositories like GitHub, track and version changes over time, and roll back when needed — all without needing to use the Cloudflare APIs.

Report Terraform configuration issues via [GitHub ↗](https://github.com/cloudflare/terraform-provider-cloudflare/issues/new/choose).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}}]}
```

---

---
title: Get started
description: Install Terraform and configure the Cloudflare provider on your operating system.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Get started

Terraform ships as a single binary file. The examples below include installation information for popular operating systems.

For official instructions on installing Terraform, refer to [Install Terraform ↗](https://developer.hashicorp.com/terraform/tutorials/certification-associate-tutorials/install-cli).

Warning

Terraform maintains your configuration state, which can be broken when you make configuration changes through both Terraform and either the Cloudflare Dashboard or API.

To avoid this state, make sure you manage Terraform resources only in Terraform. For more details, refer to our [best practices](https://developers.cloudflare.com/terraform/advanced-topics/best-practices/).

## Mac

The easiest way to install Terraform on macOS is with Homebrew.

Terminal window

```

brew tap hashicorp/tap

brew install hashicorp/tap/terraform


```

## Linux

You can install the `terraform` binary via your distribution's package manager. For example:

Terminal window

```

sudo apt install terraform


```

Alternatively, you can fetch a specific version directly and place the binary in your `PATH`:

Terminal window

```

wget -q https://releases.hashicorp.com/terraform/1.4.5/terraform_1.4.5_linux_amd64.zip


unzip terraform_1.4.5_linux_amd64.zip


```

```

Archive:  terraform_1.4.5_linux_amd64.zip

  inflating: terraform


```

Terminal window

```

sudo mv terraform /usr/local/bin/terraform


terraform version


```

```

Terraform v1.4.5


```

## Windows

1. Download the 32 or 64-bit executable from the [Download Terraform ↗](https://developer.hashicorp.com/terraform/downloads) page.
2. Unzip and place `terraform.exe` somewhere in your path.

## Other

For additional installers, refer to the [Download Terraform ↗](https://developer.hashicorp.com/terraform/downloads) page.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/installing/","name":"Get started"}}]}
```

---

---
title: Tutorials
description: Step-by-step Cloudflare Terraform tutorials from initialization to advanced configuration.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Tutorials

Before you begin, [install Terraform](https://developers.cloudflare.com/terraform/installing/). Each tutorial builds on the previous, so you should complete the tutorials in the order shown below.

Note

If you are upgrading from v4, review the [migration guide ↗](https://github.com/cloudflare/terraform-provider-cloudflare/blob/main/docs/guides/version-5-upgrade.md) for breaking changes.

## [1 – Initialize Terraform](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/)

* Brief introduction.
* Introduction of `terraform init`, `plan`, `apply`, and `show`.
* Resource covered: [cloudflare\_dns\_record ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/dns%5Frecord) (DNS record).

## [2 – Track your history](https://developers.cloudflare.com/terraform/tutorial/track-history/)

* Store Cloudflare configuration in source control.

## [3 – Configure HTTPS settings](https://developers.cloudflare.com/terraform/tutorial/configure-https-settings/)

* Modify zone settings.
* Resource covered: [cloudflare\_zone\_setting ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone%5Fsetting).

## [4 – Improve performance and reliability](https://developers.cloudflare.com/terraform/tutorial/use-load-balancing/)

* Add load balancing rules.
* Resources covered:  
   * [cloudflare\_load\_balancer ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/load%5Fbalancer)  
   * [cloudflare\_load\_balancer\_pool ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/load%5Fbalancer%5Fpool)  
   * [cloudflare\_load\_balancer\_monitor ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/load%5Fbalancer%5Fmonitor)

## [5 – Add exceptions with page rules](https://developers.cloudflare.com/terraform/tutorial/add-page-rules/)

* Add page rule.
* Resource covered: [cloudflare\_page\_rule ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/page%5Frule).
* Increase security level for a specific URL: `/expensive-db-call`.
* Add a redirect (URL forward) with a `301` status code from `/old-location.php` to `/expensive-db-call`.

## [6 – Revert configuration](https://developers.cloudflare.com/terraform/tutorial/revert-configuration/)

* Review change history.
* Roll back changes.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}}]}
```

---

---
title: 5 – Add exceptions with Page Rules
description: Page Rules let you override zone settings for specific URL patterns. Redirects old URLs with a 301 permanent redirect.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 5 – Add exceptions with Page Rules

In the [Configure HTTPS settings](https://developers.cloudflare.com/terraform/tutorial/configure-https-settings/) tutorial, you configured zone settings that apply to all incoming requests for `example.com`. In this tutorial, you will add an exception to these settings using [Page Rules](https://developers.cloudflare.com/rules/page-rules/).

Specifically, you will increase the security level for a URL known to be expensive to render and cannot be cached: `https://www.example.com/expensive-db-call`. Additionally, you will add a redirect from the previous URL used to host this page.

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Create Page Rules configuration

Create a new branch and append the configuration.

Terminal window

```

git checkout -b step5-pagerule


```

Page Rules let you override zone settings for specific URL patterns. Add two Page Rules to your `main.tf`:

```

# Increase security for expensive database operations

resource "cloudflare_page_rule" "expensive_endpoint_security" {

  zone_id  = var.zone_id

  target   = "${var.domain}/expensive-db-call"

  priority = 1


  actions = {

    security_level = "under_attack"

  }

}


# Redirect old URLs to new location

resource "cloudflare_page_rule" "legacy_redirect" {

  zone_id  = var.zone_id

  target   = "${var.domain}/old-location.php"

  priority = 2


  actions = {

    forwarding_url = {

      url         = "https://www.${var.domain}/expensive-db-call"

      status_code = 301

    }

  }

}


```

The first rule increases security to "Under Attack" mode for your database endpoint. The second rule redirects old URLs with a 301 permanent redirect.

## 2\. Preview and apply the changes:

Terminal window

```

terraform plan

terraform apply


```

## 3\. Verify changes:

Test the redirect functionality:

Terminal window

```

curl -I https://example.com/old-location.php


```

Expected output:

```

HTTP/1.1 301 Moved Permanently

Location: https://example.com/expensive-db-call


```

Test the increased security (Under Attack mode returns a challenge page):

Terminal window

```

curl -I https://example.com/expensive-db-call


```

Expected output:

```

HTTP/1.1 503 Service Temporarily Unavailable


```

The 503 response indicates the Under Attack mode is active, presenting visitors with a challenge page before allowing access to protect against DDoS attacks.

## 4\. Commit and merge the changes:

Terminal window

```

git add main.tf

git commit -m "Step 5 - Add two Page Rules"

git push


```

The call works as expected. In the first case, the Cloudflare global network responds with a `301` redirecting the browser to the new location. In the second case, the Cloudflare global network initially responds with a `503`, which is consistent with the Under Attack mode.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/add-page-rules/","name":"5 – Add exceptions with Page Rules"}}]}
```

---

---
title: 3 – Configure HTTPS settings
description: This tutorial shows how to enable TLS 1.3, Automatic HTTPS Rewrites, and Strict SSL mode using the updated v5 provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 3 – Configure HTTPS settings

After setting up basic DNS records, you can configure zone settings using Terraform. This tutorial shows how to enable [TLS 1.3](https://developers.cloudflare.com/ssl/edge-certificates/additional-options/tls-13/), [Automatic HTTPS Rewrites](https://developers.cloudflare.com/ssl/edge-certificates/additional-options/automatic-https-rewrites/), and [Strict SSL mode](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full-strict/) using the updated v5 provider.

## Prerequisites

* Completed tutorials [1](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/) and [2](https://developers.cloudflare.com/terraform/tutorial/track-history/)
* Valid SSL certificate on your origin server (use the [Cloudflare Origin CA](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/) to generate one for strict SSL mode)

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Create zone setting configuration

Create a new branch and add zone settings:

Terminal window

```

git checkout -b step3-zone-settings


```

Add the following to your `main.tf` file:

```

# Enable TLS 1.3

resource "cloudflare_zone_setting" "tls_1_3" {

  zone_id    = var.zone_id

  setting_id = "tls_1_3"

  value      = "on"

}


# Enable automatic HTTPS rewrites

resource "cloudflare_zone_setting" "automatic_https_rewrites" {

  zone_id    = var.zone_id

  setting_id = "automatic_https_rewrites"

  value      = "on"

}


# Set SSL mode to strict

resource "cloudflare_zone_setting" "ssl" {

  zone_id    = var.zone_id

  setting_id = "ssl"

  value      = "strict"

}


```

## 2\. Preview and apply the changes

Review the proposed changes:

Terminal window

```

terraform plan


```

Expected output

```

Plan: 3 to add, 0 to change, 0 to destroy.


Terraform will perform the following actions:


  # cloudflare_zone_setting.automatic_https_rewrites will be created

  + resource "cloudflare_zone_setting" "automatic_https_rewrites" {

      + setting_id = "automatic_https_rewrites"

      + value      = "on"

      + zone_id    = "your-zone-id"

    }


  # cloudflare_zone_setting.ssl will be created

  + resource "cloudflare_zone_setting" "ssl" {

      + setting_id = "ssl"

      + value      = "strict"

      + zone_id    = "your-zone-id"

    }


  # cloudflare_zone_setting.tls_1_3 will be created

  + resource "cloudflare_zone_setting" "tls_1_3" {

      + setting_id = "tls_1_3"

      + value      = "on"

      + zone_id    = "your-zone-id"

    }


```

Commit and merge the changes:

Terminal window

```

git add main.tf

git commit -m "Step 3 - Enable TLS 1.3, automatic HTTPS rewrites, and strict SSL"

git checkout main

git merge step3-zone-settings

git push


```

Before applying the changes, try to connect with TLS 1.3\. Technically, you should not be able to with default settings. To follow along with this test, you will need to [compile curl against BoringSSL ↗](https://everything.curl.dev/source/build/tls/boringssl#build-boringssl).

Terminal window

```

curl -v --tlsv1.3 https://www.example.com 2>&1 | grep "SSL connection\|error"


```

As shown above, you should receive an error because TLS 1.3 is not yet enabled on your zone. Enable it by running `terraform apply` and try again.

Apply the configuration:

Terminal window

```

terraform apply


```

Type `yes` when prompted.

## 3\. Verify the settings

Try the same command as before. The command will now succeed.

Terminal window

```

curl -v --tlsv1.3 https://www.example.com 2>&1 | grep "SSL connection\|error"


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/configure-https-settings/","name":"3 – Configure HTTPS settings"}}]}
```

---

---
title: 1 –  Initialize Terraform
description: This tutorial shows you how to get started with Terraform. You will create a DNS record pointing www.example.com to a web server at 203.0.113.10.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 1 – Initialize Terraform

This tutorial shows you how to get started with Terraform. You just signed up your domain (`example.com`) on Cloudflare to manage everything in Terraform and now you will create a DNS record pointing `www.example.com` to a web server at `203.0.113.10`.

Before you begin, ensure you have:

* [Installed Terraform](https://developers.cloudflare.com/terraform/installing/)
* [Created an API Token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/) with permissions to edit resources for this tutorial

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Create your configuration

Create a file named `main.tf`, filling in your own values for the [API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token/), [zone ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/), [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids/), and [domain](https://developers.cloudflare.com/fundamentals/manage-domains/add-site/):

Terminal window

```

terraform {

  required_providers {

    cloudflare = {

      source  = "cloudflare/cloudflare"

      version = "~> 5"

    }

  }

}


provider "cloudflare" {

  api_token = "<YOUR_API_TOKEN>"

}


variable "zone_id" {

  default = "<YOUR_ZONE_ID>"

}


variable "account_id" {

  default = "<YOUR_ACCOUNT_ID>"

}


variable "domain" {

  default = "<YOUR_DOMAIN>"

}


resource "cloudflare_dns_record" "www" {

  zone_id = "<YOUR_ZONE_ID>"

  name    = "www"

  content = "203.0.113.10"

  type    = "A"

  ttl     = 1

  proxied = true

  comment = "Domain verification record"

}


```

Warning

To prevent accidentally exposing your Cloudflare credentials, do not save this file in your version control system. The [next tutorial](https://developers.cloudflare.com/terraform/tutorial/track-history/) will cover best practices for passing in your API token.

## 2\. Initialize and plan

Initialize Terraform to download the Cloudflare provider:

Terminal window

```

terraform init


```

Review what will be created:

Terminal window

```

terraform plan


```

```

Terraform used the selected providers to generate the following execution plan. Resource actions are

indicated with the following symbols:

  + create


Terraform will perform the following actions:


  # cloudflare_dns_record.www will be created

  + resource "cloudflare_dns_record" "www" {

      + comment             = "Domain verification record"

      + comment_modified_on = (known after apply)

      + content             = "203.0.113.10"

      + created_on          = (known after apply)

      + id                  = (known after apply)

      + meta                = (known after apply)

      + modified_on         = (known after apply)

      + name                = "www"

      + proxiable           = (known after apply)

      + proxied             = true

      + settings            = (known after apply)

      + tags                = (known after apply)

      + tags_modified_on    = (known after apply)

      + ttl                 = 1

      + type                = "A"

      + zone_id             = "<YOUR_ZONE_ID>"

    }


Plan: 1 to add, 0 to change, 0 to destroy.


```

## 3\. Apply and verify

Apply your configuration:

Terminal window

```

terraform apply


```

Type `yes` when prompted.

```

Terraform used the selected providers to generate the following execution plan. Resource actions are

indicated with the following symbols:

  + create


Terraform will perform the following actions:


  # cloudflare_dns_record.www will be created

  + resource "cloudflare_dns_record" "www" {

      + comment             = "Domain verification record"

      + comment_modified_on = (known after apply)

      + content             = "203.0.113.10"

      + created_on          = (known after apply)

      + id                  = (known after apply)

      + meta                = (known after apply)

      + modified_on         = (known after apply)

      + name                = "www"

      + proxiable           = (known after apply)

      + proxied             = true

      + settings            = (known after apply)

      + tags                = (known after apply)

      + tags_modified_on    = (known after apply)

      + ttl                 = 1

      + type                = "A"

      + zone_id             = "<YOUR_ZONE_ID>"

    }


Plan: 1 to add, 0 to change, 0 to destroy.


Do you want to perform these actions?

  Terraform will perform the actions described above.

  Only 'yes' will be accepted to approve.


  Enter a value: yes


cloudflare_dns_record.www: Creating...

cloudflare_dns_record.www: Creation complete after 0s


Apply complete! Resources: 1 added, 0 changed, 0 destroyed.


```

After creation, verify the DNS record:

Terminal window

```

dig www.example.com


```

Test the web server response:

Terminal window

```

curl https://www.example.com


```

```

Hello, this is 203.0.113.10!


```

To see the full results returned from the API call:

Terminal window

```

terraform show


```

You can also check the Cloudflare dashboard and go to the **DNS** \> **Records** page.

[ Go to **Account home** ](https://dash.cloudflare.com/?to=/:account/home) 

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/initialize-terraform/","name":"1 –  Initialize Terraform"}}]}
```

---

---
title: 6 – Revert configuration
description: Sometimes, you may have to roll back configuration changes. To revert your configuration, check out the desired branch and ask Terraform to move your Cloudflare settings back in time.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 6 – Revert configuration

Sometimes, you may have to roll back configuration changes. For example, you might want to run performance tests on a new configuration or maybe you mistyped an IP address and brought your entire site down.

To revert your configuration, check out the desired branch and ask Terraform to move your Cloudflare settings back in time. If you accidentally brought your site down, consider establishing a good strategy for peer reviewing pull requests rather than merging directly to `master` as done in the tutorials for brevity.

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Review your configuration history

Before determining how far back to revert, review your Git history:

Terminal window

```

git log --oneline


```

```

f1a2b3c Step 5 - Add two Page Rules

d4e5f6g Step 4 - Create load balancer (LB) monitor, LB pool, and LB

a7b8c9d Step 3 - Enable TLS 1.3, automatic HTTPS rewrites, and strict SSL

e1f2g3h Step 2 - Initial Terraform v5 configuration


```

Another benefit of storing your Cloudflare configuration in Git is that you can see who made the change. You can also see who reviewed and approved the change if you peer-review pull requests.

Terminal window

```

git log


```

Check when the last change was made:

Terminal window

```

git show


```

This shows the most recent commit and what files changed.

## 2\. Scenario: Revert the Page Rules

Assume that shortly after you deployed the Page Rules when following the [Add exceptions with Page Rules](https://developers.cloudflare.com/terraform/tutorial/add-page-rules/) tutorial, you are told the URL is no longer needed, and the security setting and redirect should be dropped.

While you can always edit the config file directly and delete those entries, you can use Git to do that for you.

### Revert using Git

Use Git to create a revert commit that undoes the Page Rules changes:

Terminal window

```

git revert HEAD


```

Git will open your default editor with a commit message. Save and close to accept the default message, or customize it:

```

Revert "Add Page Rules for security and redirects"


This reverts commit f1a2b3c4d5e6f7a8b9c0d1e2f3g4h5i6j7k8l9m0.


```

## 3\. Preview the changes

Check what Terraform will do with the reverted configuration:

Terminal window

```

terraform plan


```

Expected output:

```

Plan: 0 to add, 0 to change, 2 to destroy.


Terraform will perform the following actions:


  # cloudflare_page_rule.expensive_endpoint_security will be destroyed

  # cloudflare_page_rule.legacy_redirect will be destroyed


```

As expected, Terraform will remove the two Page Rules that were added in tutorial 5.

## 4\. Apply the changes

Apply the changes to remove the Page Rules from your Cloudflare zone:

Terminal window

```

terraform apply --auto-approve


```

```

cloudflare_page_rule.expensive_endpoint_security: Destroying...

cloudflare_page_rule.legacy_redirect: Destroying...

cloudflare_page_rule.expensive_endpoint_security: Destruction complete after 1s

cloudflare_page_rule.legacy_redirect: Destruction complete after 1s


Apply complete! Resources: 0 added, 0 changed, 2 destroyed.


```

Two resources were destroyed, as expected, and you have rolled back to the previous version.

## 5\. Verify the revert

Test that the Page Rules are no longer active:

Terminal window

```

# This should now return 404 (no redirect)

curl -I https://www.example.com/old-location.php


# This should return normal response (no Under Attack mode)

curl -I https://www.example.com/expensive-db-call


```

Your configuration has been successfully reverted. The Page Rules are removed, and your zone settings are back to the previous state. Git's version control ensures you can always recover or revert changes safely.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/revert-configuration/","name":"6 – Revert configuration"}}]}
```

---

---
title: 2 – Track your history
description: Learn how to track history with Cloudflare Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 2 – Track your history

In the [Initialize Terraform](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/) tutorial, you created and applied basic Cloudflare configuration. Now you'll store this configuration in version control for tracking, peer review, and rollback capabilities.

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Use environment variables for authentication

Remove credentials from your Terraform files before committing to version control. The Cloudflare provider v5 reads authentication from environment variables automatically. Update your `main.tf` file to remove the hardcoded API token:

```

terraform {

  required_providers {

    cloudflare = {

      source  = "cloudflare/cloudflare"

      version = "~> 5"

    }

  }

}


provider "cloudflare" {

  # API token will be read from CLOUDFLARE_API_TOKEN environment variable

}


variable "zone_id" {

  description = "Cloudflare Zone ID"

  type        = string

  sensitive   = true

}


variable "account_id" {

  description = "Cloudflare Account ID"

  type        = string

  sensitive   = true

}


variable "domain" {

  description = "Domain name"

  type        = string

  default     = "example.com"

}


resource "cloudflare_dns_record" "www" {

  zone_id = var.zone_id

  name    = "www"

  content = "203.0.113.10"

  type    = "A"

  ttl     = 1

  proxied = true

  comment = "Domain verification record"

}


```

Note

You must still include the empty provider definition in the file, so that Terraform knows to install the Cloudflare plugin. For more information about advanced options you can use to customize the Cloudflare provider, refer to [Provider customization](https://developers.cloudflare.com/terraform/advanced-topics/provider-customization/).

Update your `terraform.tfvars` file:

```

zone_id    = "your-zone-id-here"

account_id = "your-account-id-here"

domain     = "your-domain.com"


```

Ensure your API token is set as an environment variable:

Terminal window

```

export CLOUDFLARE_API_TOKEN="your-api-token-here"


```

Verify authentication works:

Terminal window

```

terraform plan


```

You may see changes detected as Terraform compares your new variable-based configuration with the existing resources. This is normal when migrating from hardcoded values to variables:

```

# cloudflare_dns_record.www will be updated in-place

~ resource "cloudflare_dns_record" "www" {

    ~ name     = "www.your-domain.com" -> "www"

    ~ zone_id  = (sensitive value)

    # (other attributes may show changes)

}


Plan: 0 to add, 1 to change, 0 to destroy.


```

## 2\. Store configuration in GitHub

Create a `.gitignore` file with these contents:

```

.terraform/

*.tfstate*

.terraform.lock.hcl

terraform.tfvars


```

Initialize Git and commit your configuration:

Terminal window

```

git init

git add main.tf .gitignore

git commit -m "Step 2 - Initial Terraform v5 configuration"


```

Create a GitHub repository (via web interface or GitHub CLI) and push:

Terminal window

```

git branch -M main

git remote add origin https://github.com/YOUR_USERNAME/cf-config.git

git push -u origin main


```

Your Terraform configuration is now version controlled and ready for team collaboration. The sensitive data (API tokens, zone IDs) remains secure and separate from your code.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/track-history/","name":"2 – Track your history"}}]}
```

---

---
title: 4 – Improve performance
description: Learn how to use Terraform with Cloudflare Load Balancing product to fail traffic over as needed.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 4 – Improve performance

In this tutorial, you will add a second origin for some basic round robining, and then use the [Cloudflare Load Balancing](https://developers.cloudflare.com/load-balancing/) product to fail traffic over as needed. You will also enhance your load balancing configuration through the use of "geo steering" to serve results from an origin server that is geographically closest to your end users.

## Prerequisites

* Completed [Tutorial 1](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/), [Tutorial 2](https://developers.cloudflare.com/terraform/tutorial/track-history/) and [Tutorial 3](https://developers.cloudflare.com/terraform/tutorial/configure-https-settings/)
* [Load Balancing](https://developers.cloudflare.com/load-balancing/get-started/enable-load-balancing/) enabled on your Cloudflare account

Note

Terraform code snippets below refer to the v5 SDK only.

## 1\. Add another DNS record for www

Create a new branch and add a DNS record for your Asia server:

Terminal window

```

git checkout -b step4-configure-load-balancing


```

Add a DNS record for a second web server, located in Asia. For example purposes, the IP address for this server is `198.51.100.15`. Add the second DNS record to your `main.tf`:

```

# Asia origin server

resource "cloudflare_dns_record" "www_asia" {

  zone_id = var.zone_id

  name    = "www"

  content = "198.51.100.15"

  type    = "A"

  ttl     = 300

  proxied = true

  comment = "Asia origin server"

}


```

Note

Note that while the name of the `resource` is different because Terraform resources of the same type must be uniquely named, the DNS name, or what your customers will type in their browser, is the same: `www`.

Apply this change to see basic round-robin behavior:

Terminal window

```

terraform plan

terraform apply


```

Test the basic load distribution:

Terminal window

```

# Make several requests to see both origins

for i in {1..4}; do

  curl https://www.example.com

  sleep 1

done


```

Expected output:

```

Hello, this is 203.0.113.10!

Hello, this is 203.0.113.10!

Hello, this is 198.51.100.15!

Hello, this is 203.0.113.10!


```

You'll see random distribution between your two origin servers. This basic DNS-based load balancing has limitations - no health checks, no geographic steering, and unpredictable distribution patterns. For more advanced scenarios like origins in different geographies or automatic failover, you'll want to use [Cloudflare's Load Balancing](https://developers.cloudflare.com/load-balancing/).

## 2\. Switch to using Cloudflare's Load Balancing product

As described in the [Load Balancing tutorial](https://developers.cloudflare.com/learning-paths/load-balancing/concepts/), you will need to complete three tasks:

1. Create a monitor to run health checks against your origin servers.
2. Create a pool of one or more origin servers that will receive load balanced traffic.
3. Create a load balancer with an external hostname — for example, `www.example.com` — and one or more pools.

We can monitor the origins by creating a basic health check that makes a GET request to each origin on the URL. If the origin returns the 200 status code (OK) within five seconds, it is considered healthy. If it fails to do so three times in a row, it is considered unhealthy. This health check will be run once per minute from several regions and you can configure an email notification in the event any failures are detected.

In this example, the pool will be called `www-origins` with two origins added to it:

* `www-us` (`203.0.113.10`)
* `www-asia` (`198.51.100.15`)

For now, skip any sort of [geo routing](https://developers.cloudflare.com/load-balancing/understand-basics/traffic-steering/steering-policies/geo-steering/).

When you create a load balancer (LB), it will [replace any existing DNS records with the same name](https://developers.cloudflare.com/load-balancing/load-balancers/dns-records/). For example, if you create the `www.example.com` load balancer below, it will supersede the two `www` DNS records that you previously defined. One benefit of leaving the DNS records in place is that if you temporarily disable load balancing, connections to this hostname are still possible.

To achieve the above, add the load balancing configuration to `main.tf`:

```

# Health check monitor

resource "cloudflare_load_balancer_monitor" "health_check" {

  account_id     = var.account_id

  expected_body = "alive"

  expected_codes = "2xx"

  method         = "GET"

  timeout        = 5

  path           = "/health"

  interval       = 60

  retries        = 2

  description    = "Health check for www origins"

  type           = "https"


  header = {

    Host = ["${var.domain}"]

  }

}


# Origin pool

resource "cloudflare_load_balancer_pool" "www_pool" {

  account_id = var.account_id

  name       = "www-origins"

  monitor    = cloudflare_load_balancer_monitor.health_check.id


  origins = [{

    name    = "www-us"

    address = "203.0.113.10"

    enabled = true

  }, {

    name    = "www-asia"

    address = "198.51.100.15"

    enabled = true

  }]


  description     = "Primary www server pool"

  enabled         = true

  minimum_origins = 1

  notification_email = "<YOUR_EMAIL>"

  check_regions   = ["WEU", "EEU", "WNAM", "ENAM", "SEAS", "NEAS"]

}


# Load balancer

resource "cloudflare_load_balancer" "www_lb" {

  zone_id       = var.zone_id

  name          = "www.${var.domain}"

  default_pools = [cloudflare_load_balancer_pool.www_pool.id]

  fallback_pool = cloudflare_load_balancer_pool.www_pool.id

  description   = "Load balancer for www.${var.domain}"

  proxied       = true

}


```

Note

The load balancer will automatically replace your existing DNS records with the same name (www).

Preview and apply the changes:

Terminal window

```

terraform plan

terraform apply


```

Test the improved load balancing:

Terminal window

```

# Test load distribution with health monitoring

for i in {1..6}; do

  echo "Request $i:"

  curl -s https://www.example.com

  sleep 2

done


```

Expected output:

```

Request 1:

Hello, this is 198.51.100.15!

Request 2:

Hello, this is 203.0.113.10!

Request 3:

Hello, this is 198.51.100.15!

Request 4:

Hello, this is 203.0.113.10!

Request 5:

Hello, this is 203.0.113.10!

Request 6:

Hello, this is 198.51.100.15!


```

You should now see more predictable load distribution with the added benefits of health monitoring and automatic failover.

Merge and verify:

Terminal window

```

git add main.tf

git commit -m "Step 4 - Create load balancer (LB) monitor, LB pool, and LB"

git push


```

Verify the configuration is working by checking the Cloudflare dashboard under **Traffic** \> **Load Balancing**. You should see your monitor, pool, and load balancer with health status indicators. Your load balancer will now:

* Distribute traffic intelligently between origins
* Automatically route around unhealthy servers
* Provide real-time health monitoring

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/tutorial/","name":"Tutorials"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/tutorial/use-load-balancing/","name":"4 – Improve performance"}}]}
```

---

---
title: DDoS managed rulesets configuration using Terraform
description: Configure Cloudflare DDoS managed rulesets at the zone or account level using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# DDoS managed rulesets configuration using Terraform

This page provides examples of configuring [DDoS managed rulesets](https://developers.cloudflare.com/ddos-protection/managed-rulesets/) in your zone or account using Terraform. It covers the following configurations:

* [Example: Configure HTTP DDoS Attack Protection](#example-configure-http-ddos-attack-protection)
* [Example: Configure Network-layer DDoS Attack Protection](#example-configure-network-layer-ddos-attack-protection)
* [Use case: Mitigate large HTTP DDoS attacks and monitor flagged traffic](#use-case-mitigate-large-http-ddos-attacks-and-monitor-flagged-traffic)

DDoS managed rulesets are always enabled. Depending on your Cloudflare services, you may be able to adjust their behavior.

If you are using the Cloudflare API, refer to the following resources:

* [Configure HTTP DDoS Attack Protection via API](https://developers.cloudflare.com/ddos-protection/managed-rulesets/http/http-overrides/configure-api/)
* [Configure Network-layer DDoS Attack Protection via API](https://developers.cloudflare.com/ddos-protection/managed-rulesets/network/network-overrides/configure-api/)

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

## Before you start

### Obtain the necessary account, zone, and managed ruleset IDs

The Terraform configurations provided in this page need the zone ID (or account ID) of the zone/account where you will deploy the managed 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.

The deployment of managed rulesets via Terraform requires that you use the ruleset IDs. To find the IDs of managed rulesets, use the [List account rulesets](https://developers.cloudflare.com/api/resources/rulesets/methods/list/) operation. The response will include the description and IDs of existing managed rulesets.

### (Optional) Delete existing rulesets to start from scratch

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.

---

## Example: Configure HTTP DDoS Attack Protection

This example configures the [HTTP DDoS Attack Protection](https://developers.cloudflare.com/ddos-protection/managed-rulesets/http/) managed ruleset for a zone using Terraform.

* [ Terraform (v5) ](#tab-panel-8565)
* [ Terraform (v4) ](#tab-panel-8566)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `HTTP DDoS Managed Ruleset Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "zone_level_http_ddos_config" {

  zone_id     = var.cloudflare_zone_id

  name        = "HTTP DDoS Attack Protection entry point ruleset"

  description = ""

  kind        = "zone"

  phase       = "ddos_l7"


  rules = [{

    action = "execute"

    action_parameters = {

      # Cloudflare L7 DDoS Attack Protection Ruleset

      id = "4d21379b4f9f4bb088e0729962c8b3cf"

      overrides = {

        action            = "block"

        sensitivity_level = "default"

        rules = [

          {

            # Adaptive DDoS Protection based on Locations (Available only to Enterprise zones with Advanced DDoS service)

            id                = "a8c6333711ff4b0a81371d1c444be2c3"

            sensitivity_level = "default"

            action            = "managed_challenge"

          },

          {

            # Adaptive DDoS Protection based on User-Agents (Available only to Enterprise zones with Advanced DDoS service)

            id                = "7709d496081e458899c1e3a6e4fe8e55"

            sensitivity_level = "default"

            action            = "managed_challenge"

          },

          {

            # HTTP requests causing a high number of origin errors.

            id                = "dd42da7baabe4e518eaf11c393596a9d"

            sensitivity_level = "default"

            action            = "managed_challenge"

          },

        ]

      }

    }

    expression  = "true"

    description = "Zone-wide HTTP DDoS Override"

    enabled     = true

  }]

}


```

```

resource "cloudflare_ruleset" "zone_level_http_ddos_config" {

  zone_id     = "<ZONE_ID>"

  name        = "HTTP DDoS Attack Protection entry point ruleset"

  description = ""

  kind        = "zone"

  phase       = "ddos_l7"


  rules {

    action = "execute"

    action_parameters {

      # Cloudflare L7 DDoS Attack Protection Ruleset

      id = "4d21379b4f9f4bb088e0729962c8b3cf"

      overrides {

        action = "block"

        sensitivity_level = "default"

        rules {

          # Adaptive DDoS Protection based on Locations (Available only to Enterprise zones with Advanced DDoS service)

          id = "a8c6333711ff4b0a81371d1c444be2c3"

          sensitivity_level = "default"

          action = "managed_challenge"

        }

        rules {

          # Adaptive DDoS Protection based on User-Agents (Available only to Enterprise zones with Advanced DDoS service)

          id = "7709d496081e458899c1e3a6e4fe8e55"

          sensitivity_level = "default"

          action = "managed_challenge"

        }

        rules {

          # HTTP requests causing a high number of origin errors.

          id = "dd42da7baabe4e518eaf11c393596a9d"

          sensitivity_level = "default"

          action = "managed_challenge"

        }

      }

    }

    expression = "true"

    description = "Zone-wide HTTP DDoS Override"

    enabled = true

  }

}


```

For more information about HTTP DDoS Attack Protection, refer to [HTTP DDoS Attack Protection managed ruleset](https://developers.cloudflare.com/ddos-protection/managed-rulesets/http/).

## Example: Configure Network-layer DDoS Attack Protection

This example configures the [Network-layer DDoS Attack Protection](https://developers.cloudflare.com/ddos-protection/managed-rulesets/network/) managed ruleset for an account using Terraform, changing the sensitivity level of rule with ID ...e954e98b  to `low` using an override.

Important

* Only Magic Transit and Spectrum customers on an Enterprise plan can configure this managed ruleset using overrides.
* This managed ruleset only supports overrides at the account level.

* [ Terraform (v5) ](#tab-panel-8567)
* [ Terraform (v4) ](#tab-panel-8568)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `L4 DDoS Managed Ruleset Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "account_level_network_ddos_config" {

  account_id  = var.cloudflare_account_id

  name        = "Network-layer DDoS Attack Protection entry point ruleset"

  description = ""

  kind        = "root"

  phase       = "ddos_l4"


  rules = [{

    ref         = "override_l7_ddos_ruleset_dst_ip"

    description = "Override the HTTP DDoS Attack Protection managed ruleset"

    expression  = "ip.dst in { 192.0.2.0/24 }"

    action      = "execute"

    action_parameters = {

      # Cloudflare L3/4 DDoS Attack Protection Ruleset

      id = "3b64149bfa6e4220bbbc2bd6db589552"

      overrides = {

        rules = [{

          # Rule: Generic high-volume UDP traffic flows.

          id                = "599dab0942ff4898ac1b7797e954e98b"

          sensitivity_level = "low"

        }]

      }

    }

  }]

}


```

```

resource "cloudflare_ruleset" "account_level_network_ddos_config" {

  account_id  = "<ACCOUNT_ID>"

  name        = "Network-layer DDoS Attack Protection entry point ruleset"

  description = ""

  kind        = "root"

  phase       = "ddos_l4"


  rules {

    ref         = "override_l7_ddos_ruleset_dst_ip"

    description = "Override the HTTP DDoS Attack Protection managed ruleset"

    expression  = "ip.dst in { 192.0.2.0/24 }"

    action      = "execute"

    action_parameters {

      # Cloudflare L3/4 DDoS Attack Protection Ruleset

      id = "3b64149bfa6e4220bbbc2bd6db589552"

      overrides {

        rules {

          # Rule: Generic high-volume UDP traffic flows.

          id                = "599dab0942ff4898ac1b7797e954e98b"

          sensitivity_level = "low"

        }

      }

    }

  }

}


```

For more information about Network-layer DDoS Attack Protection, refer to [Network-layer DDoS Attack Protection managed ruleset](https://developers.cloudflare.com/ddos-protection/managed-rulesets/network/).

---

## Use case: Mitigate large HTTP DDoS attacks and monitor flagged traffic

In the following example, a customer is concerned about false positives, but wants to get protection against large HTTP DDoS attacks. The two rules, containing two overrides each, in their [HTTP DDoS protection](https://developers.cloudflare.com/ddos-protection/managed-rulesets/http/) configuration will have the following behavior:

1. Mitigate any large HTTP DDoS attacks by configuring a rule with a _Low_ [sensitivity level](https://developers.cloudflare.com/ddos-protection/managed-rulesets/http/override-parameters/#sensitivity-level) and a _Block_ action.
2. Monitor traffic being flagged by the DDoS protection system by configuring a rule with the default sensitivity level (_High_) and a _Log_ action.

The order of the rules is important: the rule with the highest sensitivity level must come after the rule with the lowest sensitivity level, otherwise it will never be evaluated.

Important considerations

* When a DDoS attack mitigation is ongoing, Cloudflare will check the rules order and apply the first one that matches both the expression and the sensitivity level.
* Since rules are evaluated in order and the first one to match the conditions of both the expression and the sensitivity level will get applied, take care when editing and reordering existing rules. Changing a rule from Block to Log may allow attack traffic to reach your web property.
* Overrides will not affect read-only rules in the managed ruleset.

* [ Terraform (v5) ](#tab-panel-8569)
* [ Terraform (v4) ](#tab-panel-8570)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `HTTP DDoS Managed Ruleset Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "zone_level_http_ddos_config" {

  zone_id     = var.cloudflare_zone_id

  name        = "HTTP DDoS - Terraform managed"

  description = ""

  kind        = "zone"

  phase       = "ddos_l7"


  # The resource configuration contains two rules:

  #  1. The first rule has the lowest sensitivity level (highest threshold)

  #     and it will block attacks.

  #  2. The second rule has a higher sensitivity level (lower threshold) and

  #     will only apply a Log action.

  #

  # In practice, evaluation stops whenever a rule matches both the expression

  # and the threshold, so the rule order is important:

  #   - When the traffic rate is below the (low) threshold of the default

  #     sensitivity level ('High'), no rules match (no action is applied).

  #   - When the traffic rate is between the thresholds of the 'Low' and

  #     default ('High') sensitivity levels, the first rule does not match,

  #     but the second rule does (traffic gets logged).

  #   - When the traffic rate goes above the (high) threshold of the 'Low'

  #     sensitivity level, the first rule matches (traffic gets blocked).

  #

  # The DDoS protection systems will still apply mitigation actions to incoming

  # traffic when rates exceed the threshold of the _Essentially Off_ sensitivity

  # level.


  rules = [

    {

      ref         = "l7_ddos_block_traffic_low_threshold"

      description = "At the low sensitivity threshold, block the traffic"

      expression  = "true"

      action      = "execute"

      action_parameters = {

        # Cloudflare L7 DDoS Attack Protection Ruleset

        id = "4d21379b4f9f4bb088e0729962c8b3cf"

        overrides = {

          rules = [

            {

              # Rule: HTTP requests from known botnet (signature #4).

              id                = "29d170ba2f004cc787b1ac272c9e04e7"

              sensitivity_level = "low"

              action            = "block"

            },

            {

              # Rule: HTTP requests with unusual HTTP headers or URI path (signature #16).

              id                = "60a48054bbcf4014ac63c44f1712a123"

              sensitivity_level = "low"

              action            = "block"

            },

          ]

        }

      }

    },

    {

      ref         = "l7_ddos_log_default_threshold"

      description = "At the default sensitivity threshold, log to see if any legitimate traffic gets caught"

      expression  = "true"

      action      = "execute"

      action_parameters = {

        # Cloudflare L7 DDoS Attack Protection Ruleset

        id = "4d21379b4f9f4bb088e0729962c8b3cf"

        overrides = {

          rules = [

            {

              # Rule: HTTP requests from known botnet (signature #4).

              id                = "29d170ba2f004cc787b1ac272c9e04e7"

              sensitivity_level = "default"

              action            = "log"

            },

            {

              # Rule: HTTP requests with unusual HTTP headers or URI path (signature #16).

              id                = "60a48054bbcf4014ac63c44f1712a123"

              sensitivity_level = "default"

              action            = "log"

            },

          ]

        }

      }

    },

  ]

}


```

```

variable "zone_id" {

  default = "<ZONE_ID>"

}


resource "cloudflare_ruleset" "zone_level_http_ddos_config" {

  zone_id     = var.zone_id

  name        = "HTTP DDoS - Terraform managed"

  description = ""

  kind        = "zone"

  phase       = "ddos_l7"


  # The resource configuration contains two rules:

  #  1. The first rule has the lowest sensitivity level (highest threshold)

  #     and it will block attacks.

  #  2. The second rule has a higher sensitivity level (lower threshold) and

  #     will only apply a Log action.

  #

  # In practice, evaluation stops whenever a rule matches both the expression

  # and the threshold, so the rule order is important:

  #   - When the traffic rate is below the (low) threshold of the default

  #     sensitivity level ('High'), no rules match (no action is applied).

  #   - When the traffic rate is between the thresholds of the 'Low' and

  #     default ('High') sensitivity levels, the first rule does not match,

  #     but the second rule does (traffic gets logged).

  #   - When the traffic rate goes above the (high) threshold of the 'Low'

  #     sensitivity level, the first rule matches (traffic gets blocked).

  #

  # The DDoS protection systems will still apply mitigation actions to incoming

  # traffic when rates exceed the threshold of the _Essentially Off_ sensitivity

  # level.


  rules {

    ref         = "l7_ddos_block_traffic_low_threshold"

    description = "At the low sensitivity threshold, block the traffic"

    expression  = "true"

    action      = "execute"

    action_parameters {

      # Cloudflare L7 DDoS Attack Protection Ruleset

      id = "4d21379b4f9f4bb088e0729962c8b3cf"

      overrides {

        rules {

          # Rule: HTTP requests from known botnet (signature #4).

          id                = "29d170ba2f004cc787b1ac272c9e04e7"

          sensitivity_level = "low"

          action            = "block"

        }

        rules {

          # Rule: HTTP requests with unusual HTTP headers or URI path (signature #16).

          id                = "60a48054bbcf4014ac63c44f1712a123"

          sensitivity_level = "low"

          action            = "block"

        }

      }

    }

  }


  rules {

    ref         = "l7_ddos_log_default_threshold"

    description = "At the default sensitivity threshold, log to see if any legitimate traffic gets caught"

    expression  = "true"

    action      = "execute"

    action_parameters {

      # Cloudflare L7 DDoS Attack Protection Ruleset

      id = "4d21379b4f9f4bb088e0729962c8b3cf"

      overrides {

        rules {

          # Rule: HTTP requests from known botnet (signature #4).

          id                = "29d170ba2f004cc787b1ac272c9e04e7"

          sensitivity_level = "default"

          action            = "log"

        }

        rules {

          # Rule: HTTP requests with unusual HTTP headers or URI path (signature #16).

          id                = "60a48054bbcf4014ac63c44f1712a123"

          sensitivity_level = "default"

          action            = "log"

        }

      }

    }

  }

}


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/additional-configurations/","name":"Additional configurations"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/additional-configurations/ddos-managed-rulesets/","name":"DDoS managed rulesets configuration using Terraform"}}]}
```

---

---
title: Configure Bulk Redirects using Terraform
description: Create Bulk Redirects using the Terraform Cloudflare provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/rules/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform)[ Redirects ](https://developers.cloudflare.com/search/?tags=Redirects) 

# Configure Bulk Redirects using Terraform

Note

Terraform code snippets below refer to the v4 SDK only.

This Terraform example configures account-level Bulk Redirects. It creates a [Bulk Redirect List](https://developers.cloudflare.com/rules/url-forwarding/bulk-redirects/concepts/#bulk-redirect-lists) populated with [URL redirects](https://developers.cloudflare.com/rules/url-forwarding/bulk-redirects/concepts/#url-redirects) and a corresponding [Bulk Redirect Rule](https://developers.cloudflare.com/rules/url-forwarding/bulk-redirects/concepts/#bulk-redirect-rules) to activate them.

```

# Cloudflare account ID

variable "cloudflare_account_id" {

  default = "<ACCOUNT_ID>"

}


# Bulk redirect list description

variable "bulk_redirect_list_description" {

  default = "my bulk redirect description"

}


# Bulk redirect list name

variable "bulk_redirect_list_name" {

  default = "my_bulk_redirect_list_name"

}


# Bulk redirect list item (URL redirect)

variable "bulk_redirects" {

  type = map(object({

    source_url  = string

    target_url  = string

    status_code = number

  }))


  default = {

    "redirect1" = {

      source_url = "https://source.url/redirect/1"

      target_url = "https://target.url/?redirect=1"

      status_code = 301

    }

    "redirect2" = {

      source_url = "https://source.url/redirect/2"

      target_url = "https://target.url/?redirect=2"

      status_code = 302

    }

    "redirect3" = {

      source_url = "https://source.url/redirect/3"

      target_url = "https://target.url/?redirect=3"

      status_code = 307

    }

  }

}


# Create redirect list

resource "cloudflare_list" "bulk_redirect_to_id" {

  account_id  = var.cloudflare_account_id

  name        = var.bulk_redirect_list_name

  description = var.bulk_redirect_list_description

  kind        = "redirect"

}


# Add redirect item into the redirect list

resource "cloudflare_list_item" "bulk_redirect_to_id_item" {

  for_each = { for redirect in var.bulk_redirects : "${redirect.source_url}" => redirect }


  account_id = var.cloudflare_account_id

  list_id    = cloudflare_list.bulk_redirect_to_id.id


  redirect {

    source_url  = each.value.source_url

    target_url  = each.value.target_url

    status_code = each.value.status_code

  }


  depends_on = [

    cloudflare_list.bulk_redirect_to_id

  ]


}


# Create bulk redirect and attach redirect list

resource "cloudflare_ruleset" "bulk_root_redirect_to_id" {

  account_id  = var.cloudflare_account_id

  name        = var.bulk_redirect_list_name

  description = var.bulk_redirect_list_description

  kind        = "root"

  phase       = "http_request_redirect"


  rules {

    action = "redirect"

    action_parameters {

      from_list {

        name = var.bulk_redirect_list_name

        key  = "http.request.full_uri"

      }

    }

    expression  = "http.request.full_uri in ${"$"}${var.bulk_redirect_list_name}"

    description = var.bulk_redirect_list_description

    enabled     = true

  }


  depends_on = [

    cloudflare_list_item.bulk_redirect_to_id_item

  ]

}


```

## Required token permissions

Your API token must have at least the following [permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/):

* [ Dashboard ](#tab-panel-7739)
* [ API ](#tab-panel-7740)

* Account Filter Lists > Edit
* Bulk URL Redirects > Edit

* Account Rule Lists Write
* Bulk URL Redirects Write

## Additional resources

For additional guidance on using Terraform with Cloudflare, refer to the following resources:

* [Terraform documentation](https://developers.cloudflare.com/terraform/)
* [Cloudflare Provider for Terraform ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) (reference documentation)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/rules/","name":"Rules"}},{"@type":"ListItem","position":3,"item":{"@id":"/rules/url-forwarding/","name":"Redirects"}},{"@type":"ListItem","position":4,"item":{"@id":"/rules/url-forwarding/bulk-redirects/","name":"Bulk Redirects"}},{"@type":"ListItem","position":5,"item":{"@id":"/rules/url-forwarding/bulk-redirects/terraform-example/","name":"Configure Bulk Redirects using Terraform"}}]}
```

---

---
title: Terraform example
description: Create Cache Rules using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/cache/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform) 

# Terraform example

The following example defines a single cache rule for a zone using Terraform. The rule configures several cache settings and sets a custom cache key for incoming requests addressed at `example.net`.

Terraform `cloudflare_ruleset` resource

```

# Cache rule configuring cache settings and defining custom cache keys

resource "cloudflare_ruleset" "cache_rules_example" {

  zone_id     = "<ZONE_ID>"

  name        = "Set cache settings"

  description = "Set cache settings for incoming requests"

  kind        = "zone"

  phase       = "http_request_cache_settings"


  rules {

    ref         = "cache_settings_custom_cache_key"

    description = "Set cache settings and custom cache key for example.net"

    expression  = "(http.host eq \"example.net\")"

    action      = "set_cache_settings"

    action_parameters {

      edge_ttl {

        mode    = "override_origin"

        default = 60

        status_code_ttl {

          status_code = 200

          value       = 50

        }

        status_code_ttl {

          status_code_range {

            from = 201

            to   = 300

          }

          value = 30

        }

      }

      browser_ttl {

        mode = "respect_origin"

      }

      serve_stale {

        disable_stale_while_updating = true

      }

      respect_strong_etags = true

      cache_key {

        ignore_query_strings_order = false

        cache_deception_armor      = true

        custom_key {

          query_string {

            exclude {

              all = true

            }

          }

          header {

            include        = ["habc", "hdef"]

            check_presence = ["habc_t", "hdef_t"]

            exclude_origin = true

          }

          cookie {

            include        = ["cabc", "cdef"]

            check_presence = ["cabc_t", "cdef_t"]

          }

          user {

            device_type = true

            geo         = false

          }

          host {

            resolved = true

          }

        }

      }

      origin_error_page_passthru = false

    }

  }

}


```

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications) in the Terraform documentation.

For additional guidance on using Terraform with Cloudflare, refer to [Terraform](https://developers.cloudflare.com/terraform/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/cache/","name":"Cache / CDN"}},{"@type":"ListItem","position":3,"item":{"@id":"/cache/how-to/","name":"Cache configuration"}},{"@type":"ListItem","position":4,"item":{"@id":"/cache/how-to/cache-rules/","name":"Cache Rules"}},{"@type":"ListItem","position":5,"item":{"@id":"/cache/how-to/cache-rules/terraform-example/","name":"Terraform example"}}]}
```

---

---
title: Define a single configuration rule using Terraform
description: Create a configuration rule using Terraform to turn off Email Obfuscation and Browser Integrity Check for API requests in a given zone.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/rules/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform) 

# Define a single configuration rule using Terraform

Create a configuration rule using Terraform to turn off Email Obfuscation and Browser Integrity Check for API requests in a given zone.

Note

Terraform code snippets below refer to the v4 SDK only.

The following example defines a single configuration rule for a zone using Terraform. The rule disables Email Obfuscation and Browser Integrity Check for API requests.

```

# Disable a couple of Cloudflare settings for API requests

resource "cloudflare_ruleset" "http_config_rules_example" {

  zone_id     = "<ZONE_ID>"

  name        = "Config rules ruleset"

  description = "Set configuration rules for incoming requests"

  kind        = "zone"

  phase       = "http_config_settings"


  rules {

    ref         = "disable_obfuscation_bic"

    description = "Disable email obfuscation and BIC for API requests"

    expression  = "(http.request.uri.path matches \"^/api/\")"

    action      = "set_config"

    action_parameters {

      email_obfuscation = false

      bic               = false

    }

  }

}


```

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications) in the Terraform documentation.

## Additional resources

For additional guidance on using Terraform with Cloudflare, refer to the following resources:

* [Terraform documentation](https://developers.cloudflare.com/terraform/)
* [Cloudflare Provider for Terraform ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) (reference documentation)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/rules/","name":"Rules"}},{"@type":"ListItem","position":3,"item":{"@id":"/rules/configuration-rules/","name":"Configuration Rules"}},{"@type":"ListItem","position":4,"item":{"@id":"/rules/configuration-rules/examples/","name":"Configuration Rules examples"}},{"@type":"ListItem","position":5,"item":{"@id":"/rules/configuration-rules/examples/define-single-configuration-terraform/","name":"Define a single configuration rule using Terraform"}}]}
```

---

---
title: Define a single origin rule using Terraform
description: Create an origin rule using Terraform to override the `Host` header, the resolved hostname, and the destination port of API requests.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/rules/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform)[ Headers ](https://developers.cloudflare.com/search/?tags=Headers) 

# Define a single origin rule using Terraform

Create an origin rule using Terraform to override the `Host` header, the resolved hostname, and the destination port of API requests.

Note

Terraform code snippets below refer to the v4 SDK only.

The following example defines a single origin rule for a zone using Terraform. The rule overrides the `Host` header, the resolved hostname, and the destination port of API requests.

```

# Change origin for API requests

resource "cloudflare_ruleset" "http_origin_example" {

  zone_id     = "<ZONE_ID>"

  name        = "Change origin"

  description = ""

  kind        = "zone"

  phase       = "http_request_origin"


  rules {

    ref         = "change_api_origin"

    description = "Change origin of API requests"

    expression  = "(http.request.uri.path matches \"^/api/\")"

    action      = "route"

    action_parameters {

      host_header = "example.net"

      origin {

        host = "example.net"

        port = 8000

      }

    }

  }

}


```

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications) in the Terraform documentation.

## Additional resources

For additional guidance on using Terraform with Cloudflare, refer to the following resources:

* [Terraform documentation](https://developers.cloudflare.com/terraform/)
* [Cloudflare Provider for Terraform ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) (reference documentation)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/rules/","name":"Rules"}},{"@type":"ListItem","position":3,"item":{"@id":"/rules/origin-rules/","name":"Origin Rules"}},{"@type":"ListItem","position":4,"item":{"@id":"/rules/origin-rules/examples/","name":"Origin Rules examples"}},{"@type":"ListItem","position":5,"item":{"@id":"/rules/origin-rules/examples/define-single-origin-terraform/","name":"Define a single origin rule using Terraform"}}]}
```

---

---
title: Create a redirect rule using Terraform
description: Create Single Redirect rules using the Terraform Cloudflare provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/rules/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform)[ Redirects ](https://developers.cloudflare.com/search/?tags=Redirects) 

# Create a redirect rule using Terraform

Note

Terraform code snippets below refer to the v4 SDK only.

The following example defines a single redirect rule for a zone using Terraform. The rule creates a static URL redirect for visitors requesting the contacts page using an old URL.

```

# Single Redirects resource

resource "cloudflare_ruleset" "single_redirects_example" {

  zone_id     = "<ZONE_ID>"

  name        = "redirects"

  description = "Redirects ruleset"

  kind        = "zone"

  phase       = "http_request_dynamic_redirect"


  rules {

    ref         = "redirect_old_url"

    description = "Redirect visitors still using old URL"

    expression  = "(http.request.uri.path matches \"^/contact-us/\")"

    action      = "redirect"

    action_parameters {

      from_value {

        status_code = 301

        target_url {

          value = "/contacts/"

        }

        preserve_query_string = false

      }

    }

  }

}


```

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications) in the Terraform documentation.

## Additional resources

For additional guidance on using Terraform with Cloudflare, refer to the following resources:

* [Terraform documentation](https://developers.cloudflare.com/terraform/)
* [Cloudflare Provider for Terraform ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) (reference documentation)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/rules/","name":"Rules"}},{"@type":"ListItem","position":3,"item":{"@id":"/rules/url-forwarding/","name":"Redirects"}},{"@type":"ListItem","position":4,"item":{"@id":"/rules/url-forwarding/single-redirects/","name":"Single Redirects"}},{"@type":"ListItem","position":5,"item":{"@id":"/rules/url-forwarding/single-redirects/terraform-example/","name":"Create a redirect rule using Terraform"}}]}
```

---

---
title: Configure Snippets using Terraform
description: Create Snippets using the Terraform Cloudflare provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/rules/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

### Tags

[ Terraform ](https://developers.cloudflare.com/search/?tags=Terraform) 

# Configure Snippets using Terraform

You can create Snippets using the [Terraform Cloudflare provider ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest).

To get started with Terraform for Cloudflare configuration, refer to [Get started](https://developers.cloudflare.com/terraform/installing/).

## Example configuration

Note

Terraform code snippets below refer to the v4 SDK only.

The following example Terraform configuration creates a snippet and an associated snippet rule that defines when the snippet code will run. The snippet code is loaded from the `file1.js` file in your machine.

```

resource "cloudflare_snippet" "my_snippet" {

  zone_id  = "<ZONE_ID>"

  name = "my_test_snippet_1"

  main_module = "file1.js"

  files {

    name = "file1.js"

    content = file("file1.js")

  }

}


resource "cloudflare_snippet_rules" "cookie_snippet_rule" {

  zone_id  = "<ZONE_ID>"

  rules {

    enabled = true

    expression = "http.cookie eq \"a=b\""

    description = "Trigger snippet on specific cookie"

    snippet_name = "my_test_snippet_1"

  }

  depends_on = [cloudflare_snippet.my_snippet]

}


```

The name of a snippet can only contain the characters `a-z`, `0-9`, and `_` (underscore). The name must be unique in the context of the zone. You cannot change the snippet name after creating the snippet.

All `snippet_name` values in the `cloudflare_snippet_rules` resource must match the names of existing snippets.

## More resources

Refer to the [Terraform Cloudflare provider documentation ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs) for more information on the `cloudflare_snippet` and `cloudflare_snippet_rules` resources.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/rules/","name":"Rules"}},{"@type":"ListItem","position":3,"item":{"@id":"/rules/snippets/","name":"Cloudflare Snippets"}},{"@type":"ListItem","position":4,"item":{"@id":"/rules/snippets/create-terraform/","name":"Configure Snippets using Terraform"}}]}
```

---

---
title: Infrastructure as Code (IaC)
description: Deploy and manage Cloudflare Workers using Terraform, Pulumi, and the Cloudflare API SDKs.
image: https://developers.cloudflare.com/dev-products-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/workers/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Infrastructure as Code (IaC)

While [Wrangler](https://developers.cloudflare.com/workers/wrangler/configuration) makes it easy to upload and manage Workers, there are times when you need a more programmatic approach. This could involve using Infrastructure as Code (IaC) tools or interacting directly with the [Workers API](https://developers.cloudflare.com/api/resources/workers/). Examples include build and deploy scripts, CI/CD pipelines, custom developer tools, and automated testing.

To make this easier, Cloudflare provides SDK libraries for popular languages such as [cloudflare-typescript ↗](https://github.com/cloudflare/cloudflare-typescript) and [cloudflare-python ↗](https://github.com/cloudflare/cloudflare-python). For IaC, you can use tools like HashiCorp's Terraform and the [Cloudflare Terraform Provider](https://developers.cloudflare.com/terraform) to manage Workers resources.

Below are examples of deploying a Worker using different tools and languages, along with important considerations for managing Workers with IaC.

All of these examples need an [account ID](https://developers.cloudflare.com/fundamentals/account/find-account-and-zone-ids) and [API token](https://developers.cloudflare.com/fundamentals/api/get-started/create-token) (not Global API key) to work.

## Workers Bundling

None of the examples below do [Workers Bundling](https://developers.cloudflare.com/workers/wrangler/bundling). This is usually done with Wrangler or a tool like [esbuild ↗](https://esbuild.github.io).

Generally, you'd run this bundling step before applying your Terraform plan or using the API for script upload:

Terminal window

```

wrangler deploy --dry-run --outdir build


```

When using Wrangler for building and a different method for uploading, make sure to copy all of your config from `wrangler.json` into your Terraform config or API request. This is especially important with `compatibility_date` or flags your script relies on.

## Terraform

In this example, you need a local file named `my-script.mjs` with script content similar to the below examples. Learn more about the [Cloudflare Terraform Provider](https://developers.cloudflare.com/terraform/), and refer to the [Workers script resource example ↗](https://github.com/cloudflare/terraform-provider-cloudflare/blob/main/examples/resources/cloudflare%5Fworkers%5Fscript/resource.tf) for all available resource settings.

```

variable "account_id" {

  default = "replace_me"

}


resource "cloudflare_worker" "my_worker" {

  account_id = var.account_id

  name = "my-worker"

  observability = {

    enabled = true

  }

}


resource "cloudflare_worker_version" "my_worker_version" {

  account_id = var.account_id

  worker_id = cloudflare_worker.my_worker.id

  compatibility_date = "2025-02-21" # Set this to today's date

  main_module = "my-script.mjs"

  modules = [

    {

      name = "my-script.mjs"

      content_type = "application/javascript+module"

      # Replacement (version creation) is triggered whenever this file changes

      content_file = "my-script.mjs"

    }

  ]

}


resource "cloudflare_workers_deployment" "my_worker_deployment" {

  account_id = var.account_id

  script_name = cloudflare_worker.my_worker.name

  strategy = "percentage"

  versions = [{

    percentage = 100

    version_id = cloudflare_worker_version.my_worker_version.id

  }]

}


```

Notice how you do not have to manage all of these resources in Terraform. For example, you could use just the `cloudflare_worker` resource and seamlessly use Wrangler or your own deployment tools for Versions or Deployments.

## Bindings in Terraform

[Bindings](https://developers.cloudflare.com/workers/runtime-apis/bindings/) allow your Worker to interact with resources on the Cloudflare Developer Platform. In Terraform, bindings are configured differently than in Wrangler. Instead of separate top-level properties for each binding type (like `kv_namespaces`, `r2_buckets`, etc.), Terraform uses a single `bindings` array where each binding has a `type` property along with type-specific properties.

Below are examples of each binding type and their required properties:

### KV Namespace Binding

Bind to a [KV namespace](https://developers.cloudflare.com/kv/api/) for key-value storage:

```

bindings = [{

  type = "kv_namespace"

  name = "MY_KV"

  namespace_id = "your-kv-namespace-id"

}]


```

**Properties:**

* `type`: `"kv_namespace"`
* `name`: The variable name for the binding, accessible via `env.MY_KV`
* `namespace_id`: The ID of your KV namespace

### R2 Bucket Binding

Bind to an [R2 bucket](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) for object storage:

```

bindings = [{

  type = "r2_bucket"

  name = "MY_BUCKET"

  bucket_name = "my-bucket-name"

}]


```

**Properties:**

* `type`: `"r2_bucket"`
* `name`: The binding name to access via `env.MY_BUCKET`
* `bucket_name`: The name of your R2 bucket

### D1 Database Binding

Bind to a [D1 database](https://developers.cloudflare.com/d1/worker-api/) for SQL storage:

```

bindings = [{

  type = "d1"

  name = "DB"

  id = "your-database-id"

}]


```

**Properties:**

* `type`: `"d1"`
* `name`: The binding name to access via `env.DB`
* `id`: The ID of your D1 database

### Durable Object Binding

Bind to a [Durable Object](https://developers.cloudflare.com/durable-objects/api/) class:

```

bindings = [{

  type = "durable_object_namespace"

  name = "MY_DURABLE_OBJECT"

  class_name = "MyDurableObjectClass"

}]


```

**Properties:**

* `type`: `"durable_object_namespace"`
* `name`: The binding name to access via `env.MY_DURABLE_OBJECT`
* `class_name`: The exported class name of the Durable Object
* `script_name`: (Optional) The Worker script that exports this Durable Object class. Omit if the class is defined in the same Worker.

### Service Binding

Bind to another [Worker](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/) for Worker-to-Worker communication:

```

bindings = [{

  type = "service"

  name = "MY_SERVICE"

  service = "other-worker-name"

}]


```

**Properties:**

* `type`: `"service"`
* `name`: The binding name to access via `env.MY_SERVICE`
* `service`: The name of the target Worker
* `entrypoint`: (Optional) The named [entrypoint](https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/rpc/#named-entrypoints) to bind to

### Queue Binding

Bind to a [Queue](https://developers.cloudflare.com/queues/configuration/javascript-apis/) for message passing:

For producing messages:

```

bindings = [{

  type = "queue"

  name = "MY_QUEUE"

  queue_name = "my-queue"

}]


```

**Properties:**

* `type`: `"queue"`
* `name`: The binding name to access via `env.MY_QUEUE`
* `queue_name`: The name of your Queue

For consuming messages, configure your Worker as a consumer in the queue resource itself, not via bindings.

### Vectorize Binding

Bind to a [Vectorize index](https://developers.cloudflare.com/vectorize/) for vector search:

```

bindings = [{

  type = "vectorize"

  name = "VECTORIZE_INDEX"

  index_name = "my-index"

}]


```

**Properties:**

* `type`: `"vectorize"`
* `name`: The binding name to access via `env.VECTORIZE_INDEX`
* `index_name`: The name of your Vectorize index

### Workers AI Binding

Bind to [Workers AI](https://developers.cloudflare.com/workers-ai/) for AI inference:

```

bindings = [{

  type = "ai"

  name = "AI"

}]


```

**Properties:**

* `type`: `"ai"`
* `name`: The binding name to access via `env.AI`

### Hyperdrive Binding

Bind to a [Hyperdrive](https://developers.cloudflare.com/hyperdrive/) configuration for database connection pooling:

```

bindings = [{

  type = "hyperdrive"

  name = "HYPERDRIVE"

  id = "your-hyperdrive-config-id"

}]


```

**Properties:**

* `type`: `"hyperdrive"`
* `name`: The binding name to access via `env.HYPERDRIVE`
* `id`: The ID of your Hyperdrive configuration

### VPC Service Binding

Bind to a [VPC Service](https://developers.cloudflare.com/workers-vpc/configuration/vpc-services/) for accessing resources in your private network:

```

bindings = [{

  type = "vpc_service"

  name = "PRIVATE_API"

  service_id = "your-vpc-service-id"

}]


```

**Properties:**

* `type`: `"vpc_service"`
* `name`: The binding name to access via `env.PRIVATE_API`
* `service_id`: The ID of your VPC Service (from `cloudflare_connectivity_directory_service` or the dashboard)

You can create the VPC Service with Terraform using the `cloudflare_connectivity_directory_service` resource. For a full walkthrough, refer to [Configure VPC Services with Terraform](https://developers.cloudflare.com/workers-vpc/configuration/vpc-services/terraform/).

### Analytics Engine Binding

Bind to an [Analytics Engine](https://developers.cloudflare.com/analytics/analytics-engine/) dataset:

```

bindings = [{

  type = "analytics_engine"

  name = "ANALYTICS"

  dataset = "my_dataset"

}]


```

**Properties:**

* `type`: `"analytics_engine"`
* `name`: The binding name to access via `env.ANALYTICS`
* `dataset`: The name of your Analytics Engine dataset

### Environment Variables

For plain text environment variables, use the `plain_text` binding type:

```

bindings = [{

  type = "plain_text"

  name = "MY_VARIABLE"

  text = "my-value"

}]


```

**Properties:**

* `type`: `"plain_text"`
* `name`: The binding name to access via `env.MY_VARIABLE`
* `text`: The value of the environment variable

### Secret Text Binding

For encrypted secrets, use the `secret_text` binding type:

```

bindings = [{

  type = "secret_text"

  name = "API_KEY"

  text = var.api_key

}]


```

**Properties:**

* `type`: `"secret_text"`
* `name`: The binding name to access via `env.API_KEY`
* `text`: The secret value (will be encrypted)

### Complete Example

Here's an example combining multiple binding types:

```

resource "cloudflare_worker_version" "my_worker_version" {

  account_id = var.account_id

  worker_id = cloudflare_worker.my_worker.id

  compatibility_date = "2025-08-06"

  main_module = "worker.js"


  modules = [{

    name = "worker.js"

    content_type = "application/javascript+module"

    content_file = "worker.js"

  }]


  bindings = [

    {

      type = "kv_namespace"

      name = "MY_KV"

      namespace_id = var.kv_namespace_id

    },

    {

      type = "r2_bucket"

      name = "MY_BUCKET"

      bucket_name = "my-bucket"

    },

    {

      type = "d1"

      name = "DB"

      id = var.d1_database_id

    },

    {

      type = "service"

      name = "AUTH_SERVICE"

      service = "auth-worker"

    },

    {

      type = "plain_text"

      name = "ENVIRONMENT"

      text = "production"

    },

    {

      type = "secret_text"

      name = "API_KEY"

      text = var.api_key

    },

    {

      type = "vpc_service"

      name = "PRIVATE_API"

      service_id = var.vpc_service_id

    }

  ]

}


```

## Cloudflare API Libraries

This example uses the [cloudflare-typescript ↗](https://github.com/cloudflare/cloudflare-typescript) SDK which provides convenient access to the Cloudflare REST API from server-side JavaScript or TypeScript.

* [  JavaScript ](#tab-panel-9513)
* [  TypeScript ](#tab-panel-9514)

JavaScript

```

#!/usr/bin/env -S npm run tsn -T


/**

 * Create and deploy a Worker

 *

 * Docs:

 * - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/

 * - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/

 *

 * Prerequisites:

 * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/

 * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/

 * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/

 *

 * Environment variables:

 *   - CLOUDFLARE_API_TOKEN (required)

 *   - CLOUDFLARE_ACCOUNT_ID (required)

 *   - CLOUDFLARE_SUBDOMAIN (optional)

 *

 * Usage:

 *   Run this script to deploy a simple "Hello World" Worker.

 *   Access it at: my-hello-world-worker.$subdomain.workers.dev

 */


import { exit } from "node:process";


import Cloudflare from "cloudflare";


const WORKER_NAME = "my-hello-world-worker";

const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;


function loadConfig() {

  const apiToken = process.env["CLOUDFLARE_API_TOKEN"];

  if (!apiToken) {

    throw new Error(

      "Missing required environment variable: CLOUDFLARE_API_TOKEN",

    );

  }


  const accountId = process.env["CLOUDFLARE_ACCOUNT_ID"];

  if (!accountId) {

    throw new Error(

      "Missing required environment variable: CLOUDFLARE_ACCOUNT_ID",

    );

  }


  const subdomain = process.env["CLOUDFLARE_SUBDOMAIN"];


  return {

    apiToken,

    accountId,

    subdomain: subdomain || undefined,

    workerName: WORKER_NAME,

  };

}


const config = loadConfig();

const client = new Cloudflare({

  apiToken: config.apiToken,

});


async function main() {

  try {

    console.log("🚀 Starting Worker creation and deployment...");


    const scriptContent = `

      export default {

        async fetch(request, env, ctx) {

          return new Response(env.MESSAGE, { status: 200 });

        },

      }`.trim();


    let worker;

    try {

      worker = await client.workers.beta.workers.get(config.workerName, {

        account_id: config.accountId,

      });

      console.log(`♻️  Worker ${config.workerName} already exists. Using it.`);

    } catch (error) {

      if (!(error instanceof Cloudflare.NotFoundError)) {

        throw error;

      }

      console.log(`✏️  Creating Worker ${config.workerName}...`);

      worker = await client.workers.beta.workers.create({

        account_id: config.accountId,

        name: config.workerName,

        subdomain: {

          enabled: config.subdomain !== undefined,

        },

        observability: {

          enabled: true,

        },

      });

    }


    console.log(`⚙️  Worker id: ${worker.id}`);

    console.log("✏️  Creating Worker version...");


    // Create the first version of the Worker

    const version = await client.workers.beta.workers.versions.create(

      worker.id,

      {

        account_id: config.accountId,

        main_module: SCRIPT_FILENAME,

        compatibility_date: new Date().toISOString().split("T")[0],

        bindings: [

          {

            type: "plain_text",

            name: "MESSAGE",

            text: "Hello World!",

          },

        ],

        modules: [

          {

            name: SCRIPT_FILENAME,

            content_type: "application/javascript+module",

            content_base64: Buffer.from(scriptContent).toString("base64"),

          },

        ],

      },

    );


    console.log(`⚙️  Version id: ${version.id}`);

    console.log("🚚 Creating Worker deployment...");


    // Create a deployment and point all traffic to the version we created

    await client.workers.scripts.deployments.create(config.workerName, {

      account_id: config.accountId,

      strategy: "percentage",

      versions: [

        {

          percentage: 100,

          version_id: version.id,

        },

      ],

    });


    console.log("✅ Deployment successful!");


    if (config.subdomain) {

      console.log(`

🌍 Your Worker is live!

📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/

`);

    } else {

      console.log(`

⚠️  Set up a route, custom domain, or workers.dev subdomain to access your Worker.

Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.

`);

    }

  } catch (error) {

    console.error("❌ Deployment failed:", error);

    exit(1);

  }

}


main();


```

TypeScript

```

#!/usr/bin/env -S npm run tsn -T


/**

 * Create and deploy a Worker

 *

 * Docs:

 * - https://developers.cloudflare.com/workers/configuration/versions-and-deployments/

 * - https://developers.cloudflare.com/workers/platform/infrastructure-as-code/

 *

 * Prerequisites:

 * 1. Generate an API token: https://developers.cloudflare.com/fundamentals/api/get-started/create-token/

 * 2. Find your account ID: https://developers.cloudflare.com/fundamentals/setup/find-account-and-zone-ids/

 * 3. Find your workers.dev subdomain: https://developers.cloudflare.com/workers/configuration/routing/workers-dev/

 *

 * Environment variables:

 *   - CLOUDFLARE_API_TOKEN (required)

 *   - CLOUDFLARE_ACCOUNT_ID (required)

 *   - CLOUDFLARE_SUBDOMAIN (optional)

 *

 * Usage:

 *   Run this script to deploy a simple "Hello World" Worker.

 *   Access it at: my-hello-world-worker.$subdomain.workers.dev

 */


import { exit } from 'node:process';


import Cloudflare from 'cloudflare';


interface Config {

  apiToken: string;

  accountId: string;

  subdomain: string | undefined;

  workerName: string;

}


const WORKER_NAME = 'my-hello-world-worker';

const SCRIPT_FILENAME = `${WORKER_NAME}.mjs`;


function loadConfig(): Config {

  const apiToken = process.env['CLOUDFLARE_API_TOKEN'];

  if (!apiToken) {

    throw new Error('Missing required environment variable: CLOUDFLARE_API_TOKEN');

  }


  const accountId = process.env['CLOUDFLARE_ACCOUNT_ID'];

  if (!accountId) {

    throw new Error('Missing required environment variable: CLOUDFLARE_ACCOUNT_ID');

  }


  const subdomain = process.env['CLOUDFLARE_SUBDOMAIN'];


  return {

    apiToken,

    accountId,

    subdomain: subdomain || undefined,

    workerName: WORKER_NAME,

  };

}


const config = loadConfig();

const client = new Cloudflare({

  apiToken: config.apiToken,

});


async function main(): Promise<void> {

  try {

    console.log('🚀 Starting Worker creation and deployment...');


    const scriptContent = `

      export default {

        async fetch(request, env, ctx) {

          return new Response(env.MESSAGE, { status: 200 });

        },

      }`.trim();


    let worker;

    try {

      worker = await client.workers.beta.workers.get(config.workerName, {

        account_id: config.accountId,

      });

      console.log(`♻️  Worker ${config.workerName} already exists. Using it.`);

    } catch (error) {

      if (!(error instanceof Cloudflare.NotFoundError)) { throw error; }

      console.log(`✏️  Creating Worker ${config.workerName}...`);

      worker = await client.workers.beta.workers.create({

        account_id: config.accountId,

        name: config.workerName,

        subdomain: {

          enabled: config.subdomain !== undefined,

        },

        observability: {

          enabled: true,

        },

      });

    }


    console.log(`⚙️  Worker id: ${worker.id}`);

    console.log('✏️  Creating Worker version...');


    // Create the first version of the Worker

    const version = await client.workers.beta.workers.versions.create(worker.id, {

      account_id: config.accountId,

      main_module: SCRIPT_FILENAME,

      compatibility_date: new Date().toISOString().split('T')[0]!,

      bindings: [

        {

          type: 'plain_text',

          name: 'MESSAGE',

          text: 'Hello World!',

        },

      ],

      modules: [

        {

          name: SCRIPT_FILENAME,

          content_type: 'application/javascript+module',

          content_base64: Buffer.from(scriptContent).toString('base64'),

        },

      ],

    });


    console.log(`⚙️  Version id: ${version.id}`);

    console.log('🚚 Creating Worker deployment...');


    // Create a deployment and point all traffic to the version we created

    await client.workers.scripts.deployments.create(config.workerName, {

      account_id: config.accountId,

      strategy: 'percentage',

      versions: [

        {

            percentage: 100,

            version_id: version.id,

          },

        ],

    });


    console.log('✅ Deployment successful!');


    if (config.subdomain) {

      console.log(`

🌍 Your Worker is live!

📍 URL: https://${config.workerName}.${config.subdomain}.workers.dev/

`);

    } else {

      console.log(`

⚠️  Set up a route, custom domain, or workers.dev subdomain to access your Worker.

Add CLOUDFLARE_SUBDOMAIN to your environment variables to set one up automatically.

`);

    }

  } catch (error) {

    console.error('❌ Deployment failed:', error);

    exit(1);

  }

}


main();


```

## Cloudflare REST API

Open a terminal or create a shell script to upload a Worker and manage versions and deployments with curl. Workers scripts are JavaScript [ES Modules ↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules), but we also support [Python Workers](https://developers.cloudflare.com/workers/languages/python/) (open beta) and [Rust Workers](https://developers.cloudflare.com/workers/languages/rust/).

Warning

This API is in beta. See the multipart/form-data API below for the stable API.

* [ ES Module ](#tab-panel-9509)
* [ Python ](#tab-panel-9510)

Terminal window

```

account_id="replace_me"

api_token="replace_me"

worker_name="my-hello-world-worker"


worker_script_base64=$(echo '

export default {

  async fetch(request, env, ctx) {

    return new Response(env.MESSAGE, { status: 200 });

  }

};

' | base64)


# Note the below will fail if the worker already exists!

# Here's how to delete the Worker

#

# worker_id="replace-me"

# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \

#   -X DELETE \

#   -H "Authorization: Bearer $api_token"


# Create the Worker

worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "name": "'$worker_name'"

  }' \

  | jq -r '.result.id')


echo "\nWorker ID: $worker_id\n"


# Upload the Worker's first version

version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "compatibility_date": "2025-08-06",

    "main_module": "'$worker_name'.mjs",

    "modules": [

      {

        "name": "'$worker_name'.mjs",

        "content_type": "application/javascript+module",

        "content_base64": "'$worker_script_base64'"

      }

    ],

    "bindings": [

      {

        "type": "plain_text",

        "name": "MESSAGE",

        "text": "Hello World!"

      }

    ]

  }' \

  | jq -r '.result.id')


echo "\nVersion ID: $version_id\n"


# Create a deployment for the Worker

deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "strategy": "percentage",

    "versions": [

      {

        "percentage": 100,

        "version_id": "'$version_id'"

      }

    ]

  }' \

  | jq -r '.result.id')


echo "\nDeployment ID: $deployment_id\n"


```

[Python Workers](https://developers.cloudflare.com/workers/languages/python/) have their own special `text/x-python` content type and `python_workers` compatibility flag.

Terminal window

```

account_id="replace_me"

api_token="replace_me"

worker_name="my-hello-world-worker"


worker_script_base64=$(echo '

from workers import WorkerEntrypoint, Response


class Default(WorkerEntrypoint):

    async def fetch(self, request):

        return Response(self.env.MESSAGE)

' | base64)


# Note the below will fail if the worker already exists!

# Here's how to delete the Worker

#

# worker_id="replace-me"

# curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id" \

#   -X DELETE \

#   -H "Authorization: Bearer $api_token"


# Create the Worker

worker_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "name": "'$worker_name'"

  }' \

  | jq -r '.result.id')


echo "\nWorker ID: $worker_id\n"


# Upload the Worker's first version

version_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/workers/$worker_id/versions" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "compatibility_date": "2025-08-06",

    "compatibility_flags": [

      "python_workers"

    ],

    "main_module": "'$worker_name'.py",

    "modules": [

      {

        "name": "'$worker_name'.py",

        "content_type": "text/x-python",

        "content_base64": "'$worker_script_base64'"

      }

    ],

    "bindings": [

      {

        "type": "plain_text",

        "name": "MESSAGE",

        "text": "Hello World!"

      }

    ]

  }' \

  | jq -r '.result.id')


echo "\nVersion ID: $version_id\n"


# Create a deployment for the Worker

deployment_id=$(curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name/deployments" \

  -X POST \

  -H "Authorization: Bearer $api_token" \

  -H "Content-Type: application/json" \

  -d '{

    "strategy": "percentage",

    "versions": [

      {

        "percentage": 100,

        "version_id": "'$version_id'"

      }

    ]

  }' \

  | jq -r '.result.id')


echo "\nDeployment ID: $deployment_id\n"


```

### multipart/form-data upload API

This API uses [multipart/form-data ↗](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) to upload a Worker and will implicitly create a version and deployment. The above API is recommended for direct management of versions and deployments.

* [ Workers ](#tab-panel-9511)
* [ Workers for Platforms ](#tab-panel-9512)

Terminal window

```

account_id="replace_me"

api_token="replace_me"

worker_name="my-hello-world-script"


script_content='export default {

  async fetch(request, env, ctx) {

    return new Response(env.MESSAGE, { status: 200 });

  }

};'


# Upload the Worker

curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/scripts/$worker_name" \

  -X PUT \

  -H "Authorization: Bearer $api_token" \

  -F "metadata={

    'main_module': '"$worker_name".mjs',

    'bindings': [

      {

        'type': 'plain_text',

        'name': 'MESSAGE',

        'text': 'Hello World!'

      }

    ],

    'compatibility_date': '$today'

  };type=application/json" \

  -F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF

$script_content

EOF


```

For [Workers for Platforms](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms), you can upload a [User Worker](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#user-workers) to a [dispatch namespace](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/how-workers-for-platforms-works/#dispatch-namespace). Note the [API endpoint](https://developers.cloudflare.com/api/resources/workers%5Ffor%5Fplatforms/subresources/dispatch/subresources/namespaces/subresources/scripts/methods/update/) is on `/workers/dispatch/namespaces/$DISPATCH_NAMESPACE/scripts/$SCRIPT_NAME`.

Terminal window

```

account_id="replace_me"

api_token="replace_me"

dispatch_namespace="replace_me"

worker_name="my-hello-world-script"


script_content='export default {

  async fetch(request, env, ctx) {

    return new Response(env.MESSAGE, { status: 200 });

  }

};'


# Create a dispatch namespace

curl https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces \

  -X POST \

  -H 'Content-Type: application/json' \

  -H "Authorization: Bearer $api_token" \

  -d '{

    "name": "'$dispatch_namespace'"

  }'


# Upload the Worker

curl "https://api.cloudflare.com/client/v4/accounts/$account_id/workers/dispatch/namespaces/$dispatch_namespace/scripts/$worker_name" \

  -X PUT \

  -H "Authorization: Bearer $api_token" \

  -F "metadata={

    'main_module': '"$worker_name".mjs',

    'bindings': [

      {

        'type': 'plain_text',

        'name': 'MESSAGE',

        'text': 'Hello World!'

      }

    ],

    'compatibility_date': '$today'

  };type=application/json" \

  -F "$worker_name.mjs=@-;filename=$worker_name.mjs;type=application/javascript+module" <<EOF

$script_content

EOF


```

### Python Workers

[Python Workers](https://developers.cloudflare.com/workers/languages/python/) (open beta) have their own special `text/x-python` content type and `python_workers` compatibility flag for uploading using the multipart/form-data API.

Terminal window

```

curl https://api.cloudflare.com/client/v4/accounts/<account_id>/workers/scripts/my-hello-world-script \

  -X PUT \

  -H 'Authorization: Bearer <api_token>' \

  -F 'metadata={

        "main_module": "my-hello-world-script.py",

        "bindings": [

          {

            "type": "plain_text",

            "name": "MESSAGE",

            "text": "Hello World!"

          }

        ],

        "compatibility_date": "$today",

        "compatibility_flags": [

          "python_workers"

        ]

      };type=application/json' \

  -F 'my-hello-world-script.py=@-;filename=my-hello-world-script.py;type=text/x-python' <<EOF

from workers import WorkerEntrypoint, Response


class Default(WorkerEntrypoint):

    async def fetch(self, request):

        return Response(self.env.MESSAGE)

EOF


```

## Considerations with Durable Objects

[Durable Object](https://developers.cloudflare.com/durable-objects/) migrations are applied with deployments. This means you can't bind to a Durable Object in a Version if a deployment doesn't exist i.e. migrations haven't been applied. For example, running this in Terraform will fail the first time the plan is applied:

```

resource "cloudflare_worker" "my_worker" {

  account_id = var.account_id

  name = "my-worker"

}


resource "cloudflare_worker_version" "my_worker_version" {

  account_id = var.account_id

  worker_id = cloudflare_worker.my_worker.id

  bindings = [

    {

      type = "durable_object_namespace"

      name = "my_durable_object"

      class_name = "MyDurableObjectClass"

    }

  ]

  migrations = {

    new_sqlite_classes = [

      "MyDurableObjectClass"

    ]

  }

  # ...version props omitted for brevity

}


resource "cloudflare_workers_deployment" "my_worker_deployment" {

  # ...deployment props omitted for brevity

}


```

To make this succeed, you first have to comment out the `durable_object` binding block, apply the plan, uncomment it, comment out the `migrations` block, then apply again. This time the plan will succeed. This also applies to the API or SDKs. This is an example where it makes sense to just manage the `cloudflare_worker` and/or `cloudflare_workers_deployment` resources while using Wrangler for build and Version management.

## Considerations with Worker Versions

### Resource immutability

Worker versions are immutable at the API level, meaning they cannot be updated after creation, only re-created with any desired changes. This means that meaningful changes to the `cloudflare_worker_version` Terraform resource will always trigger replacement. When the `cloudflare_worker_version` resource is replaced, a new version with the desired changes is created, but the previous version is not deleted. This ensures the Worker has a complete version history when managed via Terraform. In other words, versions are both immutable and append-only. When the parent `cloudflare_worker` resource is deleted, all existing versions associated with the Worker are also deleted.

### Module Content

Worker version modules support two mutually exclusive ways to provide content:

* **`content_file`** \- Points to a local file
* **`content_base64`** \- Inline base64-encoded content

In both cases, changes to the underlying content are tracked using the computed `content_sha256` attribute. Specifying content using the `content_file` attribute is preferred in almost all cases, as it avoids storing the content itself in state. Module content may be quite large (up to tens of megabytes), and storing it in state will bloat the state file and negatively affect the performance of Terraform operations. The main use case for the `content_base64` attribute is importing the `cloudflare_worker_version` Terraform resource from the API, discussed below.

### Import Behavior

**During import, Terraform always populates the `content_base64` attribute in state**, regardless of the attribute used in your config.

Terminal window

```

terraform import cloudflare_worker_version.my_worker_version <account_id>/<worker_id>/<version_id>


```

If your config uses `content_file`, there will be a mismatch after import (state uses `content_base64`, config uses `content_file`). This is expected.

Assuming the content of the local file referenced by `content_file` matches the imported content and their `content_sha256` values are the same, this will result in an in-place update of the `cloudflare_worker_version` Terraform resource. This should be an in-place update instead of a replacement because the underlying content is not changing (the `content_sha256` attribute is the same in both cases), and the resource does not need to be updated at the API level. The only thing that needs to be updated is Terraform state, which will switch from using `content_base64` to `content_file` after the update.

If Terraform instead wants to replace the resource, citing a difference in computed `content_sha256` values, then the content of the local file referenced by `content_file` does not match the imported content and the resource can't be cleanly imported without updating the local file to match the expected API value.

### Examples

**Using `content_file`:**

```

resource "cloudflare_worker_version" "content_file_example" {

  account_id  = var.account_id

  worker_id   = cloudflare_worker.example.id

  main_module = "worker.js"

  modules = [{

    name         = "worker.js"

    content_type = "application/javascript+module"

    content_file = "build/worker.js"

  }]

}


```

**Using `content_base64`:**

```

resource "cloudflare_worker_version" "content_base64_example" {

  account_id  = var.account_id

  worker_id   = cloudflare_worker.example.id

  main_module = "worker.js"

  modules = [{

    name           = "worker.js"

    content_type   = "application/javascript+module"

    content_base64 = base64encode("export default { async fetch() { return new Response('Hello world!') } }")

  }]

}


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/workers/","name":"Workers"}},{"@type":"ListItem","position":3,"item":{"@id":"/workers/platform/","name":"Platform"}},{"@type":"ListItem","position":4,"item":{"@id":"/workers/platform/infrastructure-as-code/","name":"Infrastructure as Code (IaC)"}}]}
```

---

---
title: Rate limiting rules configuration using Terraform
description: Create and configure Cloudflare rate limiting rules at the zone or account level using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Rate limiting rules configuration using Terraform

This page provides examples of creating [rate limiting rules](https://developers.cloudflare.com/waf/rate-limiting-rules/) in a zone or account using Terraform.

If you are using the Cloudflare API, refer to the following resources:

* [Create a rate limiting rule via API](https://developers.cloudflare.com/waf/rate-limiting-rules/create-api/)
* [Create a rate limiting ruleset via API](https://developers.cloudflare.com/waf/account/rate-limiting-rulesets/create-api/)

Note

For more information on configuring the previous version of rate limiting rules in Terraform, refer to the [cloudflare\_rate\_limit resource ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/rate%5Flimit) in the Terraform documentation.

## 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.

---

## Create a rate limiting rule at the zone level

This example creates a rate limiting rule in zone with ID `<ZONE_ID>` blocking traffic that exceeds the configured rate:

* [ Terraform (v5) ](#tab-panel-8571)
* [ Terraform (v4) ](#tab-panel-8572)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Zone WAF Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "zone_rl" {

  zone_id     = var.cloudflare_zone_id

  name        = "Rate limiting for my zone"

  description = ""

  kind        = "zone"

  phase       = "http_ratelimit"


  rules = [{

    ref         = "rate_limit_api_requests_ip"

    description = "Rate limit API requests by IP"

    expression  = "(http.request.uri.path matches \"^/api/\")"

    action      = "block"

    ratelimit = {

      characteristics     = ["cf.colo.id", "ip.src"]

      period              = 60

      requests_per_period = 100

      mitigation_timeout  = 600

    }

  }]

}


```

```

resource "cloudflare_ruleset" "zone_rl" {

  zone_id     = "<ZONE_ID>"

  name        = "Rate limiting for my zone"

  description = ""

  kind        = "zone"

  phase       = "http_ratelimit"


  rules {

    ref         = "rate_limit_api_requests_ip"

    description = "Rate limit API requests by IP"

    expression  = "(http.request.uri.path matches \"^/api/\")"

    action      = "block"

    ratelimit {

      characteristics = ["cf.colo.id", "ip.src"]

      period = 60

      requests_per_period = 100

      mitigation_timeout = 600

    }

  }

}


```

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

  
Use a single ruleset resource per phase

Each zone supports one [entry point ruleset](https://developers.cloudflare.com/ruleset-engine/about/rulesets/#entry-point-ruleset) per phase. Define all your rate limiting rules for a given zone within a single `cloudflare_ruleset` resource with `kind = "zone"` and `phase = "http_ratelimit"`.

## Create a rate limiting rule at the account level

Notes

* [Account-level rate limiting configuration](https://developers.cloudflare.com/waf/account/) requires an Enterprise plan with a paid add-on.
* Custom rulesets deployed at the account level will only apply to incoming traffic of zones on an Enterprise plan. The expression of your `execute` rule must end with `and cf.zone.plan eq "ENT"`.

This example defines a [custom ruleset](https://developers.cloudflare.com/ruleset-engine/custom-rulesets/) with a single rate limiting rule in account with ID `<ACCOUNT_ID>` that blocks traffic for the `/api/` path exceeding the configured rate. The second `cloudflare_ruleset` resource defines an `execute` rule that deploys the custom ruleset for traffic addressed at `example.com`.

* [ Terraform (v5) ](#tab-panel-8573)
* [ Terraform (v4) ](#tab-panel-8574)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Account WAF Write`
* `Account Rulesets Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "account_rl" {

  account_id  = var.cloudflare_account_id

  name        = "Rate limiting rules for APIs"

  description = ""

  kind        = "custom"

  phase       = "http_ratelimit"


  rules = [{

    ref         = "rate_limit_api_ip"

    description = "Rate limit API requests by IP"

    expression  = "http.request.uri.path contains \"/api/\""

    action      = "block"

    ratelimit = {

      characteristics     = ["cf.colo.id", "ip.src"]

      period              = 60

      requests_per_period = 100

      mitigation_timeout  = 600

    }

  }]

}


# Account-level entry point ruleset for the 'http_ratelimit' phase

resource "cloudflare_ruleset" "account_rl_entrypoint" {

  account_id  = var.cloudflare_account_id

  name        = "Account-level rate limiting"

  description = ""

  kind        = "root"

  phase       = "http_ratelimit"


  depends_on = [cloudflare_ruleset.account_rl]


  rules = [{

    # Deploy the previously defined custom ruleset containing a rate limiting rule

    ref         = "deploy_rate_limit_example_com"

    description = "Deploy custom ruleset with RL rule"

    expression  = "cf.zone.name eq \"example.com\" and cf.zone.plan eq \"ENT\""

    action      = "execute"

    action_parameters = {

      id = cloudflare_ruleset.account_rl.id

    }

  }]

}


```

```

resource "cloudflare_ruleset" "account_rl" {

  account_id  = "<ACCOUNT_ID>"

  name        = "Rate limiting rules for APIs"

  description = ""

  kind        = "custom"

  phase       = "http_ratelimit"


  rules {

    ref         = "rate_limit_api_ip"

    description = "Rate limit API requests by IP"

    expression  = "http.request.uri.path contains \"/api/\""

    action      = "block"

    ratelimit {

      characteristics     = ["cf.colo.id", "ip.src"]

      period              = 60

      requests_per_period = 100

      mitigation_timeout  = 600

    }

  }

}


# Account-level entry point ruleset for the 'http_ratelimit' phase

resource "cloudflare_ruleset" "account_rl_entrypoint" {

  account_id  = "<ACCOUNT_ID>"

  name        = "Account-level rate limiting"

  description = ""

  kind        = "root"

  phase       = "http_ratelimit"


  depends_on = [cloudflare_ruleset.account_rl]


  rules {

    # Deploy the previously defined custom ruleset containing a rate limiting rule

    ref         = "deploy_rate_limit_example_com"

    description = "Deploy custom ruleset with RL rule"

    expression  = "cf.zone.name eq \"example.com\" and cf.zone.plan eq \"ENT\""

    action      = "execute"

    action_parameters {

      id = cloudflare_ruleset.account_rl.id

    }

  }

}


```

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

  
## Create an advanced rate limiting rule

This example creates a rate limiting rule in zone with ID `<ZONE_ID>` with:

* A custom counting expression that includes a response field (`http.response.code`).
* A custom JSON response for rate limited requests.

* [ Terraform (v5) ](#tab-panel-8575)
* [ Terraform (v4) ](#tab-panel-8576)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Zone WAF Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "zone_rl_custom_response" {

  zone_id     = var.cloudflare_zone_id

  name        = "Advanced rate limiting rule for my zone"

  description = ""

  kind        = "zone"

  phase       = "http_ratelimit"


  rules = [{

    ref         = "rate_limit_example_com_status_404"

    description = "Rate limit requests to www.example.com when exceeding the threshold of 404 responses on /status/"

    expression  = "http.host eq \"www.example.com\" and (http.request.uri.path matches \"^/status/\")"

    action      = "block"

    action_parameters = {

      response = {

        status_code  = 429

        content      = "{\"response\": \"block\"}"

        content_type = "application/json"

      }

    }

    ratelimit = {

      characteristics     = ["ip.src", "cf.colo.id"]

      period              = 10

      requests_per_period = 5

      mitigation_timeout  = 30

      counting_expression = "(http.host eq \"www.example.com\") and (http.request.uri.path matches \"^/status/\") and (http.response.code eq 404)"

    }

  }]

}


```

```

resource "cloudflare_ruleset" "zone_rl_custom_response" {

  zone_id     = "<ZONE_ID>"

  name        = "Advanced rate limiting rule for my zone"

  description = ""

  kind        = "zone"

  phase       = "http_ratelimit"


  rules {

    ref         = "rate_limit_example_com_status_404"

    description = "Rate limit requests to www.example.com when exceeding the threshold of 404 responses on /status/"

    expression  = "http.host eq \"www.example.com\" and (http.request.uri.path matches \"^/status/\")"

    action      = "block"

    action_parameters {

      response {

        status_code  = 429

        content      = "{\"response\": \"block\"}"

        content_type = "application/json"

      }

    }

    ratelimit {

      characteristics     = ["ip.src", "cf.colo.id"]

      period              = 10

      requests_per_period = 5

      mitigation_timeout  = 30

      counting_expression = "(http.host eq \"www.example.com\") and (http.request.uri.path matches \"^/status/\") and (http.response.code eq 404)"

    }

  }

}


```

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

  

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/additional-configurations/","name":"Additional configurations"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/additional-configurations/rate-limiting-rules/","name":"Rate limiting rules configuration using Terraform"}}]}
```

---

---
title: Transform Rules configuration using Terraform
description: Create URL rewrites, request header, and response header Transform Rules using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Transform Rules configuration using Terraform

This page provides examples of creating [Transform Rules](https://developers.cloudflare.com/rules/transform/) in a zone using Terraform. The examples cover the following scenarios:

* [Create a URL rewrite rule](#create-a-url-rewrite-rule)
* [Create a request header transform rule](#create-a-request-header-transform-rule)
* [Create a response header transform rule](#create-a-response-header-transform-rule)
* [Configure Managed Transforms](#configure-managed-transforms)

If you are using the Cloudflare API, refer to the following resources:

* [Create a URL rewrite rule via API](https://developers.cloudflare.com/rules/transform/url-rewrite/create-api/)
* [Create a request header transform rule via API](https://developers.cloudflare.com/rules/transform/request-header-modification/create-api/)
* [Create a response header transform rule via API](https://developers.cloudflare.com/rules/transform/response-header-modification/create-api/)
* [Configure Managed Transforms](https://developers.cloudflare.com/rules/transform/managed-transforms/configure/)

## 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.

---

## Create a URL rewrite rule

The following example creates a URL rewrite rule that rewrites requests for `example.com/old-folder` to `example.com/new-folder`:

* [ Terraform (v5) ](#tab-panel-8579)
* [ Terraform (v4) ](#tab-panel-8580)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Zone Transform Rules Write`
* `Account Rulesets Read`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "transform_url_rewrite" {

  zone_id     = var.cloudflare_zone_id

  name        = "Transform Rule performing a static URL rewrite"

  description = ""

  kind        = "zone"

  phase       = "http_request_transform"


  rules = [{

    ref         = "url_rewrite_old_folder"

    description = "Example URL rewrite rule"

    expression  = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-folder\")"

    action      = "rewrite"

    action_parameters = {

      uri = {

        path = {

          value = "/new-folder"

        }

      }

    }

  }]

}


```

```

resource "cloudflare_ruleset" "transform_url_rewrite" {

  zone_id     = "<ZONE_ID>"

  name        = "Transform Rule performing a static URL rewrite"

  description = ""

  kind        = "zone"

  phase       = "http_request_transform"


  rules {

    ref         = "url_rewrite_old_folder"

    description = "Example URL rewrite rule"

    expression  = "(http.host eq \"example.com\" and http.request.uri.path eq \"/old-folder\")"

    action      = "rewrite"

    action_parameters {

      uri {

        path {

          value = "/new-folder"

        }

      }

    }

  }

}


```

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

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications).

  
For more information on rewriting URLs, refer to [URL Rewrite Rules](https://developers.cloudflare.com/rules/transform/url-rewrite/).

## Create a request header transform rule

The following configuration example performs the following adjustments to HTTP request headers:

* Adds a `my-header-1` header to the request with a static value.
* Adds a `my-header-2` header to the request with a dynamic value defined by an expression.
* Deletes the `existing-header` header from the request, if it exists.

* [ Terraform (v5) ](#tab-panel-8581)
* [ Terraform (v4) ](#tab-panel-8582)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Zone Transform Rules Write`
* `Account Rulesets Read`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "transform_modify_request_headers" {

  zone_id     = var.cloudflare_zone_id

  name        = "Transform Rule performing HTTP request header modifications"

  description = ""

  kind        = "zone"

  phase       = "http_request_late_transform"


  rules = [{

    ref         = "modify_request_headers"

    description = "Example request header transform rule"

    expression  = "true"

    action      = "rewrite"

    action_parameters = {

      headers = {

        "my-header-1" = {

          operation = "set"

          value     = "Fixed value"

        }

        "my-header-2" = {

          operation  = "set"

          expression = "cf.zone.name"

        }

        "existing-header" = {

          operation = "remove"

        }

      }

    }

  }]

}


```

```

resource "cloudflare_ruleset" "transform_modify_request_headers" {

  zone_id     = "<ZONE_ID>"

  name        = "Transform Rule performing HTTP request header modifications"

  description = ""

  kind        = "zone"

  phase       = "http_request_late_transform"


  rules {

    ref         = "modify_request_headers"

    description = "Example request header transform rule"

    expression  = "true"

    action      = "rewrite"

    action_parameters {

      headers {

        name      = "my-header-1"

        operation = "set"

        value     = "Fixed value"

      }

      headers {

        name       = "my-header-2"

        operation  = "set"

        expression = "cf.zone.name"

      }

      headers {

        name      = "existing-header"

        operation = "remove"

      }

    }

  }

}


```

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

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications).

For more information on modifying request headers, refer to [Request Header Transform Rules](https://developers.cloudflare.com/rules/transform/request-header-modification/).

## Create a response header transform rule

The following configuration example performs the following adjustments to HTTP response headers:

* Adds a `my-header-1` header to the response with a static value.
* Adds a `my-header-2` header to the response with a dynamic value defined by an expression.
* Deletes the `existing-header` header from the response, if it exists.

* [ Terraform (v5) ](#tab-panel-8583)
* [ Terraform (v4) ](#tab-panel-8584)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Zone Transform Rules Write`
* `Account Rulesets Read`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "transform_modify_response_headers" {

  zone_id     = var.cloudflare_zone_id

  name        = "Transform Rule performing HTTP response header modifications"

  description = ""

  kind        = "zone"

  phase       = "http_response_headers_transform"


  rules = [{

    ref         = "modify_response_headers"

    description = "Example response header transform rule"

    expression  = "true"

    action      = "rewrite"

    action_parameters = {

      headers = {

        "my-header-1" = {

          operation = "set"

          value     = "Fixed value"

        }

        "my-header-2" = {

          operation  = "set"

          expression = "cf.zone.name"

        }

        "existing-header" = {

          operation = "remove"

        }

      }

    }

  }]

}


```

```

resource "cloudflare_ruleset" "transform_modify_response_headers" {

  zone_id     = "<ZONE_ID>"

  name        = "Transform Rule performing HTTP response header modifications"

  description = ""

  kind        = "zone"

  phase       = "http_response_headers_transform"


  rules {

    ref         = "modify_response_headers"

    description = "Example response header transform rule"

    expression  = "true"

    action      = "rewrite"

    action_parameters {

      headers {

        name      = "my-header-1"

        operation = "set"

        value     = "Fixed value"

      }

      headers {

        name       = "my-header-2"

        operation  = "set"

        expression = "cf.zone.name"

      }

      headers {

        name      = "existing-header"

        operation = "remove"

      }

    }

  }

}


```

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

Use the `ref` field to get stable rule IDs across updates when using Terraform. Adding this field prevents Terraform from recreating the rule on changes. For more information, refer to [Troubleshooting](https://developers.cloudflare.com/terraform/troubleshooting/rule-id-changes/#how-to-keep-the-same-rule-id-between-modifications).

For more information on modifying response headers, refer to [Response Header Transform Rules](https://developers.cloudflare.com/rules/transform/response-header-modification/).

## Configure Managed Transforms

* [ Terraform (v5) ](#tab-panel-8577)
* [ Terraform (v4) ](#tab-panel-8578)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Managed headers Write`
* `Account Rulesets Read`

Configure the [cloudflare\_managed\_transforms ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/managed%5Ftransforms) resource:

```

resource "cloudflare_managed_transforms" "tf_example" {

  zone_id = var.cloudflare_zone_id


  managed_request_headers = [{

    id      = "add_visitor_location_headers"

    enabled = true

  }]


  managed_response_headers = [{

    id      = "remove_x-powered-by_header"

    enabled = true

  }]

}


```

```

resource "cloudflare_managed_headers" "tf_example" {

  zone_id = "<ZONE_ID>"


  managed_request_headers {

    id      = "add_visitor_location_headers"

    enabled = true

  }


  managed_response_headers {

    id      = "remove_x-powered-by_header"

    enabled = true

  }

}


```

Make sure you include the Managed Transforms you are updating in the correct object (`managed_request_headers` or `managed_response_headers`).

For more information on Managed Transforms, refer to [Managed Transforms](https://developers.cloudflare.com/rules/transform/managed-transforms/).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/additional-configurations/","name":"Additional configurations"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/additional-configurations/transform-rules/","name":"Transform Rules configuration using Terraform"}}]}
```

---

---
title: WAF custom rules configuration using Terraform
description: Create and deploy Cloudflare WAF custom rules at the zone or account level using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# WAF custom rules configuration using Terraform

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:

* [Add a custom rule to a zone](#add-a-custom-rule-to-a-zone)
* [Create and deploy a custom ruleset](#create-and-deploy-a-custom-ruleset)

The WAF documentation includes additional Terraform examples — refer to [More resources](#more-resources).

If you are using the Cloudflare API, refer to the following resources in the WAF documentation:

* [Create a custom rule via API](https://developers.cloudflare.com/waf/custom-rules/create-api/)
* [Create a custom ruleset using the API](https://developers.cloudflare.com/waf/account/custom-rulesets/create-api/)

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

## 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.

---

## 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:

* [ Terraform (v5) ](#tab-panel-8587)
* [ Terraform (v4) ](#tab-panel-8588)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Zone WAF Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "zone_custom_firewall" {

  zone_id     = var.cloudflare_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"

  }]

}


```

```

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.

  
## Create and deploy a custom ruleset

Note

All customers can create custom rulesets at the [zone level](https://developers.cloudflare.com/waf/custom-rules/custom-rulesets/).  
Custom rulesets at the [account level](https://developers.cloudflare.com/waf/account/custom-rulesets/) require an Enterprise plan with a paid add-on.

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.

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

* [ Terraform (v5) ](#tab-panel-8589)
* [ Terraform (v4) ](#tab-panel-8590)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Account WAF Write`
* `Account Rulesets Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "account_firewall_custom_ruleset" {

  account_id  = var.cloudflare_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"

  }]

}


```

```

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`:

* [ Terraform (v5) ](#tab-panel-8585)
* [ Terraform (v4) ](#tab-panel-8586)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Account WAF Write`
* `Account Rulesets Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "account_firewall_custom_entrypoint" {

  account_id  = var.cloudflare_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\") and (cf.zone.plan eq \"ENT\")"

    action      = "execute"

    action_parameters = {

      id = cloudflare_ruleset.account_firewall_custom_ruleset.id

    }

  }]

}


```

```

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\") and (cf.zone.plan eq \"ENT\")"

    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.

## More resources

* [Malicious uploads detection: Add a custom rule to block malicious uploads](https://developers.cloudflare.com/waf/detections/malicious-uploads/terraform-examples/#add-a-custom-rule-to-block-malicious-uploads)
* [Leaked credentials detection: Add a custom rule to challenge requests with leaked credentials](https://developers.cloudflare.com/waf/detections/leaked-credentials/terraform-examples/#add-a-custom-rule-to-challenge-requests-with-leaked-credentials)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/additional-configurations/","name":"Additional configurations"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/additional-configurations/waf-custom-rules/","name":"WAF custom rules configuration using Terraform"}}]}
```

---

---
title: WAF Managed Rules configuration using Terraform
description: Deploy and configure Cloudflare WAF Managed Rules at the zone or account level using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# WAF Managed Rules configuration using Terraform

This page provides examples of deploying and configuring [WAF Managed Rules](https://developers.cloudflare.com/waf/managed-rules/) in your zone or account using Terraform. It covers the following configurations:

* [Deploy managed rulesets at the zone level](#deploy-managed-rulesets-at-the-zone-level)
* [Deploy managed rulesets at the account level](#deploy-managed-rulesets-at-the-account-level)
* [Configure exceptions](#configure-exceptions)
* [Configure payload logging](#configure-payload-logging)
* [Configure overrides](#configure-overrides)
* [Configure the OWASP paranoia level, score threshold, and action](#configure-the-owasp-paranoia-level-score-threshold-and-action)

If you are using the Cloudflare API, refer to the following resources:

* [Deploy a WAF managed ruleset via API](https://developers.cloudflare.com/waf/managed-rules/deploy-api/)
* [Deploy a WAF managed ruleset via API (account)](https://developers.cloudflare.com/waf/account/managed-rulesets/deploy-api/)

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

## Before you start

### Obtain the necessary account, zone, and managed ruleset IDs

The Terraform configurations provided in this page need the zone ID (or account ID) of the zone/account where you will deploy the managed 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.

The deployment of managed rulesets via Terraform requires that you use the ruleset IDs. To find the IDs of managed rulesets, use the [List account rulesets](https://developers.cloudflare.com/api/resources/rulesets/methods/list/) operation. The response will include the description and IDs of existing managed rulesets.

The IDs of WAF managed rulesets are also available in the [WAF Managed Rules](https://developers.cloudflare.com/waf/managed-rules/#available-managed-rulesets) page.

### 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.

---

## Deploy managed rulesets at the zone level

The following example deploys two managed rulesets to the zone with ID `<ZONE_ID>` using Terraform, using a `cloudflare_ruleset` resource with two rules that execute the managed rulesets.

* [ Terraform (v5) ](#tab-panel-8599)
* [ Terraform (v4) ](#tab-panel-8600)

Required API token permissions

At least one of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) is required:

* `Zone WAF Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

# Configure a ruleset at the zone level for the "http_request_firewall_managed" phase

resource "cloudflare_ruleset" "zone_level_managed_waf" {

  zone_id     = var.cloudflare_zone_id

  name        = "Managed WAF entry point ruleset"

  description = "Zone-level WAF Managed Rules config"

  kind        = "zone"

  phase       = "http_request_firewall_managed"


  rules = [

    {

      # Execute Cloudflare Managed Ruleset

      ref         = "execute_cloudflare_managed_ruleset"

      description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

      expression  = "true"

      action      = "execute"

      action_parameters = {

        id = "efb7b8c949ac4650a09736fc376e9aee"

      }

    },

    {

      # Execute Cloudflare OWASP Core Ruleset

      ref         = "execute_cloudflare_owasp_core_ruleset"

      description = "Execute Cloudflare OWASP Core Ruleset on my zone-level phase entry point ruleset"

      expression  = "true"

      action      = "execute"

      action_parameters = {

        id = "4814384a9e5d4991b9815dcfc25d2f1f"

      }

    },

  ]

}


```

```

# Configure a ruleset at the zone level for the "http_request_firewall_managed" phase

resource "cloudflare_ruleset" "zone_level_managed_waf" {

  zone_id     = "<ZONE_ID>"

  name        = "Managed WAF entry point ruleset"

  description = "Zone-level WAF Managed Rules config"

  kind        = "zone"

  phase       = "http_request_firewall_managed"


  # Execute Cloudflare Managed Ruleset

  rules {

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "efb7b8c949ac4650a09736fc376e9aee"

    }

  }


  # Execute Cloudflare OWASP Core Ruleset

  rules {

    ref         = "execute_cloudflare_owasp_core_ruleset"

    description = "Execute Cloudflare OWASP Core Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "4814384a9e5d4991b9815dcfc25d2f1f"

    }

  }

}


```

## Deploy managed rulesets at the account level

Notes

* [Account-level WAF configuration](https://developers.cloudflare.com/waf/account/) requires an Enterprise plan with a paid add-on.
* Managed rulesets deployed at the account level will only apply to incoming traffic of zones on an Enterprise plan. The expression of your `execute` rule must end with `and cf.zone.plan eq "ENT"`.

The following example deploys two managed rulesets to the account with ID `<ACCOUNT_ID>` using Terraform, using a `cloudflare_ruleset` resource with two rules that execute the managed rulesets for two hostnames belonging to Enterprise zones.

* [ Terraform (v5) ](#tab-panel-8601)
* [ Terraform (v4) ](#tab-panel-8602)

Required API token permissions

All of the following [token permissions](https://developers.cloudflare.com/fundamentals/api/reference/permissions/) are required:

* `Account WAF Write`
* `Account Rulesets Write`

Configure the [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resource:

```

resource "cloudflare_ruleset" "account_level_managed_waf" {

  account_id  = var.cloudflare_account_id

  name        = "Managed WAF entry point ruleset"

  description = "Account-level WAF Managed Rules config"

  kind        = "root"

  phase       = "http_request_firewall_managed"


  rules = [

    {

      # Execute Cloudflare Managed Ruleset

      ref         = "execute_cloudflare_managed_ruleset_api_store"

      description = "Execute Cloudflare Managed Ruleset on my account-level phase entry point ruleset"

      expression  = "http.host in {\"api.example.com\" \"store.example.com\"} and cf.zone.plan eq \"ENT\""

      action      = "execute"

      action_parameters = {

        id = "efb7b8c949ac4650a09736fc376e9aee"

      }

    },

    {

      # Execute Cloudflare OWASP Core Ruleset

      ref         = "execute_owasp_core_ruleset_api_store"

      description = "Execute Cloudflare OWASP Core Ruleset on my account-level phase entry point ruleset"

      expression  = "http.host in {\"api.example.com\" \"store.example.com\"} and cf.zone.plan eq \"ENT\""

      action      = "execute"

      action_parameters = {

        id = "4814384a9e5d4991b9815dcfc25d2f1f"

      }

    },

  ]

}


```

```

resource "cloudflare_ruleset" "account_level_managed_waf" {

  account_id  = "<ACCOUNT_ID>"

  name        = "Managed WAF entry point ruleset"

  description = "Account-level WAF Managed Rules config"

  kind        = "root"

  phase       = "http_request_firewall_managed"


  # Execute Cloudflare Managed Ruleset

  rules {

    ref         = "execute_cloudflare_managed_ruleset_api_store"

    description = "Execute Cloudflare Managed Ruleset on my account-level phase entry point ruleset"

    expression  = "http.host in {\"api.example.com\" \"store.example.com\"} and cf.zone.plan eq \"ENT\""

    action      = "execute"

    action_parameters {

      id = "efb7b8c949ac4650a09736fc376e9aee"

    }

  }


  # Execute Cloudflare OWASP Core Ruleset

  rules {

    ref         = "execute_owasp_core_ruleset_api_store"

    description = "Execute Cloudflare OWASP Core Ruleset on my account-level phase entry point ruleset"

    expression  = "http.host in {\"api.example.com\" \"store.example.com\"} and cf.zone.plan eq \"ENT\""

    action      = "execute"

    action_parameters {

      id = "4814384a9e5d4991b9815dcfc25d2f1f"

    }

  }

}


```

## Configure exceptions

The following example adds two [exceptions](https://developers.cloudflare.com/waf/managed-rules/waf-exceptions/) for the Cloudflare Managed Ruleset:

* The first rule will skip the execution of the entire Cloudflare Managed Ruleset (with ID ...376e9aee ) for specific URLs, according to the rule expression.
* The second rule will skip the execution of two rules belonging to the Cloudflare Managed Ruleset for specific URLs, according to the rule expression.

Add the two exceptions to the `cloudflare_ruleset` resource before the rule that deploys the Cloudflare Managed Ruleset:

* [ Terraform (v5) ](#tab-panel-8591)
* [ Terraform (v4) ](#tab-panel-8592)

```

resource "cloudflare_ruleset" "zone_level_managed_waf" {

  # (...)


  rules = [

    {

      # Skip execution of the entire Cloudflare Managed Ruleset for specific URLs

      ref         = "skip_cloudflare_managed_ruleset_example_com"

      description = "Skip Cloudflare Managed Ruleset"

      expression  = "(http.request.uri.path eq \"/status\" and http.request.uri.query contains \"skip=rulesets\")"

      action      = "skip"

      action_parameters = {

        rulesets = ["efb7b8c949ac4650a09736fc376e9aee"]

      }

    },

    {

      # Skip execution of two rules in the Cloudflare Managed Ruleset for specific URLs

      ref         = "skip_wordpress_sqli_rules_example_com"

      description = "Skip WordPress and SQLi rules"

      expression  = "(http.request.uri.path eq \"/status\" and http.request.uri.query contains \"skip=rules\")"

      action      = "skip"

      action_parameters = {

        rules = {

          # Format: "<RULESET_ID>" = ["<RULE_ID_1>", "<RULE_ID_2>"]

          "efb7b8c949ac4650a09736fc376e9aee" = ["5de7edfa648c4d6891dc3e7f84534ffa", "e3a567afc347477d9702d9047e97d760"]

        }

      }

    },

    {

      # Execute Cloudflare Managed Ruleset

      ref         = "execute_cloudflare_managed_ruleset"

      description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

      expression  = "true"

      action      = "execute"

      action_parameters = {

        id = "efb7b8c949ac4650a09736fc376e9aee"

      }

    },

    # (...)

  ]

}


```

```

resource "cloudflare_ruleset" "zone_level_managed_waf" {

  # (...)


  # Skip execution of the entire Cloudflare Managed Ruleset for specific URLs

  rules {

    ref         = "skip_cloudflare_managed_ruleset_example_com"

    description = "Skip Cloudflare Managed Ruleset"

    expression  = "(http.request.uri.path eq \"/status\" and http.request.uri.query contains \"skip=rulesets\")"

    action      = "skip"

    action_parameters {

      rulesets = ["efb7b8c949ac4650a09736fc376e9aee"]

    }

  }


  # Skip execution of two rules in the Cloudflare Managed Ruleset for specific URLs

  rules {

    ref         = "skip_wordpress_sqli_rules_example_com"

    description = "Skip WordPress and SQLi rules"

    expression  = "(http.request.uri.path eq \"/status\" and http.request.uri.query contains \"skip=rules\")"

    action      = "skip"

    action_parameters {

      rules = {

        # Format: "<RULESET_ID>" = "<RULE_ID_1>,<RULE_ID_2>,..."

        "efb7b8c949ac4650a09736fc376e9aee" = "5de7edfa648c4d6891dc3e7f84534ffa,e3a567afc347477d9702d9047e97d760"

      }

    }

  }


  # Execute Cloudflare Managed Ruleset

  rules {

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "efb7b8c949ac4650a09736fc376e9aee"

    }

  }


  # (...)

}


```

Important

Ensure that you place the exceptions **before** the rule that executes the managed ruleset (or some of its rules) that you wish to skip, as in the previous example.

## Configure overrides

The following example adds three [overrides](https://developers.cloudflare.com/ruleset-engine/managed-rulesets/override-managed-ruleset/) for the Cloudflare Managed Ruleset:

* A rule override for rule with ID `5de7edfa648c4d6891dc3e7f84534ffa` setting the action to `log`.
* A rule override for rule with ID `75a0060762034a6cb663fd51a02344cb` disabling the rule.
* A tag override for the `wordpress` tag, setting the action of all the rules with this tag to `js_challenge`.

Important

Ruleset overrides and tag overrides apply to both existing and _future_ rules in the managed ruleset. If you want to override existing rules only, you must use rule overrides.

The following configuration includes the three overrides in the rule that executes the Cloudflare Managed Ruleset:

* [ Terraform (v5) ](#tab-panel-8593)
* [ Terraform (v4) ](#tab-panel-8594)

```

  # (...)


  # Execute Cloudflare Managed Ruleset

  rules = [{

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters = {

      id = "efb7b8c949ac4650a09736fc376e9aee"

      overrides = {

        rules = [

          {

            id      = "5de7edfa648c4d6891dc3e7f84534ffa"

            action  = "log"

            enabled = true

          },

          {

            id      = "75a0060762034a6cb663fd51a02344cb"

            enabled = false

          },

        ]

        categories = [{

          category = "wordpress"

          action   = "js_challenge"

          enabled  = true

        }]

      }

    }

  }]


  # (...)


```

```

  # (...)


  # Execute Cloudflare Managed Ruleset

  rules {

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "efb7b8c949ac4650a09736fc376e9aee"

      overrides {

        rules {

          id      = "5de7edfa648c4d6891dc3e7f84534ffa"

          action  = "log"

          enabled = true

        }

        rules {

          id      = "75a0060762034a6cb663fd51a02344cb"

          enabled = false

        }

        categories {

          category = "wordpress"

          action   = "js_challenge"

          enabled  = true

        }

      }

    }

  }


  # (...)


```

## Configure payload logging

This example enables [payload logging](https://developers.cloudflare.com/waf/managed-rules/payload-logging/) for matched rules of the Cloudflare Managed Ruleset, setting the public key used to encrypt the logged payload.

Building upon the rule that deploys the Cloudflare Managed Ruleset, the following rule configuration adds the `matched_data` object with the public key used to encrypt the payload:

* [ Terraform (v5) ](#tab-panel-8595)
* [ Terraform (v4) ](#tab-panel-8596)

```

  # (...)


  # Execute Cloudflare Managed Ruleset

  rules = [{

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters = {

      id = "efb7b8c949ac4650a09736fc376e9aee"

      matched_data = {

        public_key = "Ycig/Zr/pZmklmFUN99nr+taURlYItL91g+NcHGYpB8="

      }

    }

  }]


  # (...)


```

```

  # (...)


  # Execute Cloudflare Managed Ruleset

  rules {

    ref         = "execute_cloudflare_managed_ruleset"

    description = "Execute Cloudflare Managed Ruleset on my zone-level phase entry point ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "efb7b8c949ac4650a09736fc376e9aee"

      matched_data {

         public_key = "Ycig/Zr/pZmklmFUN99nr+taURlYItL91g+NcHGYpB8="

      }

    }

  }


  # (...)


```

## Configure the OWASP paranoia level, score threshold, and action

The OWASP managed ruleset supports the following configurations:

* Enable all the rules up to a specific paranoia level by creating tag overrides that disable all the rules associated with higher paranoia levels.
* Set the action to perform when the calculated threat score is greater than the score threshold by creating a rule override for the last rule in the Cloudflare OWASP Core Ruleset (rule with ID ...843b323c  ), and including the `action` property.
* Set the score threshold by creating a rule override for the last rule in the Cloudflare OWASP Core Ruleset (rule with ID ...843b323c  ), and including the `score_threshold` property.

For more information on the available configuration values, refer to the [Cloudflare OWASP Core Ruleset](https://developers.cloudflare.com/waf/managed-rules/reference/owasp-core-ruleset/) page in the WAF documentation.

The following example rule of a `cloudflare_ruleset` Terraform resource performs the following configuration:

* Deploys the OWASP managed ruleset.
* Sets the OWASP paranoia level to _PL2_.
* Sets the score threshold to `60` (_Low_).
* Sets the ruleset action to `log`.

* [ Terraform (v5) ](#tab-panel-8597)
* [ Terraform (v4) ](#tab-panel-8598)

```

  # (...)


  # Execute Cloudflare OWASP Core Ruleset

  rules = [{

    ref         = "execute_owasp_core_ruleset"

    description = "Execute Cloudflare OWASP Core Ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters = {

      id = "4814384a9e5d4991b9815dcfc25d2f1f"

      overrides = {

        # By default, all PL1 to PL4 rules are enabled.

        # Set the paranoia level to PL2 by disabling rules with

        # tags "paranoia-level-3" and "paranoia-level-4".

        categories = [

          {

            category = "paranoia-level-3"

            enabled  = false

          },

          {

            category = "paranoia-level-4"

            enabled  = false

          },

        ]

        rules = [{

          id              = "6179ae15870a4bb7b2d480d4843b323c"

          action          = "log"

          score_threshold = 60

        }]

      }

    }

  }]


  # (...)


```

```

  # (...)


  # Execute Cloudflare OWASP Core Ruleset

  rules {

    ref         = "execute_owasp_core_ruleset"

    description = "Execute Cloudflare OWASP Core Ruleset"

    expression  = "true"

    action      = "execute"

    action_parameters {

      id = "4814384a9e5d4991b9815dcfc25d2f1f"

      overrides {

        # By default, all PL1 to PL4 rules are enabled.

        # Set the paranoia level to PL2 by disabling rules with

        # tags "paranoia-level-3" and "paranoia-level-4".

        categories {

          category = "paranoia-level-3"

          enabled  = false

        }

        categories {

          category = "paranoia-level-4"

          enabled  = false

        }

        rules {

          id              = "6179ae15870a4bb7b2d480d4843b323c"

          action          = "log"

          score_threshold = 60

        }

      }

    }

  }


  # (...)


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/additional-configurations/","name":"Additional configurations"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/additional-configurations/waf-managed-rulesets/","name":"WAF Managed Rules configuration using Terraform"}}]}
```

---

---
title: Best practices
description: Recommended directory structure, state management, and workflow practices for Cloudflare Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Best practices

Though all Terraform deployments are unique, follow these best practices to set yourself up for success.

## Manage Terraform resources in Terraform

Terraform works best when it manages all changes to and the lifecycle of a resource.

After any operation on the configuration, Terraform attempts to reconcile the differences by syncing the remote into the local state. If there are differences in the local and remote - caused by managing resources outside of Terraform - you may need to delete and recreate the resource in the state file (usually via importing) as not all resources support in-place updates.

## Directory structure

Cloudflare recommends using a directory structure that relies on a combination of accounts, zones, and products for isolating changes. This setup lets you have fine-grained owners and scoped Terraform operations to a specific product in a zone. It also more closely aligns owners with Cloudflare's [default roles](https://developers.cloudflare.com/fundamentals/manage-members/roles/), as well as additional tools like AWS or GCP storage by permissioning separate state files.

For products that encompass many responsibilities such as Rulesets, you can extend this even further by partitioning at the phase level (WAF, redirects, origin rules).

```

example-tf/

├── demo_account_a                  # per account segregation of resources

│   ├── users                       # top level directory for account members as they are "zoneless"

│   │   ├── provider.tf             # `provider.tf` is for configuring the providers

│   │   ├── users.tf                # `<subject>.tf` (users.tf) is for managing the individual resources

│   │   └── vars.tf                 # manage all variables for this component

│   ├── zone_a                      # group all zone based features together

│   │   ├── dns                     # individual (or grouped, your choice) of products or features to manage together

│   │   │   ├── dns.tf              # `<subject>.tf` (dns.tf) is for managing the individual resources

│   │   │   ├── provider.tf         # `provider.tf` is for configuring the providers

│   │   │   └── vars.tf             # manage all variables for this component

│   │   └── page_rules              # ... same as above but for Page Rules

│   │       ├── page_rules.tf

│   │       ├── provider.tf

│   │       └── vars.tf

│   ├── zone_b

│   │   ├── dns

│   │   │   ├── dns.tf

│   │   │   ├── provider.tf

│   │   │   └── vars.tf

│   │   └── page_rules

│   │       ├── page_rules.tf

│   │       ├── provider.tf

│   │       └── vars.tf

│   └── zone_c

│       ├── dns

│       │   ├── dns.tf

│       │   ├── provider.tf

│       │   └── vars.tf

│       └── page_rules

│           ├── page_rules.tf

│           ├── provider.tf

│           └── vars.tf

└── demo_account_b

    ├── users

    │   ├── provider.tf

    │   ├── users.tf

    │   └── vars.tf

    ├── zone_a

    │   ├── dns

    │   │   ├── dns.tf

    │   │   ├── provider.tf

    │   │   └── vars.tf

    │   └── page_rules

    │       ├── page_rules.tf

    │       ├── provider.tf

    │       └── vars.tf

    ├── zone_b

    │   ├── dns

    │   │   ├── dns.tf

    │   │   ├── provider.tf

    │   │   └── vars.tf

    │   └── page_rules

    │       ├── page_rules.tf

    │       ├── provider.tf

    │       └── vars.tf

    └── zone_c

        ├── dns

        │   ├── dns.tf

        │   ├── provider.tf

        │   └── vars.tf

        └── page_rules

            ├── page_rules.tf

            ├── provider.tf

            └── vars.tf


```

## Avoid modules (or use them sparingly)

Terraform modules are ways of encapsulating multiple resources with logic in an abstracted interface. Consider the example where a module sets up default load balancer with a pool, some DNS entries, and perhaps a page rule. The end user may use it like this:

```

module "example" "an_example_site" {

  domain = "example.com"

  origin_ip = "192.168.0.1"

}


```

In terms of Terraform resources, however, the above example would be translated to:

```

resource "cloudflare_record" "example_1" {

  zone_id = var.cloudflare_zone_id

  name    = "terraform"

  value   = "198.51.100.11"

  type    = "A"

  ttl     = 3600

}


resource "cloudflare_record" "example_2" {

  zone_id = var.cloudflare_zone_id

  name    = "terraform"

  value   = "198.51.100.12"

  type    = "A"

  ttl     = 3600

}


resource "cloudflare_record" "example_3" {

  zone_id = var.cloudflare_zone_id

  name    = "terraform"

  value   = "198.51.100.13"

  type    = "A"

  ttl     = 3600

}


resource "cloudflare_load_balancer" "bar" {

  zone_id          = var.cloudflare_zone_id

  name             = "example-load-balancer.example.com"

  fallback_pool_id = cloudflare_load_balancer_pool.foo.id

  default_pool_ids = [cloudflare_load_balancer_pool.foo.id]

  description      = "example load balancer using geo-balancing"

  proxied          = true

  steering_policy  = "geo"


  pop_pools {

    pop      = "LAX"

    pool_ids = [cloudflare_load_balancer_pool.foo.id]

  }


  country_pools {

    country  = "US"

    pool_ids = [cloudflare_load_balancer_pool.foo.id]

  }


  region_pools {

    region   = "WNAM"

    pool_ids = [cloudflare_load_balancer_pool.foo.id]

  }


  rules {

    name      = "example rule"

    condition = "http.request.uri.path contains \"testing\""

    fixed_response {

      message_body = "hello"

      status_code  = 200

      content_type = "html"

      location     = "www.example.com"

    }

  }

}


resource "cloudflare_load_balancer_pool" "example_lb_pool" {

  name = "example-lb-pool"

  origins {

    name    = "example-1"

    address = "198.51.100.1"

    enabled = true

  }

}


resource "cloudflare_page_rule" "example_page_rule" {

  zone_id = var.cloudflare_zone_id

  target = "sub.${var.cloudflare_zone}/page"

  priority = 1


  actions {

    ssl = "flexible"

    email_obfuscation = "on"

  }

}


```

While convenient, this setup can cause unanticipated issues. If this module is shared and then changes internally, the module may have resources out of sync or recreated.

Using modules also increases the difficulty of debugging or reproducing issues as you must then factor in potential logic bugs outside of Terraform core and the Cloudflare provider.

Warning

This advice also applies to [Terraform dynamic blocks ↗](https://www.terraform.io/language/expressions/dynamic-blocks) that allow you to do logic in your HCL. Since these dynamic blocks are always evaluated, you can get yourself into situations where you have logic bugs in your configuration (and making the end result unreproducible).

## Migrate resources into Terraform

Cloudflare recommends using [cf-terraforming](https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/) to migrate existing resources into Cloudflare.

## Manage some resources outside of Terraform

It is perfectly fine to manage some resources inside Terraform and others using a different tool, but make sure you are not [doing both for the same resource](#manage-terraform-resources-in-terraform).

## Use separate environments

To safely manage separate environments (staging, QA, UAT, production), use separate Cloudflare accounts with separate domains (such as `example.com` and `example-staging.com`).

This is because some products defined at the account level are shared (such as Load Balancer monitors and pools) and you cannot make an isolated change to them if they are in the same account. Using separate accounts is also beneficial if you intend to test things like DNSSEC, which may affect your entire domain if configured incorrectly.

To minimize drift, use Terraform and a CI/CD pipeline that runs across both domains to keep them in sync as needed.

## Store credentials safely

We do not recommend storing Cloudflare credentials as plaintext.

Locally, you can use a third-party tool like [cf-vault ↗](https://github.com/jacobbednarz/cf-vault/) to store your Cloudflare credentials.

For CI pipelines, use an internal or secret storage tool (such as [Vault ↗](https://www.hashicorp.com/products/vault/secrets-management)).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/advanced-topics/","name":"Advanced topics"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/advanced-topics/best-practices/","name":"Best practices"}}]}
```

---

---
title: Import Cloudflare resources
description: The Cloudflare Terraform tool is available in the Terraform ME repository. To use it, you must first install the Terraform app on your Mac or Linux system. You must then import Cloudflare resources individually by providing their IDs and names.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Import Cloudflare resources

An important point to understand about Terraform is that it can only manage configuration it created or was explicitly told about after the fact. The reason for this limitation is that Terraform expects to be authoritative for the resources it manages. It relies on two types of files to understand what resources it controls and what state they are in. Terraform determines when and how to make changes from the following:

* A [configuration file ↗](https://developer.hashicorp.com/terraform/language) (ending in `.tf`) that defines the configuration of resources for Terraform to manage. This is what you worked with in the tutorial steps.
* A local [state file ↗](https://developer.hashicorp.com/terraform/language/state) that maps the resource names defined in your configuration file — for example, `cloudflare_load_balancer.www-lb` — to the resources that exist in Cloudflare.

When Terraform makes calls to Cloudflare's API to create new resources as explained in the [tutorial](https://developers.cloudflare.com/terraform/tutorial/), it persists those IDs to a state file. By default, Terraform uses the `terraform.tfstate` file in your directory, but this can also be a [remote location ↗](https://developer.hashicorp.com/terraform/language/state/remote). These IDs are later looked up and refreshed when you call `terraform plan` and `terraform apply`.

If you configured Cloudflare through other means, for example, by logging in to the Cloudflare dashboard or making `curl` calls to `api.cloudflare.com`, Terraform does not yet have these resource IDs in the state file. To manage this preexisting configuration, you will need to first reproduce the configuration in your config file and then import resources individually by providing their IDs and resource names.

## `cf-terraforming`

[cf-terraforming ↗](https://github.com/cloudflare/cf-terraforming) helps existing Cloudflare customers get started with Terraform. Currently, `cf-terraforming` helps to generate the Terraform config state by fetching all the resources of a specified type from the account and/or zone of your choosing.

### Installation

Before you start, you must install `cf-terraforming`.

If you use Homebrew on macOS, open a terminal and run the following commands:

Terminal window

```

brew tap cloudflare/cloudflare

brew install cloudflare/cloudflare/cf-terraforming


```

If you are using a different OS, [download the latest release ↗](https://github.com/cloudflare/cf-terraforming/releases) from the `cf-terraforming` GitHub repository.

To view the help file, run `cf-terraforming` or `cf-terraforming -h`.

### Basic usage

To use `cf-terraforming`, specify the items below:

1. The command to execute (for example, `generate` or `import`).
2. Your Cloudflare user email - `--email` or `-e`.
3. Your Cloudflare API token - `--token` or `-t`.
4. The account and/or zone to pull resources from - `--account`/`--zone` or `-a`/`-z`.
5. The Cloudflare resources to generate config.

The list of supported resources is available in the [Terraform README ↗](https://github.com/cloudflare/cf-terraforming#supported-resources).

## Import existing Cloudflare resources

To start managing existing Cloudflare resources in Terraform, for example, DNS records, you need:

* The Terraform configuration of that resource (defined in a `.tf` file)
* An accompanying Terraform state file of that resources state (defined in a `.tfstate` file)

### Generate Terraform configuration with `cf-terraforming`

If you do not have a Terraform configuration file defined, you need the `provider` block defined as follows:

Note

Terraform code snippets below refer to the v4 SDK only.

```

provider 'cloudflare' {

 # Cloudflare email saved in $CLOUDFLARE_EMAIL

 # Cloudflare API token saved in $CLOUDFLARE_API_TOKEN

}


```

Remember to keep your credentials saved in environment variables or terraform autovars that are not checked into your source files.

Start by making a call to `cf-terraforming generate` to generate the Terraform configuration for the DNS records in the zone you want to manage with Terraform.

Terminal window

```

cf-terraforming generate --email $CLOUDFLARE_EMAIL --token $CLOUDFLARE_API_TOKEN -z 1109d899a5ff5fd74bc01e581693685b --resource-type cloudflare_record > importing-example.tf


```

If you had not redirected the output to the `importing-example.tf` file, the result displayed in the standard output (your terminal window) would look like the following:

Note

Terraform code snippets below refer to the v4 SDK only.

```

resource "cloudflare_record" "terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31" {

    name    = "@"

    type    = "A"

    ttl     = 1

    proxied = true

    value   = "192.0.2.1"

    zone_id = "1109d899a5ff5fd74bc01e581693685b"

}


resource "cloudflare_record" "terraform_managed_resource_5e10399a590a45279f09aa8fb1163354" {

    name    = "www"

    type    = "CNAME"

    ttl     = 1

    proxied = true

    value   = "mitigateddos.net"

    zone_id = "1109d899a5ff5fd74bc01e581693685b"

}


resource "cloudflare_record" "terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248" {

    name    = "a123"

    type    = "NS"

    ttl     = 300

    proxied = false

    value   = "rafe.ns.cloudflare.com"

    zone_id = "1109d899a5ff5fd74bc01e581693685b"

}


resource "cloudflare_record" "terraform_managed_resource_5799bb01054843eea726758f935d2aa2" {

    name    = "a123"

    type    = "NS"

    ttl     = 300

    proxied = false

    value   = "terin.ns.cloudflare.com"

    zone_id = "1109d899a5ff5fd74bc01e581693685b"

}


```

Calling `terraform plan` at this point will try to create these resources as if they did not exist, since they are not present in the local state file:

Terminal window

```

terraform plan


```

```

Terraform used the selected providers to generate the following execution plan.

Resource actions are indicated with the following symbols:

  + create


Terraform will perform the following actions:


  # cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31 will be created

  + resource "cloudflare_record" "terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31" {

      + id          = (known after apply)>

      + created_on  = (known after apply)

      + domain      = "mitigateddos.net"

      + hostname    = (known after apply)

      + metadata    = (known after apply)

      + modified_on = (known after apply)

      + name        = "mitigateddos.net"

      + proxiable   = (known after apply)

      + proxied     = true

      + ttl         = 1

      + type        = "A"

      + value       = "192.0.2.1"

      + zone_id     = "1109d899a5ff5fd74bc01e581693685b"

    }


  # cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354 will be created

  + resource "cloudflare_record" "terraform_managed_resource_5e10399a590a45279f09aa8fb1163354" {

      + id          = (known after apply)

      + created_on  = (known after apply)

      + domain      = "mitigateddos.net"

      + hostname    = (known after apply)

      + metadata    = (known after apply)

      + modified_on = (known after apply)

      + name        = "www.mitigateddos.net"

      + proxiable   = (known after apply)

      + proxied     = true

      + ttl         = 1

      + type        = "CNAME"

      + value       = "mitigateddos.net"

      + zone_id     = "1109d899a5ff5fd74bc01e581693685b"

    }


  # cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248 will be created

  + resource "cloudflare_record" "terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248" {

      + id          = (known after apply)

      + created_on  = (known after apply)

      + domain      = "mitigateddos.net"

      + hostname    = (known after apply)

      + metadata    = (known after apply)

      + modified_on = (known after apply)

      + name        = "a123.mitigateddos.net"

      + proxiable   = (known after apply)

      + proxied     = false

      + ttl         = 300

      + type        = "NS"

      + value       = "rafe.ns.cloudflare.com"

      + zone_id     = "1109d899a5ff5fd74bc01e581693685b"

    }


  # cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2 will be created

  + resource "cloudflare_record" "terraform_managed_resource_5799bb01054843eea726758f935d2aa2" {

      + id          = (known after apply)

      + created_on  = (known after apply)

      + domain      = "mitigateddos.net"

      + hostname    = (known after apply)

      + metadata    = (known after apply)

      + modified_on = (known after apply)

      + name        = "a123.mitigateddos.net"

      + proxiable   = (known after apply)

      + proxied     = false

      + ttl         = 300

      + type        = "NS"

      + value       = "terin.ns.cloudflare.com"

      + zone_id     = "1109d899a5ff5fd74bc01e581693685b"

    }


Plan: 4 to add, 0 to change, 0 to destroy.


------------------------------------------------------------------------


Note: You didn't use the -out option to save this plan, so Terraform can't

guarantee to take exactly these actions if you run "terraform apply" now.


```

To fix this, you must import the real state of those resources from Cloudflare into the Terraform state file (`.tfstate`).

### Import resources into Terraform state

`cf-terraforming` allows you to import local state (`.tfstate` file) for the same resources you imported during configuration.

When you run `cf-terraforming import ...`, you will obtain a list of `terraform import ...` commands that you must run manually afterward to import those resources into Terraform state. This is currently a manual process, but it may be automated in the future.

1. Run the following command:  
Terminal window  
```  
cf-terraforming import --resource-type "cloudflare_record" --email $CLOUDFLARE_EMAIL --key $CLOUDFLARE_API_KEY --zone $CLOUDFLARE_ZONE_ID  
```
2. Copy each `terraform import ...` command included in the output and run it. Terraform will import each resource individually into Terraform state.

For example, if the output of the first command (`cf-terraforming import ...`) contained the following `terraform` commands:

```

terraform import cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31 1109d899a5ff5fd74bc01e581693685b/3c0b456bc2aa443089c5f40f45f51b31

terraform import cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354 1109d899a5ff5fd74bc01e581693685b/d09d916d059aa9fc8cb54bdd49deea5f

terraform import cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248 1109d899a5ff5fd74bc01e581693685b/8d6ec0d02c5b22212ff673782c816ef8

terraform import cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2 1109d899a5ff5fd74bc01e581693685b/3766b952a2dda4c47e71952aeef33c77


```

You would run each command individually in the terminal:

Terminal window

```

terraform import cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31 1109d899a5ff5fd74bc01e581693685b/3c0b456bc2aa443089c5f40f45f51b31


```

```

cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31: Importing from ID "1109d899a5ff5fd74bc01e581693685b/3c0b456bc2aa443089c5f40f45f51b31"...

cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31: Import complete!

  Imported cloudflare_record [id=3c0b456bc2aa443089c5f40f45f51b31]

cloudflare_record.terraform_managed_resource_3c0b456bc2aa443089c5f40f45f51b31: Refreshing state... [id=3c0b456bc2aa443089c5f40f45f51b31]


Import successful!


The resources that were imported are shown above. These resources are now in

your Terraform state and will henceforth be managed by Terraform.


```

Terminal window

```

terraform import cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354 1109d899a5ff5fd74bc01e581693685b/d09d916d059aa9fc8cb54bdd49deea5f


```

```

cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354: Importing from ID "1109d899a5ff5fd74bc01e581693685b/d09d916d059aa9fc8cb54bdd49deea5f"...

cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354: Import complete!

  Imported cloudflare_record [id=d09d916d059aa9fc8cb54bdd49deea5f]

cloudflare_record.terraform_managed_resource_5e10399a590a45279f09aa8fb1163354: Refreshing state... [id=d09d916d059aa9fc8cb54bdd49deea5f]


Import successful!


The resources that were imported are shown above. These resources are now in

your Terraform state and will henceforth be managed by Terraform.


```

Terminal window

```

terraform import cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248 1109d899a5ff5fd74bc01e581693685b/8d6ec0d02c5b22212ff673782c816ef8


```

```

cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248: Importing from ID "1109d899a5ff5fd74bc01e581693685b/8d6ec0d02c5b22212ff673782c816ef8"...

cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248: Import complete!

  Imported cloudflare_record [id=8d6ec0d02c5b22212ff673782c816ef8]

cloudflare_record.terraform_managed_resource_de1cb74bae184b569bb7f83fefe72248: Refreshing state... [id=8d6ec0d02c5b22212ff673782c816ef8]


Import successful!


The resources that were imported are shown above. These resources are now in

your Terraform state and will henceforth be managed by Terraform.


```

Terminal window

```

terraform import cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2 1109d899a5ff5fd74bc01e581693685b/3766b952a2dda4c47e71952aeef33c77


```

```

cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2: Importing from ID "1109d899a5ff5fd74bc01e581693685b/3766b952a2dda4c47e71952aeef33c77"...

cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2: Import complete!

  Imported cloudflare_record [id=3766b952a2dda4c47e71952aeef33c77]

cloudflare_record.terraform_managed_resource_5799bb01054843eea726758f935d2aa2: Refreshing state... [id=3766b952a2dda4c47e71952aeef33c77]


Import successful!


The resources that were imported are shown above. These resources are now in

your Terraform state and will henceforth be managed by Terraform.


```

If you now run `terraform plan`, you will notice that Terraform will no longer try to re-create the `cloudflare_record` resources:

Terminal window

```

terraform plan | grep changes


```

```

No changes. Infrastructure is up-to-date.


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/advanced-topics/","name":"Advanced topics"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/advanced-topics/import-cloudflare-resources/","name":"Import Cloudflare resources"}}]}
```

---

---
title: Provider customization
description: Customize the Cloudflare Terraform provider settings using configuration parameters or environment variables.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Provider customization

Terraform communicates with cloud and global network provider APIs such as Cloudflare through modules known as providers. These providers are [installed automatically](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/#2-initialize-terraform-and-the-cloudflare-provider) when you run `terraform init` in a directory that has a `.tf` file containing a provider.

Typically, the only required parameters to the provider are those required to authenticate. However, you may want to customize the provider to your needs. The following section covers some [optional settings ↗](https://www.terraform.io/docs/providers/cloudflare/#argument-reference) that you can pass to the Cloudflare Terraform provider.

## Adjust the default Cloudflare provider settings

Note

The examples below build on the [Cloudflare Terraform tutorial](https://developers.cloudflare.com/terraform/tutorial/).

You can customize the Cloudflare Terraform provider using configuration parameters, specified either in your `.tf` configuration files or via environment variables. Using environment variables may make sense when running Terraform from a CI/CD system or when the change is temporary and does not need to be persisted in your configuration history.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/advanced-topics/","name":"Advanced topics"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/advanced-topics/provider-customization/","name":"Provider customization"}}]}
```

---

---
title: Remote R2 backend
description: Store Terraform state files remotely using Cloudflare R2 as an S3-compatible backend.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Remote R2 backend

[Cloudflare R2](https://developers.cloudflare.com/r2/) and [Terraform remote backends ↗](https://developer.hashicorp.com/terraform/language/settings/backends/remote) can interact with each other to provide a seamless experience for Terraform state management.

Cloudflare R2 is an object storage service that provides a highly available, scalable, and secure way to store and serve static assets, such as images, videos, and static websites. R2 has [S3 API compatibility](https://developers.cloudflare.com/r2/api/s3/api/) making it easy to integrate with existing cloud infrastructure and applications.

## Prerequisites

### Create R2 bucket

Using [Wrangler](https://developers.cloudflare.com/workers/wrangler/install-and-update/), [API](https://developers.cloudflare.com/api/resources/r2/subresources/buckets/methods/create/), or [Account View Dashboard ↗](https://dash.cloudflare.com/?to=/:account/r2/new) create an [R2 Bucket](https://developers.cloudflare.com/r2/buckets/create-buckets/).

* [ Wrangler ](#tab-panel-8603)
* [ API ](#tab-panel-8604)

Terminal window

```

wrangler r2 bucket create <YOUR_BUCKET_NAME>


```

Terminal window

```

 curl https://api.cloudflare.com/client/v4/accounts/{account_id}/r2/buckets \

--header "Authorization: Bearer <API_TOKEN>" \

--header "Content-Type: application/json" \

--data '{"name": "<YOUR_BUCKET_NAME>"}'


```

Note

Bucket names can only contain lowercase letters (`a-z`), numbers (`0-9`), and hyphens (`-`).

### Create scoped bucket API keys

Next you will need to create a [bucket scoped R2 API token](https://developers.cloudflare.com/r2/api/tokens/) with `Object Read & Write` permissions. To create an API token, do the following:

1. In **Account Home**, select **R2**.
2. Under **Account details**, select **Manage R2 API tokens**.
3. Select [**Create API token** ↗](https://dash.cloudflare.com/?to=/:account/r2/api-tokens).
4. Select the **R2 Token** text to edit your API token name.
5. Under **Permissions**, select the **Object Read and Write** permissions, then scope your token to your `<YOUR_BUCKET_NAME>` bucket.
6. Select **Create API Token**.

After your token has been successfully created, review your **Secret Access Key** and **Access Key ID** values.

## Define R2 backend

Update your [cloudflare.tf](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/) file to include a [backend ↗](https://developer.hashicorp.com/terraform/language/backend) for the `<YOUR_BUCKET_NAME>` bucket you created above.

Note

Terraform code snippets below refer to the v4 SDK only.

```

terraform {

  backend "s3" {

    bucket = "<YOUR_BUCKET_NAME>"

    key    = "/some/key/terraform.tfstate"

    region                      = "auto"

    skip_credentials_validation = true

    skip_metadata_api_check     = true

    skip_region_validation      = true

    skip_requesting_account_id  = true

    skip_s3_checksum            = true

    use_path_style              = true

    access_key = "<YOUR_R2_ACCESS_KEY>"

    secret_key = "<YOUR_R2_ACCESS_SECRET>"

    endpoints = { s3 = "https://<YOUR_ACCOUNT_ID>.r2.cloudflarestorage.com" }

  }

  required_providers {

    cloudflare = {

      source = "cloudflare/cloudflare"

      version = "~> 4"

    }

  }

}

provider "cloudflare" {

  # token pulled from $CLOUDFLARE_API_TOKEN

}

variable "account_id" { default = "<YOUR_ACCOUNT_ID>" }


```

## Migrate state file to R2 backend

After updating your `cloudflare.tf` file you can issue the `terraform init -reconfigure` command to migrate from a local state to [remote state ↗](https://developer.hashicorp.com/terraform/language/state/remote).

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/advanced-topics/","name":"Advanced topics"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/advanced-topics/remote-backend/","name":"Remote R2 backend"}}]}
```

---

---
title: Create a partial zone using Terraform
description: Automate the setup of a Cloudflare partial (CNAME) zone using the Terraform provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Create a partial zone using Terraform

A [partial zone](https://developers.cloudflare.com/dns/zone-setups/partial-setup/) lets you use Cloudflare for a subdomain while keeping your existing authoritative DNS provider for the parent domain. This guide shows how to automate the setup using the [Cloudflare Terraform provider ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs).

Warning

A partial zone cannot be created in the same Cloudflare account as the parent domain's full zone.

## Prerequisites

* Terraform installed. Refer to [Get started](https://developers.cloudflare.com/terraform/installing/).
* Your Cloudflare account ID and a configured provider block. Refer to [Initialize Terraform](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/).

## Create the zone

Add the zone configuration and apply the change to create the zone:

```

resource "cloudflare_zone" "subdomain_example_com" {

  account = {

    id = var.cloudflare_account_id

  }

  name = "subdomain.example.com"

}


```

Then, in a new Terraform plan and apply cycle, upgrade the zone to a Business plan or higher:

```

resource "cloudflare_zone_subscription" "example_zone_subscription" {

  zone_id = cloudflare_zone.subdomain_example_com.id

  frequency = "monthly"

  rate_plan = {

    id = "business"

    currency = "USD"

  }

}


```

Then, again in a new Terraform plan and apply cycle, update your Terraform configuration to add `type = "partial"` to the zone:

```

resource "cloudflare_zone" "subdomain_example_com" {

  account = {

    id = var.cloudflare_account_id

  }

  name = "subdomain.example.com"

  type = "partial"

}


```

Terraform places the zone in a **Pending** state. You must add the necessary DNS records and verify domain ownership before Cloudflare activates it.

Note

Refer to the [cloudflare\_zone docs ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone) in the Terraform provider documentation when you need to reference other zone properties.

## Related resources

* [Partial zone setup](https://developers.cloudflare.com/dns/zone-setups/partial-setup/)
* [Convert a full zone to partial](https://developers.cloudflare.com/dns/zone-setups/conversions/convert-full-to-partial/)
* [cloudflare\_zone resource ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/how-to/","name":"How-to guides"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/how-to/create-partial-zone/","name":"Create a partial zone using Terraform"}}]}
```

---

---
title: Create a subdomain zone using Terraform
description: Automate the setup of a Cloudflare subdomain zone for Enterprise accounts using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Create a subdomain zone using Terraform

A [subdomain zone](https://developers.cloudflare.com/dns/zone-setups/subdomain-setup/) lets you manage a subdomain in a separate Cloudflare zone from the parent domain. This is useful for access control and team management. This guide shows how to automate the setup using the [Cloudflare Terraform provider ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs). It is only available for Enterprise accounts

> NOTE: subdomain setup is only available for Enterprise accounts

## Prerequisites

* Terraform installed. Refer to [Get started](https://developers.cloudflare.com/terraform/installing/).
* Your Cloudflare account ID and a configured provider block. Refer to [Initialize Terraform](https://developers.cloudflare.com/terraform/tutorial/initialize-terraform/).

## Create the zone

Create a `cloudflare_zone` resource for the subdomain zone. The following example creates a zone for `subdomain.example.com`:

```

resource "cloudflare_zone" "subdomain_example_com" {

  account = {

    id = var.cloudflare_account_id

  }

  name = "subdomain.example.com"

  type = "full"

}


```

Terraform creates the zone in a **Pending** state. You must add NS delegation records to the parent zone before Cloudflare activates it.

Note

Refer to the [cloudflare\_zone docs ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone) in the Terraform provider documentation when you need to reference other zone properties.

## Related resources

* [Subdomain setup](https://developers.cloudflare.com/dns/zone-setups/subdomain-setup/)
* [cloudflare\_zone resource ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/zone)
* [cloudflare\_dns\_record resource ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/dns%5Frecord)

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/how-to/","name":"How-to guides"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/how-to/create-secondary-zone/","name":"Create a subdomain zone using Terraform"}}]}
```

---

---
title: 403 Authentication error when creating DNS records
description: Fix the 403 authentication error caused by incorrect zone data source indexing in Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# 403 Authentication error when creating DNS records

When creating DNS records using Terraform, the API returns the following error:

`Error: failed to create DNS record: HTTP status 403: Authentication error (10000)`

This is caused by an error in your code syntax, when you are not using index `[0]` for the zones. Find an example below and a more detailed thread on [GitHub ↗](https://github.com/cloudflare/terraform-provider-cloudflare/issues/913).

Instead of this:

```

zone_id = data.cloudflare_zones.example_com.id


```

Use this:

```

zone_id = data.cloudflare_zones.example_com.zones[0].id`


```

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/troubleshooting/","name":"Troubleshooting"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/troubleshooting/authentication-error-dns-records/","name":"403 Authentication error when creating DNS records"}}]}
```

---

---
title: Rule IDs change when I modify a ruleset
description: Prevent rule ID changes in Cloudflare rulesets managed by Terraform by using the ref field.
image: https://developers.cloudflare.com/core-services-preview.png
---

> Documentation Index  
> Fetch the complete documentation index at: https://developers.cloudflare.com/terraform/llms.txt  
> Use this file to discover all available pages before exploring further.

[Skip to content](#%5Ftop) 

# Rule IDs change when I modify a ruleset

For [cloudflare\_ruleset ↗](https://registry.terraform.io/providers/cloudflare/cloudflare/latest/docs/resources/ruleset) resources, the Cloudflare provider may delete a rule and create a new one when you modify a ruleset in your Terraform configuration. This happens because the API cannot match rules in your new Terraform configuration with existing rules in your Cloudflare configuration. Modifying a ruleset in your Terraform configuration and applying the changes will create new rules with different rule IDs in your Cloudflare account or zone.

This behavior may have an impact on any automation or monitoring systems you may have configured that rely on having immutable rule IDs between rule modifications.

## How to keep the same rule ID between modifications

To keep existing rule IDs when making changes to a ruleset through Terraform, add a `ref` field to each rule.

The `ref` field is a user-defined external identifier that must be unique for each rule in a ruleset. When you provide a `ref` value, the provider will match the rule in your updated Terraform configuration with the existing rule with the same `ref` external identifier, and the rule ID will be preserved.

`ref` values have a string data type with a minimum length of one character. For example, `my_ref`.

Once you set the `ref` field of a rule, changing the `ref` field value will make Terraform create a new rule.

## `cf-terraforming` support for `ref` field values

By default, when you create a rule, its `ref` value will be equal to the rule ID. You can set `ref` values via Cloudflare API.

When you [import your existing Cloudflare configuration to Terraform](https://developers.cloudflare.com/terraform/advanced-topics/import-cloudflare-resources/) using [cf-terraforming ↗](https://github.com/cloudflare/cf-terraforming), the generated Terraform configuration will have `ref` values for each rule, with the same value as the rule ID.

If you manually created your Terraform configuration and your rules' configuration does not have a `ref` field, add a `ref` field to each rule so that each ruleset modification does not generate new rule IDs.

```json
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"item":{"@id":"/directory/","name":"Directory"}},{"@type":"ListItem","position":2,"item":{"@id":"/terraform/","name":"Terraform"}},{"@type":"ListItem","position":3,"item":{"@id":"/terraform/troubleshooting/","name":"Troubleshooting"}},{"@type":"ListItem","position":4,"item":{"@id":"/terraform/troubleshooting/rule-id-changes/","name":"Rule IDs change when I modify a ruleset"}}]}
```
