---
title: Cloudflare Terraform provider
description: Configure Cloudflare using HashiCorp's “Infrastructure as Code” tool, Terraform. With Cloudflare’s Terraform provider, 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.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: Terraform ships as a single binary file. The examples below include installation information for popular operating systems.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/installing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: Before you begin, install Terraform. Each tutorial builds on the previous, so you should complete the tutorials in the order shown below.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/index.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/add-page-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/configure-https-settings.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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


```

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/initialize-terraform.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/revert-configuration.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/track-history.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/tutorial/use-load-balancing.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: This page provides examples of configuring DDoS managed rulesets in your zone or account using Terraform. It covers the following configurations:
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/ddos-managed-rulesets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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.

Note

Terraform code snippets below refer to the v4 SDK only.

```

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.

Note

Terraform code snippets below refer to the v4 SDK only.

```

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: Bulk Redirects
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-bulk-redirects.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Bulk Redirects

```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/link-bulk-redirects/","name":"Bulk Redirects"}}]}
```

---

---
title: Cache Rules
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-cache-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Cache Rules

```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/link-cache-rules/","name":"Cache Rules"}}]}
```

---

---
title: Configuration Rules
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-configuration-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Configuration Rules

```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/link-configuration-rules/","name":"Configuration Rules"}}]}
```

---

---
title: Origin Rules
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-origin-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Origin Rules

```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/link-origin-rules/","name":"Origin Rules"}}]}
```

---

---
title: Single Redirects
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-single-redirects.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Single Redirects

```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/link-single-redirects/","name":"Single Redirects"}}]}
```

---

---
title: Snippets
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-snippets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Snippets

```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/link-snippets/","name":"Snippets"}}]}
```

---

---
title: Workers
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/link-workers.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# Workers

```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/link-workers/","name":"Workers"}}]}
```

---

---
title: Rate limiting rules configuration using Terraform
description: This page provides examples of creating rate limiting rules in a zone or account using Terraform.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/rate-limiting-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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.

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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.

Note

Terraform code snippets below refer to the v4 SDK only.

```

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: This page provides examples of creating Transform Rules in a zone using Terraform. The examples cover the following scenarios:
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/transform-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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"

        }

      }

    }

  }

}


```

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

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

  
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.

Note

Terraform code snippets below refer to the v4 SDK only.

```

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"

      }

    }

  }

}


```

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

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

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.

Note

Terraform code snippets below refer to the v4 SDK only.

```

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"

      }

    }

  }

}


```

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

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

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

Note

Terraform code snippets below refer to the v4 SDK only.

Use the `cloudflare_managed_headers` Terraform resource to configure Managed Transforms. For example:

```

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: This page provides examples of creating WAF custom rules in a zone or account using Terraform. The examples cover the following scenarios:
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/waf-custom-rules.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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:

Note

Terraform code snippets below refer to the v4 SDK only.

```

resource "cloudflare_ruleset" "account_firewall_custom_ruleset" {

  account_id  = "<ACCOUNT_ID>"

  name        = "Custom ruleset blocking traffic in non-standard HTTP(S) ports"

  description = ""

  kind        = "custom"

  phase       = "http_request_firewall_custom"


  rules {

    ref         = "block_non_default_ports"

    description = "Block ports other than 80 and 443"

    expression  = "(not cf.edge.server_port in {80 443})"

    action      = "block"

  }

}


```

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

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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: This page provides examples of deploying and configuring WAF Managed Rules in your zone or account using Terraform. It covers the following configurations:
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/additional-configurations/waf-managed-rulesets.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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

Note

Terraform code snippets below refer to the v4 SDK only.

```

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:

Note

Terraform code snippets below refer to the v4 SDK only.

```

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:

Note

Terraform code snippets below refer to the v4 SDK only.

```

  # (...)


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

Note

Terraform code snippets below refer to the v4 SDK only.

```

  # (...)


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

Note

Terraform code snippets below refer to the v4 SDK only.

```

  # (...)


  # 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: Though all Terraform deployments are unique, follow these best practices to set yourself up for success.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/advanced-topics/best-practices.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

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

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/advanced-topics/import-cloudflare-resources.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: Terraform communicates with cloud and global network provider APIs such as Cloudflare through modules known as providers. These providers are installed automatically when you run terraform init in a directory that has a .tf file containing a provider.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/advanced-topics/provider-customization.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: Cloudflare R2 and Terraform remote backends can interact with each other to provide a seamless experience for Terraform state management.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/advanced-topics/remote-backend.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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-6668)
* [ API ](#tab-panel-6669)

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: A partial zone 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.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/how-to/create-partial-zone.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: A subdomain zone 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. It is only available for Enterprise accounts
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/how-to/create-secondary-zone.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: When creating DNS records using Terraform, the API returns the following error:
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/troubleshooting/authentication-error-dns-records.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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: For cloudflare_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.
image: https://developers.cloudflare.com/core-services-preview.png
---

[Skip to content](#%5Ftop) 

Was this helpful?

YesNo

[ Edit page ](https://github.com/cloudflare/cloudflare-docs/edit/production/src/content/docs/terraform/troubleshooting/rule-id-changes.mdx) [ Report issue ](https://github.com/cloudflare/cloudflare-docs/issues/new/choose) 

Copy page

# 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"}}]}
```
