---
title: Secure your first application · Cloudflare Learning Paths
description: "To ensure holistic security precautions, we recommend securing
  each distinct private application with at least two policies:"
lastUpdated: 2025-10-22T21:11:06.000Z
chatbotDeprioritize: false
source_url:
  html: https://developers.cloudflare.com/learning-paths/replace-vpn/build-policies/create-policy/
  md: https://developers.cloudflare.com/learning-paths/replace-vpn/build-policies/create-policy/index.md
---

To ensure holistic security precautions, we recommend securing each distinct private application with at least two policies:

* A [Gateway DNS policy](https://developers.cloudflare.com/cloudflare-one/traffic-policies/dns-policies/) with the appropriate identity and device posture values, targeting the domain list that defines your application. Policy enforcement happens at the request resolution event, before the user’s device makes a connection request to the application itself; if denied here, no traffic will reach your private network.

* A [Gateway network policy](https://developers.cloudflare.com/cloudflare-one/traffic-policies/network-policies/) with the same identity and device posture values as the DNS policy, targeting the IP list that defines your application. You can optionally include the domain list by matching the SNI header. Then, you can include any combinations of ports or protocols that are relevant for application access. Network policy enforcement happens after the user passes the DNS policy, when the user's device attempts to connect to the target application.

## Create a Gateway policy

To create a new policy, open [Zero Trust](https://one.dash.cloudflare.com/) and go to **Gateway** > **Firewall policies**.

## Example DNS policy

* Dashboard

  | Traffic Selector | Operator | Value |
  | - | - | - |
  | Domain | in list | `Company Wiki domains` |

  | Identity Selector | Operator | Value |
  | - | - | - |
  | User email | matches regex | `.*@example.com` |

  | Action |
  | - |
  | Allow |

* API

  ```bash
  curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/rules \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Company Wiki DNS policy",
    "conditions": [
      {
        "type": "traffic",
        "expression": {
          "any": {
            "in": {
              "lhs": {
                "splat": "dns.domains"
              },
              "rhs": "$<DOMAIN_LIST_ID>"
            }
          }
        }
      },
      {
        "type": "identity",
        "expression": {
          "matches": {
            "lhs": "identity.email",
            "rhs": ".*@example.com"
          }
        }
      }
    ],
    "action": "allow",
    "precedence": 13002,
    "enabled": true,
    "description": "Allow employees to access company wiki domains.",
    "filters": [
      "dns"
    ]
  }'
  ```

* Terraform (v5)

  ```tf
  resource "cloudflare_zero_trust_gateway_policy" "dns_allow_wiki_domains" {
    name        = "Company Wiki DNS policy"
    enabled     = true
    account_id  = var.cloudflare_account_id
    description = "Managed by Terraform - Allow employees to access company wiki domains."
    precedence  = 102
    action      = "allow"
    filters     = ["dns"]
    traffic     = "any(dns.domains[*] in ${"$"}${cloudflare_zero_trust_list.wiki_domains.id})"
    identity    = "identity.email matches \".*@example.com\""
  }
  ```

## Example network policy

* Dashboard

  | Traffic Selector | Operator | Value |
  | - | - | - |
  | Destination IP | in list | `Company Wiki IPs` |

  | Identity Selector | Operator | Value |
  | - | - | - |
  | User Email | matches regex | `.*@example.com` |

  | Action |
  | - |
  | Allow |

* API

  ```bash
  curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/rules \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Company Wiki network policy",
    "conditions": [
      {
        "type": "traffic",
        "expression": {
          "in": {
            "lhs": "net.dst.ip",
            "rhs": "$<IP_LIST_ID>"
          }
        }
      },
      {
        "type": "identity",
        "expression": {
          "matches": {
            "lhs": "identity.email",
            "rhs": ".*@example.com"
          }
        }
      }
    ],
    "action": "allow",
    "precedence": 13002,
    "enabled": true,
    "description": "Allow employees to access company wiki IPs.",
    "filters": [
      "l4"
    ]
  }'
  ```

* Terraform (v5)

  ```tf
  resource "cloudflare_zero_trust_gateway_policy" "network_allow_wiki_IPs" {
    name        = "Company Wiki Network policy"
    enabled     = true
    account_id  = var.cloudflare_account_id
    description = "Managed by Terraform - Allow employees to access company wiki IPs."
    precedence  = 103
    action      = "allow"
    filters     = ["l4"]
    traffic     = "net.dst.ip in ${"$"}${cloudflare_zero_trust_list.wiki_IPs.id}"
    identity    = "identity.email matches \".*@example.com\""
  }
  ```

### Catch-all policy

We recommend adding a catch-all policy to the bottom of your network policy list. An effective Zero Trust model should prioritize default-deny actions to avoid any overly permissive policy building. For example,

* Dashboard

  | Traffic Selector | Operator | Value | Logic |
  | - | - | - | - |
  | Destination IP | in list | `All private network ranges` | Or |
  | SNI Domain | in list | `All private apex domains` | |

  | Action |
  | - |
  | Block |

* API

  ```bash
  curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/rules \
  --header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "name": "Catch-all block policy",
    "conditions": [
      {
        "type": "traffic",
        "expression": {
          "or": [
            {
              "in": {
                "lhs": "net.dst.ip",
                "rhs": "$<IP_LIST_ID>"
              }
            },
            {
              "any": {
                "in": {
                  "lhs": {
                    "splat": "net.sni.domains"
                  },
                  "rhs": "$<DOMAIN_LIST_ID>"
                }
              }
            }
          ]
        }
      }
    ],
    "action": "block",
    "precedence": 14002,
    "enabled": true,
    "description": "Block access to private network.",
    "filters": [
      "l4"
    ]
  }'
  ```

* Terraform (v5)

  ```tf
  resource "cloudflare_zero_trust_gateway_policy" "network_catch_all" {
    name        = "Catch-all block policy"
    enabled     = true
    account_id  = var.cloudflare_account_id
    description = "Managed by Terraform - Block access to private network."
    precedence  = 14002
    action      = "block"
    filters     = ["l4"]
    traffic     = "net.dst.ip in ${"$"}${cloudflare_zero_trust_list.private_IPs.id} or any(net.sni.domains[*] in ${"$"}${cloudflare_zero_trust_list.private_domains.id})"
  }
  ```

Network policies are evaluated in [top-down order](https://developers.cloudflare.com/cloudflare-one/traffic-policies/order-of-enforcement/#order-of-precedence), so if a user does not match an explicitly defined policy for an application, they will be blocked. To learn how multiple policies interact, refer to [Order of enforcement](https://developers.cloudflare.com/cloudflare-one/traffic-policies/order-of-enforcement/).

Note

It is not recommended to employ a default-deny model while testing. Instead, build your explicit application policies and [monitor your logs](https://developers.cloudflare.com/cloudflare-one/insights/logs/gateway-logs/) to determine if your policies are working as expected. If you do not see the policies triggering in your logs, you may need to tune your policies and review what kind of information (identity groups, device posture values, etc.) is being sent with your traffic.
