# WAF quickstart

This guide instructs you through scoring your first request against [Web Application Firewall (WAF)](/en/documentation/platform/firewall/#waf).

- Create a rule set that carries eight threat families at `medium` sensitivity.
- Apply that rule set to a firewall with a `Set WAF` behavior in blocking mode.
- Send an injection-shaped request and read the refusal.

Four objects put a request under inspection, and each one links to the next:

1. The **rule set** holds the threat families and the sensitivity chosen for each.
2. The **firewall** carries the WAF module, and the **workload** serving your application is bound to that firewall.
3. The **Rules Engine rule** on that firewall carries a `Set WAF` behavior, which names the rule set and the mode.
4. The **request** is what WAF then scores against the rule set.

A rule set inspects nothing on its own. Until a Rules Engine rule names it, no request is scored.

---

Select the interface you will use. The prerequisites and every stage below follow that choice.

## Prerequisites

- An Azion account. To create one, refer to [How to create an account on Azion](/en/documentation/fundamentals/creating-account/).
- A firewall with the WAF module turned on. WAF is the one module a firewall does not turn on for itself. Refer to [Set a firewall's main settings](/en/documentation/guides/application-security/firewall-and-waf/firewall-configure-main-settings/).
- A workload bound to that firewall, serving an application. Refer to [Bind a firewall to a workload](/en/documentation/guides/application-security/firewall-and-waf/firewall-protect-your-domain/).

**Console**

- Access to Azion Console. To sign in, refer to [How to access Azion Console](/en/documentation/guides/platform/account-and-billing/how-to-access-azion-console/).

**CLI**

- The [Azion CLI](/en/documentation/devtools/cli/) installed and authorized.

**API**

- A personal token and `curl`. To create a token, refer to [Personal Tokens](/en/documentation/fundamentals/personal-tokens/).

---

## Create the WAF rule set

The rule set holds what to detect. It carries eight threat families, and each family has a sensitivity level that fixes the score at which it blocks. Every family starts at `medium`, which is the level this guide uses.

**Console**

To create the rule set in Azion Console:

1. **Open the WAF Rules page**

   Access [Azion Console](https://console.azion.com/) > **Edge Libraries** > **WAF Rules**.

2. **Select + WAF Rule**

3. **Name the rule set**

   In the **General** section, enter a **Name**. For example: `storefront-waf`.

4. **Leave every family at its default sensitivity**

   The **Threat Type Configuration** section lists eight threat families. Each one opens on *Sensitivity Medium*.

5. **Keep the Active switch turned on**

6. **Save the rule set**

The rule set appears in **WAF Rules**, which lists its **Threat Type Configuration**, **Status**, **Last Editor**, and **Last Modified**.

**CLI**

The Azion CLI creates a complete rule set from a name alone. To create it:

1. **Run the create command**

   ```bash
   azion create waf --name "storefront-waf"
   ```

2. **Read the output**

   The command prints the id of the new rule set:

   ```text
   Created WAF with ID 12349
   ```

The rule set is active, it runs on ruleset `1`, and it carries all eight threat families at `medium`. Record the id. You pass it to the rule.

**API**

The request body carries the eight threat families and the sensitivity for each.

1. **Send the create request**

   Replace `[TOKEN VALUE]` with your personal token:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/workspace/wafs \
     --header 'Accept: application/json' \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data '{
     "name": "storefront-waf",
     "active": true,
     "product_version": "1.0",
     "engine_settings": {
       "engine_version": "2021-Q3",
       "type": "score",
       "attributes": {
         "rulesets": [1],
         "thresholds": [
           { "threat": "cross_site_scripting", "sensitivity": "medium" },
           { "threat": "directory_traversal", "sensitivity": "medium" },
           { "threat": "evading_tricks", "sensitivity": "medium" },
           { "threat": "file_upload", "sensitivity": "medium" },
           { "threat": "identified_attack", "sensitivity": "medium" },
           { "threat": "remote_file_inclusion", "sensitivity": "medium" },
           { "threat": "sql_injection", "sensitivity": "medium" },
           { "threat": "unwanted_access", "sensitivity": "medium" }
         ]
       }
     }
   }'
   ```

2. **Read the response**

   A create answers `202`, not `201`, and `"state": "pending"` means the change is still propagating:

   ```json
   {
     "state": "pending",
     "data": {
       "id": 12349,
       "active": true,
       "name": "storefront-waf",
       "last_editor": "user@example.com",
       "last_modified": "2026-01-01T12:00:00.000000Z",
       "product_version": "1.0",
       "engine_settings": {
         "engine_version": "2021-Q3",
         "type": "score",
         "attributes": {
           "rulesets": [1],
           "thresholds": [
             { "threat": "cross_site_scripting", "sensitivity": "medium" },
             { "threat": "directory_traversal", "sensitivity": "medium" },
             { "threat": "evading_tricks", "sensitivity": "medium" },
             { "threat": "file_upload", "sensitivity": "medium" },
             { "threat": "identified_attack", "sensitivity": "medium" },
             { "threat": "remote_file_inclusion", "sensitivity": "medium" },
             { "threat": "sql_injection", "sensitivity": "medium" },
             { "threat": "unwanted_access", "sensitivity": "medium" }
           ]
         }
       },
       "version_id": null,
       "version_state": null,
       "is_versioned": false,
       "version": null
     }
   }
   ```

Record the `id`. You pass it to the rule.

---

## Apply the rule set with a Rules Engine rule

A [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/) rule selects requests by its own criteria. Its `Set WAF` behavior then names one rule set and one mode. The rule below applies your rule set to every request the firewall receives.

The mode is required, and it belongs to the behavior rather than to the rule set. `Blocking` refuses a request whose score reaches a threshold. `Logging` records the same request and serves it. This guide uses `Blocking`.

> **Caution**
>
> Key the criterion on the request URI, never on the query string. A criterion such as `${request_args}` `matches` `.*` does not match a request without a query string. It silently skips every `POST` whose payload sits in the body.

**Console**

To apply the rule set in Azion Console:

1. **Open the firewall bound to your workload**

   Still in Azion Console, go to **Firewalls** and select that firewall.

2. **Select the Rules Engine tab**

3. **Select + Rule**

4. **Name the rule**

   Enter a name for the rule. For example: `Apply storefront-waf`.

5. **Set the criterion**

   In the **Criteria** section, select the `Request Uri` variable, the *starts with* comparison operator, and `/` as the argument.

6. **Add the Set WAF behavior**

   In the **Behaviors** section, select **Set WAF**, then select the rule set you created.

7. **Select Blocking as the mode**

8. **Save the rule**

The firewall now scores every request it receives against the rule set. A rule carries at most one `Set WAF` behavior.

**CLI**

`azion create firewall-rule` reads the whole rule from a JSON file. It has no flag for a behavior or a mode.

1. **Write the rule to a file**

   Save the following as `fw-rule.json`, with the id of your rule set in `waf_id`:

   ```json
   {
     "name": "Apply storefront-waf",
     "active": true,
     "description": "",
     "criteria": [
       [
         {
           "conditional": "if",
           "variable": "${request_uri}",
           "operator": "starts_with",
           "argument": "/"
         }
       ]
     ],
     "behaviors": [
       {
         "type": "set_waf",
         "attributes": {
           "waf_id": 12349,
           "mode": "blocking"
         }
       }
     ]
   }
   ```

2. **Create the rule**

   Replace `<firewall-id>` with the id of your firewall:

   ```bash
   azion create firewall-rule --firewall-id <firewall-id> --file fw-rule.json
   ```

3. **Read the output**

   The command prints the id of the new rule:

   ```text
   Created Firewall Rule with ID 123457
   ```

The firewall now scores every request it receives against the rule set.

**API**

The criteria carry `${request_uri}`, so the body is sent from a file.

1. **Write the rule to a file**

   Save the following as `rule.json`, with the id of your rule set in `waf_id`:

   ```json
   {
     "name": "Apply storefront-waf",
     "active": true,
     "criteria": [
       [
         {
           "conditional": "if",
           "variable": "${request_uri}",
           "operator": "starts_with",
           "argument": "/"
         }
       ]
     ],
     "behaviors": [
       {
         "type": "set_waf",
         "attributes": {
           "waf_id": 12349,
           "mode": "blocking"
         }
       }
     ]
   }
   ```

2. **Send the create request**

   Replace `<firewall-id>` with the id of your firewall:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/request_rules \
     --header 'Accept: application/json' \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data @rule.json
   ```

3. **Read the response**

   A `202` carries the rule, with the `order` it holds among the rules of the firewall:

   ```json
   {
     "state": "pending",
     "data": {
       "id": 123456,
       "name": "Apply storefront-waf",
       "last_editor": "user@example.com",
       "last_modified": "2026-01-01T12:00:00.000000Z",
       "created_at": "2026-01-01T12:00:00.000000Z",
       "active": true,
       "criteria": [
         [
           {
             "conditional": "if",
             "variable": "${request_uri}",
             "operator": "starts_with",
             "argument": "/"
           }
         ]
       ],
       "behaviors": [
         {
           "type": "set_waf",
           "attributes": {
             "waf_id": 12349,
             "mode": "blocking"
           }
         }
       ],
       "description": "",
       "order": 1
     }
   }
   ```

A body that omits `mode` is refused with `400` and the error `10059 Required Field`, pointing at `/data/behaviors/0/attributes/mode`.

---

## Verify that WAF blocks a request

The check is the same whichever interface built the rule. Your workload answers on a domain of the form `<id>.map.azionedge.net`, written below as `<your-workload-domain>`.

A new binding takes time to reach Azion's distributed infrastructure. Expect close to six minutes before the rule takes effect. The domain then answers inconsistently for several minutes, while the change arrives in different places. A request that still returns the earlier response has broken nothing: wait, then send it again.

Send a clean request first:

```bash
curl -i https://<your-workload-domain>/
```

The request is not scored as a threat and reaches the application, which answers it:

```text
HTTP/2 200
```

Your own application decides that status. What matters is that the request is not refused.

Now send an injection-shaped request, carrying `1' OR '1'='1` in the query string:

```bash
curl -i "https://<your-workload-domain>/?q=1%27%20OR%20%271%27%3D%271"
```

WAF refuses it:

```text
HTTP/2 400
content-type: text/html
x-azion-request-id: 0123456789abcdef0123456789abcdef
```

The body is Azion's default error page, headed **Bad Request**. Nothing in the response names WAF, the rule that matched, or the score. Record `x-azion-request-id`: that value finds the request in [Real-Time Events](/en/documentation/platform/real-time-events/).

That request tripped the internal rules `1009` and `1013`, both on the `q` argument. It scored under the SQL injection and cross-site scripting families. Your rule set is now scoring traffic.

---

## Next steps

- [Scoring and modes](/en/documentation/platform/firewall/waf/scoring-and-modes.md): The path a request travels, the scoring model, and what each mode does.
- [Firewall best practices](/en/documentation/platform/firewall/best-practices.md#waf): Why production starts in Logging rather than the Blocking this guide used, and what each practice costs.
- [Tune a WAF rule set](/en/documentation/guides/application-security/firewall-and-waf/tune-waf.md): Read what a rule set matched and turn a false positive into an exception.
- [Exempt one query string parameter](/en/documentation/guides/application-security/firewall-and-waf/exempt-query-parameter.md): Take one argument out of the scoring of one internal rule, the first time a legitimate request is blocked.
- [Apply a rule set to every request](/en/documentation/guides/application-security/firewall-and-waf/apply-rule-set.md): The rule that binds a rule set to a firewall, for the next firewall that needs one.
- [Firewall limits](/en/documentation/platform/firewall/limits.md#waf): The bounds a rule set operates within, and the usage each service plan includes.
