---
name: azion-migrate-from-fastly-to-azion
description: >-
  Move a Fastly service to Azion: recreate delivery, VCL logic, Compute code, cache, and security, move the stored data, then switch the domain.
---

# Migrate from Fastly to Azion

A Fastly service collects years of delivery logic: several origins, VCL snippets, conditions, Compute packages, dictionaries, and cache overrides. Around it sit TLS automation, WAF policies, logging endpoints, and inspector dashboards. Moving it means recreating each layer on Azion and confirming the behavior before the domain changes.

On Azion, an [application](/en/documentation/platform/applications/) and its rules take over delivery, routing, and cache, and [connectors](/en/documentation/platform/connectors/) hold the origins. [Functions](/en/documentation/platform/functions/) runs the Compute code. [KV Store](/en/documentation/platform/kv-store/) and [Object Storage](/en/documentation/platform/object-storage/) hold the data. [Firewall](/en/documentation/platform/firewall/) filters traffic, and a [workload](/en/documentation/platform/workloads/) serves the domain. [Real-Time Metrics](/en/documentation/platform/real-time-metrics/), [Real-Time Events](/en/documentation/platform/real-time-events/), and [Data Stream](/en/documentation/platform/data-stream/) replace the inspectors and the logging endpoints.

Each stage of this guide moves one layer, in the order a migration runs: inventory, delivery, code and rules, data, security, monitoring, and the domain. Origins, cache policies, redirects, headers, and data access move with focused configuration changes. When near-zero downtime is not a requirement, migrate in phases with maintenance windows. Writes stop during each step, so the data needs no parallel synchronization.

---

The prerequisites and the procedures on this page switch with the interface you select:

## Prerequisites

- An Azion account. To open one, [sign up in Azion Console](https://console.azion.com/signup). For more information, refer to [Create an account](/en/documentation/fundamentals/creating-account/).
- The Fastly account, with access to its services, service versions, Compute packages, data stores, and TLS settings.
- Access to the DNS records or the registrar of each domain you move.
- `curl` and `dig`, to check responses and DNS answers.

**Console**

- Access to Azion Console. To sign in, refer 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 with your account. The commands on this page match Azion CLI 4.23.0.

**API**

- A personal token, sent in the `Authorization` header as `Token [TOKEN VALUE]`. To create one, refer to [Manage a personal token](/en/documentation/guides/platform/account-and-billing/personal-tokens/). Every request on this page goes to `https://api.azion.com/v4`.

---

## Inventory the Fastly account

Move one service first, not the most complex one in the account. Pick a service that tests the whole path and still moves quickly. A good first service has one or two domains, a few origins, cache rules, redirects, and headers. It may also have one Compute package and one logging destination. Use it to document the process and find the patterns your team reuses. Then expand: more complex VCL, more Compute services, the stored data, the observability, and the security rules.

Before you create anything on Azion, list what the service uses:

- Active services and their service versions.
- Domains and TLS certificates.
- Origins, shielding, health checks, and failover logic.
- Cache settings, cache keys, stale behavior, and purge workflows.
- Conditions, VCL snippets, custom VCL, and request and response settings.
- Compute packages and their language runtimes, with `fastly.toml` and the CI/CD settings that deploy them.
- Dictionaries, Config Store data, Secret Store values, and Edge Data Storage.
- Object Storage buckets, image optimization, streaming, WebSocket, and AI Accelerator traffic.
- WAF policies, bot controls, DDoS protections, and rate limits.
- Logging endpoints, inspector dashboards, and the alerts that depend on them.

Each item in the list maps to a stage of this guide. The table in Map each Fastly product to Azion names the destination of each one.

---

## Map each Fastly product to Azion

Every Fastly product in the inventory has a destination on Azion. Find the product in the first column, then move it with the stage that names its destination. A dash (`-`) in the last column means that Azion has no direct equivalent.

| Fastly product              | What it covers                                                                              | Destination on Azion                                                                                                                                                 |
| --------------------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| AI Accelerator              | Semantic caching and traffic acceleration for generative AI APIs                            | [AI Inference](/en/documentation/platform/ai-inference/) and Cache                                                                                                   |
| Bot Management              | Detection of, and response to, automated traffic                                            | [Bot Manager](/en/documentation/platform/firewall/bot-manager/quickstart/) and [Bot Manager Lite](/en/documentation/platform/firewall/bot-manager/bot-manager-lite/) |
| Cache APIs                  | Programmatic cache interaction for Compute applications                                     | Functions, Cache, and [Real-Time Purge](/en/documentation/platform/applications/cache/real-time-purge/)                                                              |
| Cache Reservation           | Reserved cache capacity for content                                                         | [Cache](/en/documentation/platform/applications/#cache), including [Tiered Cache](/en/documentation/platform/applications/cache/tiered-cache/)                       |
| Certainly                   | Fastly certificate authority for TLS certificates                                           | [Certificate Manager](/en/documentation/platform/workloads/certificate-manager/certificates/)                                                                        |
| Compute                     | Serverless compute that runs custom code on Fastly's network                                | Functions                                                                                                                                                            |
| Compute packages            | Packaged application code deployed to Compute                                               | Functions and the [Azion CLI](/en/documentation/devtools/cli/)                                                                                                       |
| Conditions                  | Request, cache, and response conditions                                                     | [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine/)                                                                               |
| DDoS Protection             | DDoS visibility and mitigation                                                              | [DDoS Protection](/en/documentation/platform/workloads/#ddos-protection)                                                                                             |
| Domain Inspector            | Domain-level traffic visibility and reporting                                               | Real-Time Metrics                                                                                                                                                    |
| Domains                     | Hostnames associated with services                                                          | Workloads                                                                                                                                                            |
| Edge Data Storage           | Key-value data used by Compute services                                                     | KV Store                                                                                                                                                             |
| Edge Rate Limiting          | Counting requests and acting when limits are exceeded                                       | [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/) and [Functions for Firewall](/en/documentation/platform/firewall/functions/)          |
| Fanout                      | Publish-subscribe messaging patterns                                                        | Functions and [WebSocket Proxy](/en/documentation/platform/applications/websocket/)                                                                                  |
| Fastly API                  | Programmatic management of Fastly resources                                                 | [Azion API](/en/documentation/devtools/api/)                                                                                                                         |
| Fastly CLI                  | Command-line workflow for services and Compute projects                                     | Azion CLI                                                                                                                                                            |
| Fastly services             | The delivery resource for domains, origins, caching, request handling, and service versions | Applications, served by Workloads                                                                                                                                    |
| Fastly Terraform Provider   | Infrastructure as code for Fastly resources                                                 | [Terraform Provider](/en/documentation/devtools/terraform/)                                                                                                          |
| Full-Site Delivery          | Website, application, API, and mobile delivery                                              | Applications                                                                                                                                                         |
| High Volume Logging         | Real-time log streaming to external destinations                                            | Data Stream                                                                                                                                                          |
| Hosts and origins           | Backend origin configuration for delivery services                                          | Connectors                                                                                                                                                           |
| Image Optimizer             | Real-time image transformation and optimization                                             | [Image Processor](/en/documentation/platform/applications/image-processor/quickstart/)                                                                               |
| Log Explorer & Insights     | Inspecting and monitoring log data                                                          | Real-Time Events                                                                                                                                                     |
| Media Shield for Live       | Origin offload for live video                                                               | Tiered Cache, with [Origin Shield](/en/documentation/platform/connectors/#origin-shield) to restrict the origin                                                      |
| Media Shield for VOD        | Origin offload for video on demand                                                          | Tiered Cache, with Origin Shield to restrict the origin                                                                                                              |
| Next-Gen WAF                | Detection of suspicious traffic and application protection                                  | [Web Application Firewall](/en/documentation/platform/firewall/waf/quickstart/)                                                                                      |
| Object Storage              | S3-compatible object storage                                                                | Object Storage                                                                                                                                                       |
| Observability features      | Traffic, security, performance, and origin visibility                                       | Real-Time Metrics, Real-Time Events, and Data Stream                                                                                                                 |
| Origin Inspector            | Origin response visibility and reporting                                                    | Real-Time Metrics and Real-Time Events                                                                                                                               |
| Platform TLS                | API-driven certificate and key management                                                   | Certificate Manager and the Azion API                                                                                                                                |
| Request settings            | Request manipulation, forwarding, and handling                                              | Rules Engine for Applications and Functions                                                                                                                          |
| Response settings           | Response headers and delivery behavior                                                      | Rules Engine for Applications                                                                                                                                        |
| Shielding                   | Routing cache misses through an intermediate shield location                                | Tiered Cache. Origin Shield, a module of Connectors, restricts the origin to Azion's addresses                                                                       |
| Streaming Delivery          | Live and video-on-demand streaming delivery                                                 | Applications, Cache, and Object Storage                                                                                                                              |
| TLS Service Options         | TLS settings for serving sites over HTTPS                                                   | Certificate Manager and Workloads                                                                                                                                    |
| VCL snippets and custom VCL | Custom logic for redirects, headers, cache keys, origin selection, and request control      | Rules Engine for Applications and Functions                                                                                                                          |
| WebSockets                  | Long-lived, bidirectional connections                                                       | WebSocket Proxy and the [WebSocket API](/en/documentation/devtools/runtime/api-reference/websocket/)                                                                 |

---

## Recreate the service on Azion

On Azion, a Fastly service splits into three resources. A connector holds the origin, an application holds the rules and the cache, and a workload serves the application on its domains.

| Aspect                    | Fastly                                                    | Azion                                                                                                                                               |
| ------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Primary delivery resource | Service and service version                               | Application, served by a workload                                                                                                                   |
| Domains                   | Service domains                                           | The **Domains** of a workload                                                                                                                       |
| Origins                   | Hosts and backends                                        | Connectors                                                                                                                                          |
| Configuration file        | `fastly.toml`, VCL, and the service version configuration | [`azion.config.js`](/en/documentation/devtools/cli/azion-config-js/), which can also be `azion.config.mjs` or `azion.config.cjs`, and Azion Console |
| Behavior logic            | Conditions, request and response settings, and VCL        | Rules Engine and Functions                                                                                                                          |
| Cache control             | Cache settings, VCL, and surrogate keys                   | Cache settings, Rules Engine, and Real-Time Purge                                                                                                   |
| Compute deployment        | Compute package                                           | A function and its function instance                                                                                                                |
| Observability             | Inspectors, logging endpoints, and Log Explorer           | Real-Time Metrics, Real-Time Events, and Data Stream                                                                                                |

| Task        | Fastly CLI                                      | Azion CLI                                                                            |
| ----------- | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
| Install     | `brew install fastly/tap/fastly`                | `curl -fsSL https://cli.azion.app/install.sh \| bash`, or `brew install azion`       |
| Sign in     | `fastly profile create`                         | `azion login`                                                                        |
| Run locally | `fastly compute serve`                          | `azion dev`                                                                          |
| Deploy      | `fastly compute publish`, or service activation | `azion link`, then `azion deploy`                                                    |
| View logs   | A logging endpoint, or Log Explorer             | `azion logs http` for the requests, and `azion logs cells` for function console logs |
| Purge       | `fastly purge`                                  | `azion purge --urls`, `--cachekey`, or `--wildcard`                                  |

Both `azion logs` subcommands return the logs of the last 5 minutes. For their flags, refer to [Azion CLI logs](/en/documentation/devtools/cli/logs/).

### Deliver an origin through an application

A service that fronts an origin becomes the chain connector, application, rule, and workload. The rule sends every request to the connector with *Set Connector*.

**Console**

To build the chain in Azion Console, create four resources in order. They are a connector of the HTTP type, an application, a Request Phase rule with **Set Connector**, and a workload. The Console panels of [Applications quickstart](/en/documentation/platform/applications/quickstart/) show every field. Create one connector for each origin, or one connector with several addresses, as Balance traffic across origins shows.

**CLI**

To build the chain with the Azion CLI, declare it in `azion.config.js`. This file holds one connector, one application with a cache setting and two rules, and one workload:

```javascript
import { defineConfig } from '@aziontech/config'

export default defineConfig({
  connectors: [{
    name: 'primary-origin',
    type: 'http',
    attributes: {
      addresses: [{ address: 'origin.example.com' }],
      connectionOptions: { transportPolicy: 'force_https', host: 'origin.example.com' },
      modules: {
        loadBalancer: { enabled: false, config: null },
        originShield: { enabled: false, config: null }
      }
    }
  }],
  applications: [{
    name: 'main-app',
    active: true,
    cache: [{
      name: 'default-cache',
      stale: true,
      browser: { maxAgeSeconds: 3600 },
      edge: { maxAgeSeconds: 3600 }
    }],
    rules: {
      request: [
        {
          name: 'Send to origin',
          active: true,
          criteria: [[{ variable: '${uri}', conditional: 'if', operator: 'starts_with', argument: '/' }]],
          behaviors: [
            { type: 'set_connector', attributes: { value: 'primary-origin' } },
            { type: 'set_cache_policy', attributes: { value: 'default-cache' } }
          ]
        },
        {
          name: 'API routes bypass cache',
          active: true,
          criteria: [[{ variable: '${uri}', conditional: 'if', operator: 'starts_with', argument: '/api/' }]],
          behaviors: [{ type: 'bypass_cache' }]
        }
      ]
    }
  }],
  workloads: [{
    name: 'main-app',
    active: true,
    infrastructure: 1,
    deployments: [{
      name: 'main-app',
      current: true,
      active: true,
      strategy: { type: 'default', attributes: { application: 'main-app' } }
    }]
  }]
})
```

A behavior `value` names a resource of the same file by its `name`. A connector of type `http` needs its `modules` object, even with both modules off. In the project folder, install the package the file imports:

```bash
npm install -D @aziontech/config
```

Then create the resources:

```bash
azion config apply
```

The command prints the ID of each resource it creates, then a line that starts with `Configuration applied successfully`. *Bypass Cache* requires Application Accelerator, and the CLI turns it on for the application that uses it. Running it again with the same file updates the same resources. For every key, refer to [azion.config.js](/en/documentation/devtools/cli/azion-config-js/) and [azion config](/en/documentation/devtools/cli/config/).

**API**

The API creates each resource on its own. An application needs only `name`, and its cache settings and rules are sub-resources:

1. Create the connector with a `POST` request to `https://api.azion.com/v4/workspace/connectors`.
2. Create the application with a `POST` request to `https://api.azion.com/v4/workspace/applications`.
3. Create the rule with a `POST` request to `/v4/workspace/applications/<application-id>/request_rules`.
4. Create the workload with a `POST` request to `/v4/workspace/workloads`.
5. Create its deployment with a `POST` request to `/v4/workspace/workloads/<workload-id>/deployments`.

Each response carries `"state": "pending"` and the `id` that the next request passes. For every body, refer to [Applications quickstart](/en/documentation/platform/applications/quickstart/).

### Deploy a project from its repository

A Compute project or a framework site deploys from its repository. Azion supports 19 frameworks and 5 generic presets. Azion Console imports a repository with one of six presets, and the Azion CLI asks for the preset in a picker.

**Console**

To import the repository in Azion Console:

1. **Open the create dialog**

   Access [Azion Console](https://console.azion.com/) > **Create**. The **New** dialog opens.

2. **Open the Import from GitHub tab**

   In the **New** dialog, select the **Import from GitHub** tab, then select the card.

3. **Connect your GitHub account**

   In the **GitHub Connection** section, select **Connect with GitHub**, and install the Azion GitHub App for the repository.

4. **Select the repository**

   In **Git Scope**, select the GitHub account. In **Repository**, select the repository to move.

5. **Select the preset**

   In **Preset**, select the framework: *Next.js*, *Angular*, *Astro*, *Hexo*, *React*, or *Vue*.

6. **Enter the install command**

   In **Install Command**, enter the command that installs the project. For example: `npm install`.

7. **Select Deploy**

Azion builds the project and creates its application and workload. For every field of the page, refer to [Import a project from GitHub](/en/documentation/guides/application-development/automation/import-an-existing-project-from-github/).

**CLI**

In the root of the project, link it to Azion, and select the preset when the CLI asks for it:

```bash
azion link
```

Then deploy. `azion deploy` needs the project settings that `azion link` writes:

```bash
azion deploy
```

The CLI builds the project and deploys it to Azion. To set the preset in code, put it in `build.preset` of `azion.config.js`:

```javascript
import { defineConfig } from '@aziontech/config'

export default defineConfig({
  build: {
    preset: 'javascript',
    polyfills: true
  }
})
```

The preset of a Next.js project is `next`. The `azion` package that older samples import is deprecated: import `defineConfig` from `@aziontech/config`. Install that package in the project first, with `npm install -D @aziontech/config`. Without it, the CLI fails with `Failed to load configuration file`. For the commands, refer to [Azion CLI quickstart](/en/documentation/devtools/cli/quickstart/) and [azion deploy](/en/documentation/devtools/cli/deploy/).

**API**

The API creates the application, its rules, and the workload one resource at a time. To build that chain, refer to [Applications quickstart](/en/documentation/platform/applications/quickstart/).

### Check the deployment

The deployment answers on a workload domain that Azion assigns under `map.azionedge.net`, such as `xxxxxxxxxx.map.azionedge.net`. Before you move any production domain, send a request to the root path, with that domain in place of `<your-workload-domain>`:

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

The response carries the status, the headers, and the body the service returns for `/`. Send the same request to each critical route, such as a `/health` endpoint. A first `200` proves little on its own: cache behavior, redirects, headers, and origin routing can still differ from Fastly.

If the build fails on Azion, compare the preset with the framework of the project, and check `build.preset`, `build.entry`, and `build.bundler` in `azion.config.js`. The `build` block has no command field.

---

## Move environment variables

Environment variables hold API keys, origin credentials, feature flags, third-party endpoints, and operational settings. A variable that does not reach Azion breaks the service at run time, even when the deployment succeeds.

| Aspect             | Fastly                                       | Azion                                                                     |
| ------------------ | -------------------------------------------- | ------------------------------------------------------------------------- |
| Access from code   | Language-specific environment or secret APIs | `Azion.env.get('VARIABLE')`, or `process.env.VARIABLE`                    |
| Configuration data | Dictionaries, Config Store, and Secret Store | Environment variables, KV Store, and the arguments of a function instance |
| Scope              | The service or the Compute package           | The account, up to 100 variables                                          |

Look for variables in `fastly.toml` and the Compute project, the dictionaries and Config Store entries, the Secret Store, the CI/CD settings, and the source code. On Azion, each variable has a key, a value, and a flag that marks it as a secret. Data that changes per request, such as a routing table, fits KV Store better. A value that belongs to one function instance goes in its arguments, which the code reads from `ctx.args`.

**Console**

To create the variables in Azion Console, open the **Variables** page of the **Account** menu, and create each variable with its key and its value. Turn a variable that holds a credential into a secret.

**CLI**

To create each variable with the Azion CLI:

```bash
azion create variables --key API_KEY --value <your-value> --secret false
```

The CLI answers with the UUID of the new variable:

```text
Created variable with UUID 00000000-0000-0000-0000-000000000001
```

Set `--secret true` for a credential. To list the variables, run `azion list variables`. For the other commands, refer to [variables](/en/documentation/devtools/cli/resources/variables/).

**API**

The Azion CLI and Azion Console create the variables. For the interfaces that create, list, and change a variable, refer to [Environment variables](/en/documentation/platform/functions/environment-variables/).

A deployed function reads a variable with `Azion.env.get()`, and `process.env` keeps working:

```javascript diff
-// Before: Fastly Compute, where configuration access varies by runtime
-const apiKey = process.env.API_KEY;
 
+// After: Azion
+const apiKey = Azion.env.get('API_KEY');
```

Under `azion dev`, a function reads the project `.env` file instead of the account variables, or the whole shell environment when there is no `.env` file. If a function reports a variable as not found, confirm that the variable exists on the account and that the code reads it with `Azion.env.get()`. For the instance arguments, refer to [Functions instances](/en/documentation/platform/applications/functions-instances/).

> **Caution**
>
> Keep secrets in approved systems, and limit access to the processes that need them. Never copy a secret into local notes, tickets, chat messages, or temporary documents.

---

## Move Compute services to Functions

Compute services carry request handling, API orchestration, personalization, authentication, and integration logic. On Azion, this code runs in [Functions](/en/documentation/platform/functions/). A function holds the code, a [function instance](/en/documentation/platform/applications/functions-instances/) runs it on an application, and a rule decides which requests reach it.

| Aspect          | Fastly Compute                                    | Azion Functions                                        |
| --------------- | ------------------------------------------------- | ------------------------------------------------------ |
| Deployment unit | Compute package                                   | A function and its function instance                   |
| Runtime model   | WebAssembly-based runtimes                        | Functions generated as JavaScript or WebAssembly       |
| Handler         | Runtime-specific request handler                  | `export default { async fetch(request, env, ctx) {} }` |
| Variables       | Runtime-specific environment APIs                 | `Azion.env.get('VARIABLE')`                            |
| Platform data   | Edge Data Storage, Config Store, and Secret Store | KV Store, Object Storage, and environment variables    |

On a deployed function, `env` is an empty object, and `ctx` carries `args` and `waitUntil`. Azion also runs the Service Worker shape, `addEventListener('fetch', ...)`, but the ES Modules object is the recommended handler:

```javascript diff
-// Before: Fastly Compute, JavaScript
-addEventListener('fetch', (event) => {
-  event.respondWith(handleRequest(event.request));
-});
-
-async function handleRequest(request) {
-  const url = new URL(request.url);
-  return new Response('Path: ' + url.pathname, {
-    status: 200,
-    headers: { 'Content-Type': 'text/plain' }
-  });
-}
 
+// After: Azion
+export default {
+  async fetch(request, env, ctx) {
+    const url = new URL(request.url);
+    return new Response('Path: ' + url.pathname, {
+      status: 200,
+      headers: { 'Content-Type': 'text/plain' }
+    });
+  }
+};
```

For a Compute project in another language, `azion list presets` includes `rustwasm` for Rust and `emscripten` for C++. A language with no preset, such as Go, is rewritten in JavaScript. Either way, code that calls Fastly platform APIs changes. Separate the business logic from those calls, and map each area to its Azion API:

| Code area          | On Azion                                                |
| ------------------ | ------------------------------------------------------- |
| Request parsing    | The standard `Request` and `URL` APIs                   |
| Response creation  | The standard `Response` API                             |
| Headers            | `request.headers` and the response headers              |
| Environment values | `Azion.env.get()`                                       |
| Key-value data     | `Azion.KV`, as Move Edge Data Storage to KV Store shows |
| External services  | `fetch()`                                               |

For the presets, refer to [Azion CLI presets](/en/documentation/devtools/cli/resources/presets/). For the handler shapes, refer to [Handlers](/en/documentation/devtools/runtime/api-reference/handlers/), and for the runtime APIs, to [Web APIs](/en/documentation/devtools/runtime/api-reference/javascript/).

---

## Translate VCL and service conditions

Business-critical behavior often lives in VCL snippets, conditions, and request or response settings. Most of it moves to [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine/). Keep Functions for logic that needs code, such as external lookups, signed tokens, or custom algorithms.

| Logic            | Fastly                         | Azion                                                                               |
| ---------------- | ------------------------------ | ----------------------------------------------------------------------------------- |
| Simple redirects | VCL or request settings        | A rule with *Redirect To (301 Moved Permanently)* or *Redirect To (302 Found)*      |
| Header changes   | VCL or response settings       | A rule with *Add Request Header*, *Filter Request Header*, or *Add Response Header* |
| Origin selection | VCL backend selection          | A rule with *Set Connector*, which names a connector                                |
| Pattern matching | Conditions and VCL expressions | Rule criteria, with the `matches` operator for a regular expression                 |
| Complex logic    | Custom VCL or Compute          | Functions                                                                           |

Convert each condition on the path to a rule criterion. Move each header that a response setting adds to a Response Phase rule, and each request header normalization to a Request Phase rule. Rebuild the backend selection as one connector per origin, chosen by *Set Connector* in the rule that matches. In `azion.config.js`, these behaviors are `redirect_to_301`, `add_request_header`, `filter_request_header`, `add_response_header`, and `set_connector`. To keep a path out of cache, add *Bypass Cache*, `bypass_cache`, to its rule.

When the application answers differently from the Fastly service, compare the active service version with the rules and the connectors of the application. The usual causes are a rule, a VCL branch, a cache key, or an origin selection that was not mapped. To see which rules run on a request, refer to [Debug rules](/en/documentation/guides/application-development/getting-started/debug-rules/).

---

## Recreate redirects and rewrites

Redirects protect search rankings, campaign links, backlinks, and bookmarks. When a redirect breaks, the result is lost traffic and broken user journeys. Azion keeps redirects in the rules of the application, which you write in Azion Console, the API, or `azion.config.js`.

| Aspect           | Fastly                         | Azion                                                                                                                  |
| ---------------- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Configuration    | VCL or request settings        | [Rules Engine for Applications](/en/documentation/platform/applications/rules-engine/)                                 |
| Pattern matching | Conditions and VCL expressions | Regular expressions with the `matches` operator, such as `^/.*$`, plus `starts_with` and `is_equal`                    |
| Captured values  | `re.group.1`                   | `%{name[index]}`, such as `%{capture[1]}` and `%{capture[2]}`, from a *Capture Match Groups* behavior in the same rule |

A VCL redirect becomes a rule with a criterion, a capture, and a redirect:

```text
# Fastly VCL
if (req.url.path ~ "^/old/(.*)$") {
  set req.http.Location = "/new/" + re.group.1;
  error 750 "Moved Permanently";
}

# Azion rule
Criteria:  ${uri} matches ^/old/(.*)$
Behavior:  Capture Match Groups (array capture, subject ${uri}, regex ^/old/(.*)$)
Behavior:  Redirect To (301 Moved Permanently): /new/%{capture[1]}
```

The criteria of a rule select the requests, but they capture nothing. To reuse part of the path in the target, add a *Capture Match Groups* behavior before the redirect, in the same rule. *Capture Match Groups* requires [Application Accelerator](/en/documentation/platform/applications/application-accelerator/settings/) on the application. The array is local, so only the rule that captures it can read it.

**Console**

To create the redirect in Azion Console:

1. **Open the application**

   Access [Azion Console](https://console.azion.com/) > **Applications**, then select the application.

2. **Go to the Rules Engine tab**

3. **Select + Rule**

4. **Name the rule**

   Enter a name such as `old-path-redirect`, and select **Request Phase**.

5. **Set the criteria**

   Under **Criteria**, select `${uri}` and the *matches* operator. As the argument, enter `^/old/(.*)$`.

6. **Add the Capture Match Groups behavior**

   Under **Behaviors**, select *Capture Match Groups*. Enter `capture` as the array name, `${uri}` as the **Subject**, and `^/old/(.*)$` as the **Regex**.

7. **Add the redirect**

   Add a second behavior, *Redirect To (301 Moved Permanently)*, with `/new/%{capture[1]}` as the argument.

8. **Select Save**

The rule appears in the list of request rules.

**CLI**

To create the redirect with the Azion CLI, add the rule to the `rules` of the application in `azion.config.js`. Give it the behaviors `capture_match_groups` and `redirect_to_301`, then run `azion deploy`. For the fields of a rule, refer to [azion.config.js](/en/documentation/devtools/cli/azion-config-js/).

**API**

To create the redirect, send a `POST` request to the request rules of the application:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/applications/<application-id>/request_rules \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "old-path-redirect",
  "criteria": [[{ "conditional": "if", "variable": "${uri}", "operator": "matches", "argument": "^/old/(.*)$" }]],
  "behaviors": [
    { "type": "capture_match_groups", "attributes": { "subject": "${uri}", "regex": "^/old/(.*)$", "captured_array": "capture" } },
    { "type": "redirect_to_301", "attributes": { "value": "/new/%{capture[1]}" } }
  ]
}'
```

The response carries `"state": "pending"` and the rule with its `id`.

To check the redirect, request an old path:

```bash
curl -I https://<your-workload-domain>/old/post
```

The response carries `301 Moved Permanently` and a `location` header that ends in `/new/post`. A new rule can take a few minutes to propagate. On an unexpected response, wait and retry before diagnosing. If the redirect still fails, test the regular expression and its capture groups against the old paths.

To serve content from another path without a redirect, use *Rewrite Request* with the same captures. For SEO-sensitive moves, prefer permanent redirects, avoid redirect chains, keep canonical paths consistent, and test the paths with and without a trailing slash.

---

## Recreate custom headers

Headers control caching, security, and browser behavior. On Fastly, VCL and response settings change them. Azion adds them with rules in either phase: *Add Request Header* changes the request sent to the origin, and *Add Response Header* changes the response sent to the user.

| Aspect         | Fastly                    | Azion                                                                          |
| -------------- | ------------------------- | ------------------------------------------------------------------------------ |
| Configuration  | VCL and response settings | Rules Engine for Applications, in Azion Console, the API, or `azion.config.js` |
| Phases         | —                         | Request and response                                                           |
| Dynamic values | —                         | Rule variables, such as `${uri}`, `${host}`, or `${geoip_city_country_code}`   |

This `azion.config.js` adds two security headers to every response of the application:

```javascript
import { defineConfig } from '@aziontech/config'

export default defineConfig({
  applications: [{
    name: 'my-app',
    rules: {
      response: [{
        name: 'Security Headers',
        active: true,
        criteria: [[{
          variable: '${uri}',
          conditional: 'if',
          operator: 'starts_with',
          argument: '/'
        }]],
        behaviors: [
          { type: 'add_response_header', attributes: { value: 'X-Frame-Options: SAMEORIGIN' } },
          { type: 'add_response_header', attributes: { value: 'X-Content-Type-Options: nosniff' } }
        ]
      }]
    }
  }]
})
```

The value takes the form `Name: value`, and the Console refuses any other shape with `Header must follow the header-name: value format`. A workload must serve the application for the headers to reach a domain. Run `azion deploy`, then check a response:

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

The response carries `x-frame-options: SAMEORIGIN` and `x-content-type-options: nosniff`. A header that depends on logic no rule can express goes in a function.

---

## Recreate cache settings

On Fastly, the cache depends on service settings, VCL, surrogate keys, cache overrides, and the purge APIs. On Azion, a [cache setting](/en/documentation/platform/applications/cache/cache-settings/) holds how long a response stays in cache and what makes two requests share one copy. A rule with *Set Cache Policy* applies the setting to the requests it matches. The rule selects a setting, and the setting holds the TTL and the cache key.

| Aspect        | Fastly                                     | Azion                                                                                                                         |
| ------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- |
| Cache policy  | Service settings, VCL, and cache overrides | Cache settings, applied by Rules Engine                                                                                       |
| Cache key     | VCL and cache configuration                | Customizable in the cache setting, with **Cache vary by** controls that require Application Accelerator                       |
| TTL           | —                                          | **Max Age** of each cache setting, from 0 to 31,536,000 seconds                                                               |
| Shielding     | A shield location                          | [Tiered Cache](/en/documentation/platform/applications/cache/tiered-cache/), a second cache layer turned on per cache setting |
| Purge         | URL, surrogate key, and service purge      | URL, cache key, and wildcard                                                                                                  |
| Stale content | VCL and stale settings                     | **Stale cache**, which serves an expired copy when revalidation fails                                                         |

**Max Age** defaults to 60 seconds. A value below 60 requires Application Accelerator, and a cache setting with Tiered Cache on needs at least 3 seconds and *Override cache behavior*. **Stale cache** honors the `stale-while-revalidate` the origin sends, or keeps a 300-second window under *Override cache behavior*. It is on by default in Azion Console and off in the API and the CLI.

**Console**

To create the cache setting in Azion Console:

1. **Open the application**

   Access [Azion Console](https://console.azion.com/) > **Applications**, then select the application.

2. **Go to the Cache Settings tab**

3. **Select + Cache**

4. **Name the cache setting**

   In **Name**, enter `default-cache`.

5. **Keep Override cache behavior selected**

   Under **Cache**, keep *Override cache behavior* selected, so **Max Age** replaces the TTL the origin sends.

6. **Set Max Age**

   In **Max Age**, enter the TTL in seconds. For example: `86400`.

7. **(Optional) Turn on Tiered Cache**

   Turn on **Tiered Cache**, and select the **Tiered Cache Region**. Use it where the Fastly service used shielding.

8. **Select Save**

The new setting appears in the **Cache Settings** list. To apply it, create a Request Phase rule in the **Rules Engine** tab. Use criteria such as `${uri}` *starts with* `/static/`, and the behavior **Set Cache Policy** set to `default-cache`.

**CLI**

The CLI flags cannot set the cache TTL, the cache behavior, or Tiered Cache. Send the full cache setting body from a file with `--file`, as the [Cache quickstart](/en/documentation/platform/applications/cache/quickstart/) shows.

**API**

To create the cache setting, send a `POST` request to the cache settings of the application:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/applications/<application-id>/cache_settings \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "default-cache",
  "browser_cache": { "behavior": "override", "max_age": 3600 },
  "modules": {
    "cache": {
      "behavior": "override",
      "max_age": 86400,
      "stale_cache": { "enabled": true },
      "tiered_cache": { "enabled": true, "topology": "nearest-region" }
    }
  }
}'
```

Cache settings are sub-resources of an application. To change one, send the same fields in a `PATCH` request to `/v4/workspace/applications/<application-id>/cache_settings/<cache-setting-id>`. Then apply the setting with a request rule:

```json
{
  "name": "apply-default-cache",
  "active": true,
  "criteria": [[{ "variable": "${uri}", "operator": "starts_with", "conditional": "if", "argument": "/static/" }]],
  "behaviors": [{ "type": "set_cache_policy", "attributes": { "value": <cache-setting-id> } }]
}
```

The rule applies `default-cache` to every request whose path starts with `/static/`.

To vary the cache by query string, cookie, or device, use the **Cache vary by** controls of the cache setting: **Cache vary by Query String**, **Cache vary by Cookies**, and **Cache vary by Devices**. They require Application Accelerator on the application. **Cache vary by Devices** with the *Allowlist* behavior keeps one copy for each device group you select. The groups come from the **Device Groups** tab of the application. For the controls, refer to [Cache variation](/en/documentation/platform/applications/application-accelerator/cache-variation/).

When fewer requests come from cache after the move, compare the cache setting with the Fastly service. Check the **Cache vary by** controls against the cache keys, **Max Age** against the TTLs, and Tiered Cache against the shielding.

Azion has no surrogate key purge. Replace a `fastly purge` with the purge endpoint of its type:

```bash diff
-# Before: Fastly purge by URL
-fastly purge --service-id SERVICE_ID https://www.example.com/images/logo.png
 
+# After: Azion purge by URL
+curl --request POST \
+  --url https://api.azion.com/v4/workspace/purge/url \
+  --header 'Authorization: Token [TOKEN VALUE]' \
+  --header 'Content-Type: application/json' \
+  --data '{
+  "items": ["https://www.example.com/images/logo.png"],
+  "layer": "cache"
+}'
 
+# After: Azion purge by wildcard
+curl --request POST \
+  --url https://api.azion.com/v4/workspace/purge/wildcard \
+  --header 'Authorization: Token [TOKEN VALUE]' \
+  --header 'Content-Type: application/json' \
+  --data '{
+  "items": ["https://www.example.com/images/*"],
+  "layer": "cache"
+}'
```

Purge is a top-level endpoint, not nested under applications. A URL purge takes up to 50 items, and a wildcard purge takes one expression. Only a cache key purge, at `/v4/workspace/purge/cachekey`, reaches Tiered Cache with `"layer": "tiered_cache"`. A URL or wildcard purge with that layer fails with `30001`.

The Azion CLI purges the same way:

```bash
azion purge --urls 'www.example.com/images/logo.png'
```

The command answers `Purge carried out successfully`. In a script, always pass `--urls`, `--cachekey`, or `--wildcard`: without a flag and without a terminal, the command fails with `Error: EOF`. For the purge types, refer to [Real-Time Purge](/en/documentation/platform/applications/cache/real-time-purge/) and [azion purge](/en/documentation/devtools/cli/purge/).

---

## Balance traffic across origins

On Azion, [Load Balancer](/en/documentation/platform/connectors/load-balancer/balancing-methods/) is a module of a connector, not a separate resource. One connector of type `http` holds every origin as an address, up to 15 addresses with Load Balancer on, and one address without it. A rule with *Set Connector* sends the requests of the application to the connector.

| Aspect            | Fastly                  | Azion Load Balancer                                                                                                 |
| ----------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
| Origin resource   | Backend or host         | Addresses of one connector                                                                                          |
| Weighted origins  | Backend selection logic | A **Weight** for each address                                                                                       |
| Balancing methods | —                       | *Round Robin*, *Least Connections*, and *IP Hash*, which are `round_robin`, `least_conn`, and `ip_hash` in the API  |
| Health checks     | Backend health checks   | None. Failover is passive: **Max Retries** and the timeouts handle a failed connection                              |
| Failover          | VCL or service settings | Several *Primary* addresses with weights, and *Backup* addresses that receive traffic only when every primary fails |
| Shielding         | Shielding               | Tiered Cache, in the cache setting. Origin Shield on the connector restricts the origin to Azion's addresses        |

No method steers by location or by measured response time, and there is no cookie affinity: *IP Hash* maps each client IP address to one address. Each address has a **Weight** from 1 to 100, which sets its share of the traffic. *IP Hash* refuses *Backup* addresses, with `28005` in the API. **Max Retries** takes 0 to 20, **Connection Timeout** 1 to 300 seconds, and **Read/Write Timeout** 1 to 600 seconds. These fields exist only with Load Balancer on. When you turn it on in Azion Console, the form fills in *Round Robin*, `3`, `30`, and `60`. The API defaults are `0`, `60`, and `120`.

**Console**

To turn on Load Balancer in Azion Console:

1. **Open the Connectors page**

   Access [Azion Console](https://console.azion.com/) > **Connectors**.

2. **Open the connector of the origin**

3. **Turn on Load Balancer**

   In **Modules**, turn on **Load Balancer**.

4. **Select the balancing method**

   In **Load Balancer Configuration**, set **Method** to *Round Robin*, *Least Connections*, or *IP Hash*.

5. **Set the first address**

   Under **Address Management**, on the existing address, set **Server Role** and **Weight**.

6. **Select Add Address**

7. **Enter the next origin**

   In the new **Address**, enter the host of the origin, without a protocol or a port. Then set its **Server Role** and **Weight**.

8. **Select Save**

The Console shows `Connector has been updated`. The connector has Load Balancer on and one address for each origin.

**CLI**

The update command needs the full connector body. Put it in a JSON file and send it with `--file`, as the [Load Balancer quickstart](/en/documentation/platform/connectors/load-balancer/quickstart/) shows.

**API**

To turn on Load Balancer, send a `PATCH` request to the connector. The address carries `server_role` and `weight`, and the method, the retries, and the timeouts sit in `attributes.modules.load_balancer.config`:

```bash
curl --request PATCH \
  --url https://api.azion.com/v4/workspace/connectors/<connector-id> \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "attributes": {
    "addresses": [
      { "address": "origin1.example.com", "modules": { "load_balancer": { "server_role": "primary", "weight": 3 } } },
      { "address": "origin2.example.com", "modules": { "load_balancer": { "server_role": "primary", "weight": 1 } } }
    ],
    "modules": {
      "load_balancer": {
        "enabled": true,
        "config": { "method": "round_robin", "max_retries": 3, "connection_timeout": 10, "read_write_timeout": 30 }
      }
    }
  }
}'
```

The response carries `"state": "pending"`. Two addresses with Load Balancer off are refused with `28004`, and `"enabled": true` with an empty `config` is refused with `28014`.

For the connector fields, refer to [Connector settings](/en/documentation/platform/connectors/settings/) and [Connectors](/en/documentation/platform/connectors/).

---

## Serve optimized images

[Image Processor](/en/documentation/platform/applications/image-processor/quickstart/) resizes, crops, converts, and filters images on request. It stores nothing: it reads the source image from the origin of the application. That origin can be an HTTP server or an Object Storage bucket behind a connector.

| Aspect          | Fastly Image Optimizer                       | Azion Image Processor                                           |
| --------------- | -------------------------------------------- | --------------------------------------------------------------- |
| Transformations | Real-time image transformations              | Resize, crop, fit, format, quality, rotate, fill, and watermark |
| URL format      | Query parameters or configured image options | `/image.png?ims=<OPTIONS>`                                      |
| Formats         | Optimized image formats                      | WebP, AVIF, JPEG, GIF, and PNG                                  |
| Delivery path   | The Fastly service                           | An application with Image Processor on                          |
| Image source    | The origin or object storage                 | The origin of the application, such as Object Storage           |

Image Processor works in two steps: turn on the module on the application, then create a rule with the *Optimize Images* behavior. A request that no such rule matches is delivered unprocessed. Image Processor has no default quality or format setting: the URL of each request carries them.

**Console**

To turn on Image Processor in Azion Console:

1. **Open the application**

   Access [Azion Console](https://console.azion.com/) > **Applications**, then select the application.

2. **Turn on Image Processor**

   In the **Main Settings** tab, under **Modules**, turn on **Image Processor**. For more information, refer to [Main Settings](/en/documentation/platform/applications/main-settings/).

3. **Select Save**

4. **Add the Optimize Images rule**

   In the **Rules Engine** tab, create a Request Phase rule with `${uri}` *matches* `\.(jpg|jpeg|gif|bmp|png|ico|webp|avif)` and the *Optimize Images* behavior.

Image requests that match the rule are now processed.

**CLI**

To turn on Image Processor with the Azion CLI, follow the CLI panel of the [Image Processor quickstart](/en/documentation/platform/applications/image-processor/quickstart/).

**API**

To turn on the module, send a `PATCH` request to the application. The `image_processor` switch sits under `modules`, at the application level:

```bash
curl --request PATCH \
  --url https://api.azion.com/v4/workspace/applications/<application-id> \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "modules": {
    "image_processor": { "enabled": true }
  }
}'
```

Then create a request rule with the `optimize_images` behavior, as the [Image Processor quickstart](/en/documentation/platform/applications/image-processor/quickstart/) shows.

Image Processor reads the transformation from the `ims` query parameter:

```text
# Fastly Image Optimizer
https://www.example.com/image.jpg?width=400&quality=85

# Azion
https://www.example.com/image.jpg?ims=400x/filters:quality(85)
```

| Syntax                                | Result                                                             | Example                                   |
| ------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------- |
| `?ims=WxH`                            | Resizes to the width and height, cropping to fit when both are set | `?ims=400x300`                            |
| `?ims=Wx`                             | Resizes to the width, with the height in proportion                | `?ims=400x`                               |
| `?ims=xH`                             | Resizes to the height, with the width in proportion                | `?ims=x300`                               |
| `?ims=fit-in/WxH`                     | Fits the image inside the dimensions, never enlarging it           | `?ims=fit-in/400x300`                     |
| `?ims=fit-in/WxH/filters:fill(Color)` | Fits the image and fills the rest of the canvas with a color       | `?ims=fit-in/400x300/filters:fill(white)` |

Image Processor converts to WebP when the `Accept` header of the client allows it. AVIF needs `?ims=filters:format(avif)` and a client that accepts `image/avif`. To cache one copy for each `ims` value, turn on Application Accelerator and vary the cache by query string. Check representative image URLs on the workload domain before the cutover. For every parameter, refer to [URL parameters](/en/documentation/platform/applications/image-processor/url-parameters/).

---

## Move streaming, WebSockets, and Fanout

Streaming, Media Shield, WebSocket, and Fanout traffic is bandwidth-heavy or long-lived. Move each one by its traffic model: cacheable media, long-lived connections, or publish-subscribe messaging.

| Fastly capability                 | On Azion                                                                                                                                                                                                              |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Streaming Delivery                | An application with Cache, and Object Storage for the media files                                                                                                                                                     |
| Media Shield for Live and for VOD | Tiered Cache in the cache setting, which cuts the requests that reach the origin                                                                                                                                      |
| WebSockets                        | [WebSocket Proxy](/en/documentation/platform/applications/websocket/) when the origin is the WebSocket server, or the [WebSocket API](/en/documentation/devtools/runtime/api-reference/websocket/) when a function is |
| Fanout                            | Functions with the WebSocket API, plus an external publish-subscribe service when needed                                                                                                                              |

A live stream that Azion ingests reaches it through a connector of type `live_ingest`. It takes the stream over RTMP and converts it to HLS, and an application delivers it to viewers. For the flow, refer to [Ingestion and delivery](/en/documentation/platform/connectors/live-ingest/ingestion-and-delivery/).

### Carry WebSocket connections

WebSocket Proxy and the WebSocket API are available with Business, Enterprise, or Mission-Critical support, or with a Reserved Capacity or Saving Plan contract. To request access, contact [Technical Support](/en/documentation/support/).

An application with WebSocket Proxy treats every request that carries both upgrade headers as a WebSocket connection, whatever its path:

| Header       | Value       |
| ------------ | ----------- |
| `Upgrade`    | `websocket` |
| `Connection` | `upgrade`   |

Before the cutover:

1. Confirm that the client and the origin both support WebSocket natively, and that the origin accepts `Upgrade: websocket` and `Connection: upgrade`.
2. Send the upgrade headers only on the requests that open a connection, and keep cache and image rules off those paths.
3. Test an upgrade: a connection that opens returns `101 Switching Protocols`. Any other status, even a `2xx` or a `3xx`, means the upgrade did not complete.
4. Make the client reopen a closed connection. Azion recycles keepalive connections about every 15 minutes, which can close an active WebSocket connection.

The firewall rules can inspect a WebSocket request before the upgrade completes. After the cutover, watch the `websocket.connection.accepted`, `message.sent`, and `message.received` events in Real-Time Metrics and Data Stream.

### Serve WebSockets from a function

When a function is the WebSocket server, `upgradeWebSocket(request)` accepts the upgrade and returns a `response` and a `socket`. The runtime also provides helpers that broadcast a message to every connected client, which covers a Fanout pattern:

```javascript
export default {
  async fetch(request, env, ctx) {
    if (request.headers.get('upgrade') === 'websocket') {
      const { response, socket } = upgradeWebSocket(request);

      socket.addEventListener('message', (event) => {
        socket.send('received: ' + event.data);
      });

      return response;
    }

    return new Response('Expected WebSocket upgrade', { status: 400 });
  }
};
```

A request without the `upgrade` header receives `400` with `Expected WebSocket upgrade`. Under `azion dev`, `upgradeWebSocket` is not defined, so test the function deployed. For the API, refer to [WebSocket](/en/documentation/devtools/runtime/api-reference/websocket/).

---

## Move AI Accelerator patterns

Fastly AI Accelerator caches and accelerates traffic to generative AI APIs. On Azion, the destination depends on the goal: running inference, controlling traffic, observing it, or caching responses.

| Aspect          | Fastly AI Accelerator                  | Azion                                                                                                |
| --------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| Primary pattern | Semantic caching for AI API traffic    | AI Inference, Functions, and Cache                                                                   |
| Request control | Service and Compute logic              | Rules Engine and Functions                                                                           |
| Observability   | Logging and inspectors                 | Real-Time Events, Data Stream, and Real-Time Metrics                                                 |
| Model execution | External AI APIs accelerated by Fastly | [AI Inference](/en/documentation/platform/ai-inference/), or external AI APIs called from a function |

A function can keep calling the external AI provider, with the key in an environment variable:

```javascript
export default {
  async fetch(request, env, ctx) {
    const prompt = await request.text();

    return fetch('https://ai-provider.example.com/v1/chat/completions', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + Azion.env.get('AI_API_KEY')
      },
      body: JSON.stringify({ prompt })
    });
  }
};
```

The function returns the answer of the provider to the client. To run the model on Azion instead, call it by its ID with `Azion.AI.run()`. AI Inference runs a catalog of open-source [models](/en/documentation/platform/ai-inference/models/), and the call needs no credential:

```javascript
const modelResponse = await Azion.AI.run("Qwen/Qwen3-30B-A3B-Instruct-2507-FP8", {
  "stream": false,
  "messages": [
    { "role": "system", "content": "You are a helpful assistant." },
    { "role": "user", "content": "Name three European capitals." }
  ]
})
const answer = modelResponse?.choices?.[0]?.message?.content
```

Under `azion dev`, `Azion.AI` is `undefined`. Test the call on a deployed function. For the request fields, refer to [Model invocation](/en/documentation/platform/ai-inference/model-invocation/) and the [AI runtime API](/en/documentation/devtools/runtime/api-reference/ai/). For an OpenAI-compatible `/v1/chat/completions` endpoint on Azion, deploy the [AI Inference Starter Kit](/en/documentation/guides/application-development/frameworks/ai-inference-starter-kit/) template from Azion Console > **Create**.

---

## Move Edge Data Storage to KV Store

[KV Store](/en/documentation/platform/kv-store/) holds configuration, feature flags, personalization, authorization metadata, and lightweight state. Keys live in a namespace. A function opens the namespace with `Azion.KV.open()`, a runtime global that needs no import line.

| Aspect      | Fastly Edge Data Storage                   | Azion KV Store                                                                              |
| ----------- | ------------------------------------------ | ------------------------------------------------------------------------------------------- |
| Data model  | Key-value containers                       | Namespaces of key-value pairs                                                               |
| Access      | From Compute                               | From a function, through `Azion.KV`                                                         |
| Common uses | Configuration, flags, and session metadata | Session state, feature flags, routing tables, per-user preferences, and rate-limit counters |
| Consistency | Application dependent                      | A write becomes visible everywhere within 60 seconds, or within the `cacheTtl` of the read  |

```javascript diff
-// Before: Fastly, where data access varies by product and runtime
-const value = await edgeData.get('feature:checkout');
-await edgeData.put('feature:checkout', 'enabled');
 
+// After: Azion
+const kv = await Azion.KV.open('my-namespace');
+const value = await kv.get('feature:checkout');
+await kv.put('feature:checkout', 'enabled');
```

`Azion.KV.open()` is the only entry point, and it is asynchronous. The namespace must exist first, or `open()` throws `NotFound`. `get()` returns the value as text by default, and `null` for a missing key, the same as for an expired one. It also returns `json`, `arrayBuffer`, or `stream` when the second argument names that type.

KV Store has no bulk import. No Azion Console screen, CLI command, API call, or Terraform resource reads or writes keys. To move the data:

1. Export the keys, the values, the metadata, and the expiration rules from Fastly.

2. Create the namespace through the API:

   ```bash
   curl --request POST \
     --url https://api.azion.com/v4/workspace/kv/namespaces \
     --header 'Authorization: Token [TOKEN VALUE]' \
     --header 'Content-Type: application/json' \
     --data '{ "name": "fastly-migration-kv" }'
   ```

   The namespace exists, and a function can open it by name. The name takes 3 to 63 characters, is case-sensitive, and is permanent: a namespace cannot be renamed or deleted.

3. Write the keys from a deployed function with `kv.put()`.

The function writes a key at most once per second. A value takes up to 25 MB, a key up to 512 bytes, and the metadata up to 1,024 bytes. Map each expiration to the `expiration` option, in Unix seconds, or to `expirationTtl`, in seconds with a minimum of 60. Before the import, keep the key prefixes, and check the value encoding of text, JSON, and binary data. Review the code that handles a missing key. Test the read and write paths before production traffic moves. If keys are missing after the import, export them again, check the namespace name, and check the encoding.

For the namespace operations and their errors, refer to [Namespaces](/en/documentation/platform/kv-store/namespaces/). For the client, refer to [KV Store runtime API](/en/documentation/devtools/runtime/api-reference/kv-store/) and [Manage KV Store with Functions](/en/documentation/guides/application-development/data/manage-with-functions/).

---

## Move Fastly Object Storage to Object Storage

[Object Storage](/en/documentation/platform/object-storage/) holds images, documents, static assets, media, uploads, and generated files. It speaks the S3 protocol, so S3 tools and SDKs reach it with a new endpoint, a region, and a key pair.

| Aspect            | Fastly Object Storage              | Azion Object Storage                                       |
| ----------------- | ---------------------------------- | ---------------------------------------------------------- |
| Protocol          | S3-compatible workflows            | S3                                                         |
| Endpoint          | The Fastly object storage endpoint | `s3.us-east-005.azionstorage.net`                          |
| Region            | —                                  | `us-east-005`                                              |
| Object management | S3-compatible tools                | S3-compatible tools, the API, the CLI, and the runtime API |
| Delivery to users | The Fastly delivery service        | An application, through a connector of type `storage`      |

The key pair comes from an Object Storage credential. Create it in Azion Console or with a `POST` request to `https://api.azion.com/v4/workspace/storage/credentials`. The `secret_key` comes back only in the create response. To migrate, the credential needs at least `listBuckets`, `listFiles`, and `writeFiles`, plus `listAllBucketNames` to list the buckets. When access is denied, check the key pair and the endpoint first.

A Node.js migration script reaches Object Storage with the AWS SDK:

```javascript
import { S3Client } from '@aws-sdk/client-s3';

const client = new S3Client({
  region: 'us-east-005',
  endpoint: 'https://s3.us-east-005.azionstorage.net',
  credentials: {
    accessKeyId: process.env.AZION_ACCESS_KEY,
    secretAccessKey: process.env.AZION_SECRET_KEY
  }
});
```

To copy the data with [s3cmd](https://s3tools.org/s3cmd), create the destination bucket first in Azion Console, the API, or the CLI. s3cmd cannot create or remove a bucket on Azion: `s3cmd mb` and `s3cmd rb` are refused with `403 AccessDenied`. A bucket name takes 6 to 63 characters, is unique across all accounts, and cannot start with `azion`.

1. Run `s3cmd --configure -c ~/.s3cfg-azion`, and enter these values:

   - **Access Key** and **Secret Key**: the key pair of the credential.
   - **Default Region**: `us-east-005`.
   - **S3 Endpoint**: `s3.us-east-005.azionstorage.net`.
   - **DNS-style bucket+hostname:port template**: `%(bucket).s3.us-east-005.azionstorage.net`.
   - **Use HTTPS protocol**: `true`.

2. Configure a second file, `~/.s3cfg-fastly`, with the endpoint and the keys of the Fastly bucket. One s3cmd configuration holds one endpoint.

3. Download the Fastly objects:

   ```bash
   s3cmd -c ~/.s3cfg-fastly sync s3://fastly-bucket/ ./export/
   ```

4. Upload them to Azion:

   ```bash
   s3cmd -c ~/.s3cfg-azion sync ./export/ s3://azion-bucket/
   ```

The objects are in the Azion bucket. To check, list them with `s3cmd -c ~/.s3cfg-azion ls s3://azion-bucket/`. `s3cmd ls` with no bucket lists every bucket, which needs `listAllBucketNames` on the credential. `s3cmd sync s3://source-bucket/ s3://dest-bucket/` copies between two buckets of the same provider only. Other commands work the same way: `s3cmd put file.png s3://my-bucket/` uploads an object, and `s3cmd get s3://my-bucket/file.png` downloads one. rclone, the AWS CLI, and other S3 tools also work.

Azion Console refuses a single upload over 300 MB. The API and S3 tools are not bound by that limit. Use the S3 endpoint to manage objects. Serve them to users through an application and a connector, so the traffic gets cache, security, and your domain. Bucket access to workloads is `read_only`, `read_write`, or `restricted`, and a credential works independently of it. For the S3 operations, refer to [S3 compatibility](/en/documentation/platform/object-storage/s3-compatibility/) and [Use S3-compatible tools](/en/documentation/guides/application-development/data/use-s3-compatible-tools-with-object-storage/). For the buckets and their objects, refer to [Create and modify a bucket](/en/documentation/guides/application-development/data/create-and-modify-bucket/), [Upload and download objects](/en/documentation/guides/application-development/data/upload-and-download-objects-from-bucket/), and [Use a bucket as origin](/en/documentation/guides/application-development/data/use-bucket-as-origin/).

---

## Protect the application with WAF

[Web Application Firewall](/en/documentation/platform/firewall/waf/quickstart/) scores requests against eight threat families: cross-site scripting, directory traversal, evading tricks, file upload, identified attack, remote file inclusion, SQL injection, and unwanted access. A WAF rule set holds a sensitivity for each family, and a firewall rule applies it with *Set WAF*.

| Aspect             | Fastly Next-Gen WAF                       | Azion WAF                                                                                                          |
| ------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Managed protection | Next-Gen WAF rules and signals            | One managed ruleset, scored per threat family                                                                      |
| Custom logic       | WAF rules and conditions                  | [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/)                                     |
| Actions            | Allow, block, log, and challenge patterns | *Deny*, *Drop*, *Set Rate Limit*, *Set WAF*, *Run Function*, and *Set Custom Response*. There is no allow behavior |
| Modes              | —                                         | *Logging* and *Blocking*                                                                                           |
| Tuning             | Signal exclusion and thresholds           | A sensitivity for each threat family, WAF exceptions, and the **Tuning** tab                                       |
| Scope              | The service or the domain                 | A firewall bound to the workload                                                                                   |

`mode` is required on every *Set WAF* behavior, and it has no default. Start in *Logging* to check what the rule set would block, then switch to *Blocking*. Compare the false positives, the top matched rules, and the exceptions the application needs before you enforce. In *Blocking* mode, a request the rule set blocks receives `400`. To keep a legitimate request from matching, add a WAF exception or use the **Tuning** tab.

**Console**

To set up WAF in Azion Console:

1. **Create the rule set**

   Access [Azion Console](https://console.azion.com/) > **Edge Libraries** > **WAF Rules**, and create a rule set. Set the sensitivity of each family in **Threat Type Configuration**.

2. **Open the firewall**

   Go to **Secure** > **Firewalls**, and select or create the firewall.

3. **Turn on Web Application Firewall**

   In the **Main Settings** tab, under **Modules**, turn on **Web Application Firewall**, and select **Save**.

4. **Apply the rule set**

   In the **Rules Engine** tab, create a rule with the *Set WAF* behavior. In **Select a WAF**, select the rule set. In **Select a WAF mode**, select *Logging*.

5. **Bind the firewall to the workload**

   In the workload, under **Deployment Settings**, select the firewall in **Firewall**.

The firewall applies the rule set to the requests of the workload.

**CLI**

To bind a firewall with the Azion CLI, pass `--firewall-id` to the workload deployment. For the rule set and the rule, follow the [WAF quickstart](/en/documentation/platform/firewall/waf/quickstart/).

**API**

To create the rule set, send a `POST` request to the WAF endpoint:

```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 '{
  "active": true,
  "name": "Fastly Migration WAF",
  "product_version": "1.0",
  "engine_settings": {
    "engine_version": "2021-Q3",
    "type": "score",
    "attributes": {
      "rulesets": [1],
      "thresholds": [
        { "threat": "sql_injection", "sensitivity": "medium" }
      ]
    }
  }
}'
```

`rulesets` accepts only `[1]`. Then apply the rule set with a firewall rule whose behavior is `{ "type": "set_waf", "attributes": { "waf_id": <waf-rule-set-id>, "mode": "logging" } }`. The API refuses `learning` as a mode.

Convert each Fastly WAF rule to firewall criteria. Firewall variables differ from application variables: the path is `${request_uri}`, and an address range goes in a Network List matched with `${network}`.

```text
# Intent
Block requests to /admin unless the client IP is in an approved network.

# Azion criteria
${request_uri}  starts with     /admin
and
${network}      is not in list  <network-list-id>   (a Network List that holds 10.0.0.0/8)

# Azion behavior
Deny (403 Forbidden)
```

The `${network}` criterion requires Network Shield on the firewall. For the variables and operators, refer to [Rules Engine for Firewall](/en/documentation/platform/firewall/rules-engine/). For the rule set fields, refer to [WAF rule sets](/en/documentation/platform/firewall/waf/rules-set/), and for code on a firewall, to [Functions for Firewall](/en/documentation/platform/firewall/functions/).

---

## Rely on DDoS Protection

[DDoS Protection](/en/documentation/platform/workloads/#ddos-protection) is on for every workload, with nothing to create and nothing to configure. It mitigates volumetric, protocol, and application-layer attacks on layers 3, 4, 6, and 7.

| Aspect        | Fastly DDoS Protection                   | Azion DDoS Protection                                                            |
| ------------- | ---------------------------------------- | -------------------------------------------------------------------------------- |
| Activation    | Product and service configuration        | Automatic, and it cannot be turned off                                           |
| Layers        | Network and application-layer mitigation | 3, 4, 6, and 7                                                                   |
| Customization | Fastly security controls                 | Custom firewall rules                                                            |
| Visibility    | Fastly security dashboards and logs      | Real-Time Metrics, Real-Time Events, and Data Stream                             |
| Billing       | —                                        | Unmetered for layers 3 and 4. Layer 7 mitigation can generate chargeable traffic |

A firewall shows the **DDoS Protection Unmetered** switch in **Main Settings** > **Modules**, always on. In the API, `modules.ddos_protection` is read-only. DDoS Protection has no thresholds, no per-rule switches, and no alerts. For targeted mitigation, write custom rules on the firewall bound to the workload. The Security Response Team is an add-on to Enterprise and Mission-Critical support. For the attack types, refer to [Attack mitigation](/en/documentation/platform/workloads/ddos-protection/ddos-mitigation/).

Before the cutover, write down the DDoS assumptions, the support processes, and the escalation paths of the Fastly setup. Confirm the workload, the firewall, and its Network Shield configuration, and keep the rollback and escalation procedures ready during the cutover window.

[Network Shield](/en/documentation/platform/firewall/network-shield/quickstart/) is a different module of the firewall. It matches the client address against a Network List of IP addresses, CIDR ranges, ASNs, or countries, through the `${network}` criterion. Use it to block or allow sets of clients, restrict countries, or rate-limit a set of clients.

---

## Recreate bot management

[Bot Manager](/en/documentation/platform/firewall/bot-manager/quickstart/) scores each request and acts on the score. Bot Manager Lite is the Marketplace function included on every plan, and the full Bot Manager is available on Enterprise.

| Aspect           | Fastly Bot Management                | Azion Bot Manager                                                                                                    |
| ---------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Detection        | Bot signals and behavior             | Static rules, a dynamic behavioral method in the full Bot Manager, device fingerprints, and reputation Network Lists |
| Actions          | Allow, block, and challenge patterns | `allow`, `custom_html`, `deny`, `drop`, `hold_connection`, `random_delay`, and `redirect`                            |
| Lite option      | Plan-dependent Fastly features       | Bot Manager Lite, from Marketplace                                                                                   |
| Rule integration | Fastly security configuration        | A *Run Function* rule on the firewall                                                                                |

Bot Manager Lite scores a request with 26 static rules, against a `threshold` that defaults to 30, and takes the `deny` action by default. It can also check the client against reputation Network Lists. Tolerance levels belong to the dynamic rules of the full Bot Manager, which Bot Manager Lite does not have.

To set up Bot Manager Lite in Azion Console:

1. **Install the integration**

   Access [Azion Console](https://console.azion.com/) > **Marketplace**, search for **Bot Manager Lite**, and select **Install**. The installation takes effect at once.

2. **Open the firewall**

   Go to **Firewalls**, and select a firewall with the **Functions** module on.

3. **Create the function instance**

   In the **Functions Instances** tab, create an instance of Bot Manager Lite. For more information, refer to [Functions instances](/en/documentation/platform/firewall/functions-instances/). In its JSON arguments, set `threshold` and `action`.

4. **Run the function**

   In the **Rules Engine** tab, create a rule with the *Run Function* behavior and the instance.

5. **Bind the firewall to the workload**

The firewall scores the requests of the workload. For every argument, refer to [Install Bot Manager Lite](/en/documentation/guides/application-development/integrations/bot-manager-lite/) and [Bot Manager Lite](/en/documentation/platform/firewall/bot-manager/bot-manager-lite/).

To block a client by its user agent, add a firewall rule:

```text
Criteria: ${header_user_agent} matches BadBot
Behavior: Deny (403 Forbidden)
```

`${header_user_agent}` requires the WAF module on the firewall and supports only *matches* and *does not match*. The firewall has no allow behavior. To exempt a client such as `Googlebot`, add a *does not match* criterion to the deny rule, or order the rules. A client can send any user agent, so verify a good bot another way. To check the rule:

```bash
curl -A "BadBot/1.0" https://<your-domain>/
curl -A "Mozilla/5.0" https://<your-domain>/
```

The first response is `403`, with the Forbidden error page. The second, with a browser user agent, receives the normal response.

---

## Recreate rate limits

Azion limits request rates in two ways, and each covers a different part of what Fastly Edge Rate Limiting does. Use the native *Set Rate Limit* behavior of a firewall rule to cap the requests per second or per minute, per client IP address or across all clients. Use the [Upstash Rate Limiting](/en/documentation/guides/application-development/integrations/upstash-rate-limiting-integration/) integration, a rate limit with penalty run as a firewall function, for custom keys, custom windows, or a penalty period.

| Capability      | Native *Set Rate Limit*                   | Upstash Rate Limiting function                                                       |
| --------------- | ----------------------------------------- | ------------------------------------------------------------------------------------ |
| Count key       | Client IP address or global               | Any combination of request metadata, headers, and the hostname                       |
| Window          | Per second or per minute                  | Any interval in seconds or minutes, with different limits for different times of day |
| Algorithm       | Leaky bucket, counted in each data center | Fixed window, sliding window, or token bucket, counted globally                      |
| Response        | `429`, with no rate-limit header          | `429` at the limit, and `403` during a penalty                                       |
| Log-only action | None                                      | None                                                                                 |
| Requirements    | None                                      | An Upstash account and Global Database                                               |

### Use the native rate limit

A *Set Rate Limit* behavior counts the requests its rule matches. The criteria of the rule scope the limit, such as the path with `${request_uri}`, or the method, which requires WAF on the firewall. **Rate Limit Type** is *Req/s* or *Req/min*, and **Limit By** is *Client IP address* or *Global*. **Average Rate Limit** takes at least 1, and **Maximum Burst Size** takes at least 1 and applies to *Req/s* only. No behavior can follow *Set Rate Limit* in a rule. A rule whose criteria join several paths with `or` shares one count across all of them.

**Console**

To create the rate limit in Azion Console:

1. **Open the firewall**

   Access [Azion Console](https://console.azion.com/) > **Secure** > **Firewalls**, and select the firewall.

2. **Go to the Rules Engine tab**

3. **Select + Rule**

4. **Set the criteria**

   Under **Criteria**, select `${request_uri}`, the *starts with* operator, and `/api/` as the argument.

5. **Add the Set Rate Limit behavior**

   Under **Behaviors**, select *Set Rate Limit*. Set **Rate Limit Type** to *Req/s*, **Limit By** to *Client IP address*, **Average Rate Limit** to `10`, and **Maximum Burst Size** to `10`.

6. **Select Save**

The rule appears in the list of firewall rules.

**CLI**

To create the rule with the Azion CLI, save the rule body of the API panel in a file. Then pass the file with `--file` to the firewall rule command. For the commands, refer to [Firewall quickstart](/en/documentation/platform/firewall/quickstart/).

**API**

To create the rate limit, send a `POST` request to the request rules of the firewall:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/firewalls/<firewall-id>/request_rules \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "api rate limit",
  "active": true,
  "criteria": [
    [{ "variable": "${request_uri}", "conditional": "if", "operator": "starts_with", "argument": "/api/" }]
  ],
  "behaviors": [
    { "type": "set_rate_limit", "attributes": { "type": "second", "limit_by": "client_ip", "average_rate_limit": 10, "maximum_burst_size": 10 } }
  ]
}'
```

The response carries `"state": "pending"` and the rule.

A request beyond the rate and the burst receives `429`, with the error page titled Too Many Requests. For how the rate and the burst admit requests, refer to [Set Rate Limit](/en/documentation/platform/firewall/rules-engine/#set-rate-limit).

### Use the rate limit with penalty

The Upstash Rate Limiting function keeps its counters in an Upstash Global Database. It therefore counts every request across the network, not in each data center. A request during a penalty receives `403 Forbidden`. A valid request is counted, and the function returns `429 Too Many Requests` when the count reaches the limit.

To set it up in Azion Console:

1. **Install the integration**

   Access [Azion Console](https://console.azion.com/) > **Marketplace**, search for `Upstash Rate Limiting`, and select **Install**.

2. **Open the firewall**

   Go to **Firewalls**, and open a firewall with **Functions** turned on in **Modules**.

3. **Create the function instance**

   In the **Functions Instances** tab, create an instance. In **Function**, select the Upstash Rate Limiting function, and edit the JSON **Arguments**.

4. **Run the function**

   In the **Rules Engine** tab, create a rule with criteria such as `Host` *matches* `yourdomain.com`, and the *Run Function* behavior with the instance.

5. **Bind the firewall to the workload**

   Run the CLI command that creates the workload deployment with the firewall:

   ```bash
   azion create workload-deployment --workload-id <workload-id> --name <deployment-name> --application-id <application-id> --firewall-id <firewall-id> --strategy-type default --active true --current true
   ```

The function counts the requests the rule matches. These arguments set a sliding window of 2 requests per 20 seconds from midnight to noon UTC, with a 45-second penalty:

```json
{
  "upstash_redis_rest_url": "https://your-database.upstash.io",
  "upstash_redis_rest_token": "<your-upstash-token>",
  "rate_limit_prefix": "my_rate_limit",
  "rate_limit_key_metadata": ["remote_addr"],
  "rate_limit_key_header": ["x-a-custom-header"],
  "rate_limit_key_hostname": true,
  "rate_limit_repenalize": true,
  "rate_limits": [
    {
      "algorithm": "sliding_window",
      "requests": 2,
      "interval": "20 s",
      "start": "00:00",
      "end": "12:00",
      "penalty_in_seconds": 45
    }
  ]
}
```

| Argument                                             | Description                                                                                                             |
| ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `upstash_redis_rest_url`, `upstash_redis_rest_token` | The REST URL and the token of the Upstash database that stores the counters and the penalties                           |
| `rate_limit_prefix`                                  | A prefix for every key, which keeps two instances of the function apart                                                 |
| `rate_limit_key_metadata`                            | The request metadata that forms the key, such as `remote_addr`                                                          |
| `rate_limit_key_header`                              | The headers that form the key                                                                                           |
| `rate_limit_key_hostname`                            | When `true`, the hostname is part of the key                                                                            |
| `rate_limit_repenalize`                              | When `true`, every request during a penalty restarts it                                                                 |
| `rate_limits`                                        | The windows, at least one. When two windows overlap, the first one in the list applies                                  |
| `algorithm`                                          | `fixed_window`, `sliding_window`, or `token_bucket`                                                                     |
| `requests`                                           | The requests allowed in the interval                                                                                    |
| `interval`                                           | The window, as a number and `s` or `m`. For example: `"120 s"`                                                          |
| `start`, `end`                                       | The time of day the window covers, in 24-hour UTC. They default to `00:00` and `23:59`                                  |
| `penalty_in_seconds`                                 | How long a client that exceeds the limit receives `403`. Without it, the window is a plain rate limit                   |
| `max_tokens`, `refil_rate`                           | The bucket size and the refill per interval of a `token_bucket` window. `refil_rate` is the spelling the function reads |

The key joins the prefix and every value the arguments select. In this example, it is `my_rate_limit + client IP + x-a-custom-header value + hostname`, such as `my_rate_limit_127.0.0.1_Value_azion.com`. For the full setup, refer to [Install the Upstash Rate Limiting integration](/en/documentation/guides/application-development/integrations/upstash-rate-limiting-integration/). KV Store also lists rate-limit counters among its uses, for a counter a function keeps itself.

---

## Rebuild monitoring

Azion splits observability across three products. [Real-Time Metrics](/en/documentation/platform/real-time-metrics/) charts aggregates over time, and replaces Domain Inspector and Origin Inspector. [Real-Time Events](/en/documentation/platform/real-time-events/) answers queries about individual requests, and replaces Log Explorer & Insights. [Data Stream](/en/documentation/platform/data-stream/) sends the logs to external destinations, and replaces High Volume Logging.

### Real-Time Metrics

| Aspect          | Fastly Domain Inspector and Origin Inspector | Azion Real-Time Metrics                                                           |
| --------------- | -------------------------------------------- | --------------------------------------------------------------------------------- |
| Scope           | Domain and origin reporting                  | Requests, data transferred, status codes, cache offload, and average request time |
| Access          | The Fastly UI and APIs                       | Dashboards, **Copy query**, **Export CSV**, and the GraphQL API                   |
| Use cases       | Traffic trends and origin performance        | Traffic trends, cache offload, and errors from Azion or from the origin           |
| Migration focus | Dashboard and alert parity                   | Dashboards, filters, GraphQL queries, and Grafana                                 |

A metric takes up to 10 minutes to aggregate. Data stays for 2 years, except 90 days for `httpBreakdownMetrics` and 60 days for `botManagerBreakdownMetrics`. The interval is 1 minute under 2.5 days, 1 hour up to 60 days, and 1 day beyond.

The Applications dashboards chart:

- **Requests**: total requests, requests by method and by scheme, and **Average Request Time**, the average time in seconds Azion takes to process and answer a request.
- **Status Codes**: the 2XX, 3XX, 4XX, and 5XX responses, and the **Requests by Status and Upstream Status** table, which tells errors from Azion and errors from the origin apart.
- **Data Transferred**: saved and missed data and bandwidth, and **Edge Offload**.
- Cache: **Requests Offloaded**, **Saved Requests**, and **Missed Requests**.

Real-Time Metrics reports no latency, time to first byte, or origin response time. To read the cache status of the requests, filter a dashboard by **Upstream Cache Status**, whose values include `HIT`, `MISS`, `STALE`, and `EXPIRED`. To find origin errors, filter by **Upstream Status**, which is `0` when the origin did not answer.

To open the dashboards, access [Azion Console](https://console.azion.com/) > **Real-Time Metrics**. It opens on **Build** > **Applications** > **Data Transferred**, over the **Last 5 minutes**. To narrow a dashboard to one workload, add the **Domain** or **Workload** filter. To export a chart, open its **More options** menu and select **Export CSV**.

To query the same data, send a GraphQL query to `https://api.azion.com/v4/metrics/graphql`:

```graphql
query {
  workloadMetrics(limit: 3, filter: { tsRange: {begin: "2026-10-01T14:00:00", end: "2026-10-03T14:00:00"} }, aggregate: { sum: requests }, groupBy: [ts], orderBy: [ts_DESC]) {
    ts
    sum
  }
}
```

Replace the dates with a range inside the retention period. A range past it returns an empty array. `limit` takes up to 10,000 rows and defaults to 10. The `httpMetrics` dataset of older queries still works, but it is deprecated. For every field, refer to [Real-Time Metrics GraphQL fields](/en/documentation/devtools/graphql/gql-real-time-metrics-fields/) and [Real-Time Metrics quickstart](/en/documentation/platform/real-time-metrics/quickstart/). For dashboards in Grafana, refer to [Grafana plugin custom dashboards](/en/documentation/guides/platform/observability/azion-plugin-grafana-custom-dash/). To read the dashboards, refer to [Analyze metrics](/en/documentation/guides/platform/observability/analyze-metrics/).

### Real-Time Events

| Aspect     | Fastly Log Explorer & Insights       | Azion Real-Time Events                                     |
| ---------- | ------------------------------------ | ---------------------------------------------------------- |
| Access     | The Fastly UI                        | Queries in Azion Console or the GraphQL API                |
| Data model | Fastly log fields                    | Eight data sources, listed below                           |
| Querying   | Explorer filters                     | Console filters and GraphQL                                |
| Use cases  | Debugging and security investigation | Debugging, security investigation, and compliance evidence |

A record is queryable up to 30 seconds after the event, and stays for 7 days. For longer retention, use Data Stream. Real-Time Events needs no setup. Its data sources are **HTTP Requests**, **Functions**, **Functions Console**, **Image Processor**, **Tiered Cache**, **Edge DNS**, **Data Stream**, and **Activity History**. WAF results are fields of **HTTP Requests**.

To query the events in Azion Console:

1. **Open Real-Time Events**

   Access [Azion Console](https://console.azion.com/) > **Products menu** > **Observe** > **Real-Time Events**.

2. **Select the data source**

   Select the data source, such as **HTTP Requests**.

3. **Set the time and the filters**

   Set the **Time Filter**, which opens on the last 15 minutes, and add conditions in **Filter by**.

4. **Select Refresh**

The results table lists the events. Select a row to open the whole record.

To query the same data, send a GraphQL query to `https://api.azion.com/v4/events/graphql`. The `workloadEvents` dataset holds the HTTP requests:

```graphql
query {
  workloadEvents(
    limit: 100
    filter: { tsRange: { begin: "2026-10-07T00:00:00", end: "2026-10-07T01:00:00" } }
    orderBy: [ts_DESC]
  ) {
    ts
    remoteAddress
    requestUri
    status
    upstreamResponseTime
  }
}
```

Replace the dates with a range inside the last 7 days. `upstreamResponseTime` and `upstreamHeaderTime` are raw fields of `workloadEvents`, and `upstreamResponseTime` reads `-` for a response served from cache. For every field, refer to [Real-Time Events GraphQL fields](/en/documentation/devtools/graphql/gql-real-time-events-fields/) and [Investigate requests with the GraphQL API](/en/documentation/guides/platform/observability/investigate-requests-graphql-api/). To read a record, refer to [Understand Real-Time Events logs](/en/documentation/guides/platform/observability/understand-logs/).

### Data Stream

| Aspect       | Fastly High Volume Logging            | Azion Data Stream                                                 |
| ------------ | ------------------------------------- | ----------------------------------------------------------------- |
| Delivery     | Log streaming to configured endpoints | Push to an external destination                                   |
| Format       | Endpoint-specific log formats         | Templates that select the fields                                  |
| Destinations | External logging and storage services | 11 types, listed below                                            |
| Sources      | Fastly service logs                   | *Activity History*, *Applications*, *Functions*, and *WAF Events* |

Records go out in batches of 2,000 or every 60 seconds, delivered within 3 minutes. Retention is set by the destination. A stream sends one data source to one destination:

- **Storage**: Amazon S3, Azure Blob Storage, and Azion Object Storage, through the S3 type.
- **Monitoring**: Datadog, Splunk, Elasticsearch, and Azure Monitor.
- **Streaming**: AWS Kinesis Data Firehose and Apache Kafka.
- **Analytics**: Google BigQuery.
- **Security**: IBM QRadar.
- **Custom**: Standard HTTP/HTTPS POST.

A stream needs exactly one of sampling or a workload filter. Saving an active sampled stream deactivates every other stream on the account. Filtered streams coexist.

**Console**

To create a stream in Azion Console:

1. **Open Data Stream**

   Access [Azion Console](https://console.azion.com/) > **Data Stream**.

2. **Select + Stream**

3. **Select the data source**

   In **Input**, select the **Data Source**: *Activity History*, *Applications*, *Functions*, or *WAF Events*.

4. **Select the template**

   In **Render Template**, select the **Template**.

5. **Select the destination**

   In **Output**, select the **Connector**, such as *Simple Storage Service (S3)*, and enter its credentials.

6. **Turn on Active and select Save**

The stream starts sending after 1 to 2 minutes.

**CLI**

To create a stream with the Azion CLI, follow the CLI panel of the [Data Stream quickstart](/en/documentation/platform/data-stream/quickstart/).

**API**

To create a stream, send a `POST` request to `https://api.azion.com/v4/workspace/stream/streams`. The body has `inputs`, `transform`, and `outputs`:

```json
{
  "name": "activity-to-bucket",
  "active": true,
  "inputs": [
    { "type": "raw_logs", "attributes": { "data_source": "activity_history" } }
  ],
  "transform": [
    { "type": "sampling", "attributes": { "rate": 100 } },
    { "type": "render_template", "attributes": { "template": 251 } }
  ],
  "outputs": [
    {
      "type": "s3",
      "attributes": {
        "host_url": "https://s3.us-east-005.azionstorage.net",
        "bucket_name": "<your-bucket>",
        "region": "us-east-005",
        "access_key": "[ACCESS KEY]",
        "secret_key": "[SECRET KEY]",
        "object_key_prefix": "activity",
        "content_type": "plain/text"
      }
    }
  ]
}
```

A `POST` returns `201`. For an Applications stream, use `"data_source": "workloads"`. Without sampling or a workload filter, the create fails with `32002`, and with both it fails with `32007`. Templates are created at `/v4/workspace/stream/templates`.

When logs do not reach the destination, check the stream status, the destination credentials, and the variables the template selects. For an Object Storage destination, the credential needs `listAllBucketNames`, `listBuckets`, `listFiles`, and `writeFiles`, or every send fails with `503`. For the fields, refer to [Stream settings](/en/documentation/platform/data-stream/stream-settings/) and [Data sources and variables](/en/documentation/platform/data-stream/data-sources-and-variables/). For sampling, refer to [Configure sampling](/en/documentation/guides/platform/observability/configure-sampling/).

---

## Prepare the certificate

[Certificate Manager](/en/documentation/platform/workloads/certificate-manager/quickstart/) holds the certificates that workloads serve. Fastly Platform TLS, TLS Service Options, and Certainly map to it. Prepare the certificate before the domain points to Azion, so users reach the service over HTTPS from the first request.

| Area                 | Fastly TLS                            | Azion Certificate Manager                                                                                               |
| -------------------- | ------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| Managed certificates | Platform TLS and Certainly workflows  | Let's Encrypt for your own domains. Azion SAN covers the `azionedge.net` workload domain and the `azion.app` hostname   |
| Custom certificates  | Upload or API-managed certificates    | Upload of a certificate and its private key, single-domain or SAN, with RSA 2048 or P-256 keys                          |
| Automation           | The Fastly API                        | Azion Console and the Azion API                                                                                         |
| mTLS                 | Fastly TLS and security configuration | Trusted CA certificates. Azion SAN does not support mTLS                                                                |
| Association          | The service or the domain             | The workload                                                                                                            |
| Renewal              | —                                     | Let's Encrypt certificates renew from 30 days before their 90-day expiry. Custom certificates follow your own lifecycle |

Azion issues a Let's Encrypt certificate at no additional cost once you choose a Let's Encrypt preset. Pick the challenge by where DNS answers:

- **HTTP-01** needs the hostname, and every alternative name, to already point to Azion.
- **DNS-01** works before the move. At an external DNS provider, add a CNAME from `_acme-challenge.<domain>` to `<domain>.letsencrypt.azion.com`. In Edge DNS, the record is automatic.

For a move from Fastly, use DNS-01.

**Console**

To request the certificate in Azion Console:

1. **Open the workload**

   Access [Azion Console](https://console.azion.com/) > **Workloads**, then select or create the workload.

2. **Enter the domains**

   In **Domains**, enter each hostname the service answers to. Wildcards are refused.

3. **Turn on HTTPS**

   In **Protocol Settings**, turn on **HTTPS support**.

4. **Select the certificate**

   In **Digital Certificate**, select *New Let's Encrypt Certificate (DNS-01)*.

5. **Select Save**

The first attempt runs up to 5 minutes after you save, and retries follow at 5, 10, 15, 20, and 30 minutes, then on a slower schedule. The status moves from `pending` to `challenge_verification`, then to `active` or `failed`.

To upload a certificate you already have, access **Certificate Manager** and create a digital certificate with the **Server Certificate** preset. Paste the PEM certificate and the private key. Intermediate certificates go in the same field as the certificate. Then select it in the **Digital Certificate** field of the workload. For the steps, refer to [Upload a digital certificate](/en/documentation/guides/application-security/tls-and-certificates/digital-certificates/).

**CLI**

To set mTLS or the certificate of a workload with the Azion CLI, run `azion update workload --file` with the workload body. For the certificate commands, refer to [Certificate Manager quickstart](/en/documentation/platform/workloads/certificate-manager/quickstart/).

**API**

To upload a certificate, send a `POST` request to the certificates endpoint:

```bash
curl --request POST \
  --url https://api.azion.com/v4/workspace/tls/certificates \
  --header 'Accept: application/json' \
  --header 'Authorization: Token [TOKEN VALUE]' \
  --header 'Content-Type: application/json' \
  --data '{
  "name": "my-custom-cert",
  "type": "edge_certificate",
  "certificate": "-----BEGIN CERTIFICATE-----\n<certificate-body>\n-----END CERTIFICATE-----\n",
  "private_key": "-----BEGIN PRIVATE KEY-----\n<private-key-body>\n-----END PRIVATE KEY-----\n"
}'
```

The API answers `201`. Each PEM is one JSON string with `\n` line breaks. To request a Let's Encrypt certificate instead, send a `POST` request to `/v4/workspace/tls/certificates/request` with `name`, `"authority": "lets_encrypt"`, `challenge` (`http` or `dns`), `common_name`, and optional `alternative_names`.

To serve the certificate, set its ID in `tls.certificate` of the workload, or `null` for Azion SAN:

```json
{
  "name": "my-workload",
  "active": true,
  "infrastructure": 1,
  "domains": ["www.example.com"],
  "tls": { "certificate": 12345, "ciphers": 4, "minimum_version": "tls_1_2" }
}
```

`infrastructure` `1` is production. `minimum_version` takes `tls_1_0`, `tls_1_1`, `tls_1_2`, or `tls_1_3`, and defaults to `tls_1_3`. `ciphers` takes 1 to 8, and defaults to 7. Send the body to `https://api.azion.com/v4/workspace/workloads`.

If the certificate does not become active, read its `status` and `status_detail`. For HTTP-01, check that the hostname points to Azion. For DNS-01, check the `_acme-challenge` CNAME. Confirm that the hostname is in the **Domains** of the workload. Retries continue on schedule, so a fixed record issues the certificate later. For the issuance rules, refer to [Issuance and renewal](/en/documentation/platform/workloads/certificate-manager/issuance-and-renewal/), and for every certificate field, to [Certificates](/en/documentation/platform/workloads/certificate-manager/certificates/).

mTLS needs a Trusted CA certificate, and an Azion-generated certificate cannot be the Trusted CA. Sales activates mTLS on the account. Then set `mtls.enabled`, `mtls.config.certificate`, and `verification`, `enforce` or `permissive`, on the workload through the API or `azion update workload --file`. mTLS works over HTTPS only. For the steps, refer to [Configure mTLS on a workload](/en/documentation/guides/application-security/tls-and-certificates/associate-an-mtls-certificate/) and [mTLS](/en/documentation/platform/workloads/mtls/).

---

## Point the domain to the workload

The DNS change is the switch: once the domain resolves to the workload, users reach the service through Azion. It affects users, SEO, brand trust, and availability, so treat it as a controlled cutover. Before you switch, confirm that:

- The certificate is active.
- The hostname is in the **Domains** of the workload.
- The DNS records are ready.
- The critical routes and the redirects answer as expected on the workload domain. To test them under the real hostname before the switch, refer to [Test an application through the hosts file](/en/documentation/guides/application-development/getting-started/stage-applications-through-hosts-file/).
- Monitoring is ready to watch the traffic after the switch.

Point each name with the record its zone allows:

| Strategy    | Use it for                                                          | Record                                      |
| ----------- | ------------------------------------------------------------------- | ------------------------------------------- |
| CNAME       | A subdomain, keeping the current DNS provider                       | `www CNAME <your-workload-domain>`          |
| Nameservers | The apex and every other name, with Edge DNS answering for the zone | An ANAME at the apex to the workload domain |

For the nameserver strategy, create the zone in [Edge DNS](/en/documentation/platform/edge-dns/) first, with every record of the current zone. Edge DNS takes 11 record types: A, AAAA, ANAME, CAA, CNAME, DS, MX, NS, PTR, SRV, and TXT. An ANAME aliases the apex to an Azion hostname, and its TTL must be 20. Then set the nameservers of the domain at the registrar to `ns1.aziondns.net`, `ns2.aziondns.com`, and `ns3.aziondns.org`. For the zone and its records, refer to [Edge DNS quickstart](/en/documentation/platform/edge-dns/quickstart/).

To check a CNAME and the response under the real hostname:

```bash
dig www.example.com CNAME +short
curl -I https://www.example.com/
```

The `dig` answer is the workload domain, such as `xxxxxxxxxx.map.azionedge.net`, and `curl` returns the response of the workload. DNS changes take time to propagate. For the Console settings of the custom domain, refer to [Point a domain to a workload](/en/documentation/guides/platform/migration/point-domain-to-azion/). To move the nameservers, refer to [Migrate the nameservers to Azion](/en/documentation/guides/platform/migration/migrate-ns-to-azion/). For the workload settings, refer to [Workloads](/en/documentation/platform/workloads/).

---

## Next steps

- [Real-Time Metrics](/en/documentation/platform/real-time-metrics/quickstart.md): Watch requests, data transferred, status codes, and cache offload after the switch.
- [Real-Time Events](/en/documentation/platform/real-time-events/quickstart.md): Query individual requests, function logs, and WAF results from the last 7 days.
- [Web Application Firewall](/en/documentation/platform/firewall/waf/quickstart.md): Move the rule set from Logging to Blocking once the traffic is clean.
- [Azion CLI](/en/documentation/devtools/cli.md): Deploy, link, and manage the service from a terminal.
- [Azion community](https://discord.gg/azion): Ask the Azion community on Discord how others run their services on Azion.
- [Azion Support](/en/documentation/support.md): Open a ticket with the support team when the migration needs help.
