---
description: Configure the Gateway Forward action to run a Worker on permitted HTTP requests and responses.
title: Forward traffic
image: https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/forward-traffic/og.png?v=8ed88a14e05ed330
---

[Skip to content](#main-content)

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

# Forward traffic

Last updated Oct 15, 2026|Copy as Markdown| [View as Markdown](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/forward-traffic/index.md)| [Agent setup](https://developers.cloudflare.com/agent-setup/)

Public beta

The Gateway Forward action is available in public beta on all Zero Trust plans. Workers usage is subject to [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/).

The **Forward** action invokes a Worker on HTTP traffic that Gateway permits. Use it to implement request and response transformations, application-specific controls, or background tasks without operating a separate HTTP proxy.

Write your own Worker or adapt a [Worker template](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/worker-templates/). The [gateway-workers skill ↗︎](https://github.com/cloudflare/skills/blob/main/skills/gateway-workers/SKILL.md) provides guidance for coding agents working with this integration.

## How it works

Gateway evaluates Forward policies after HTTP request policies and before body policies and inspection. It invokes the Worker selected by the first matching Forward policy, in [order of precedence](https://developers.cloudflare.com/cloudflare-one/traffic-controls/order-of-enforcement/#order-of-precedence).

An HTTP request policy with a Block action prevents Worker invocation. Traffic that matches no Forward policy continues without invoking a Worker.

Gateway inspects the original body before delivering it to the Worker. A Block decision from body policies or inspection can interrupt an already-running Worker.

Worker invocation does not confirm that body inspection succeeded.

```
flowchart LR
    accTitle: Gateway Forward request and response flow
    accDescr: Gateway evaluates HTTP request policies, then Forward policies, and invokes the first matching Worker before body policies and inspection. It inspects the original body before delivering it to the Worker. A Block decision from body policies or inspection can interrupt the Worker. Gateway inspects the final response before returning it to the user.
    A["Original request"] --> B["Gateway HTTP request policies"]
    B -->|Permitted, Forward matches| C["Invoke Worker"]
    B -->|Blocked| X["Block response"]
    B -->|Original body, Forward matches| I["Gateway body policies and inspection"]
    I -->|Inspected body stream| C
    I -->|Blocked| X
    I -.->|Body Block interrupts Worker| C
    C -->|next: at most once| D["Original destination"]
    D --> C
    C -->|Final or synthetic response| E["Gateway response inspection"]
    E --> F["User"]

```

Gateway calls the named `GatewayMiddleware` entrypoint's `handle(request, context, next)` method:

- `request` is the original HTTP `Request`, including its body stream.
- `context` contains [Gateway metadata](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/available-context/). It describes the original request and does not change when your Worker modifies the request.
- `next(request)` sends a request to its original destination through Gateway and returns the upstream `Response`. Gateway establishes the upstream connection when the Worker calls `next()`.

The Worker can modify the method, headers, cookies, or body before calling `next()`. It can modify the upstream response or return a synthetic response without calling `next()`. Call `next()` at most once per invocation. You cannot use it to change the destination URL or host, retry an upstream request, or send requests to several destinations.

Gateway inspects the **original request body before delivering it to the Worker** and the **final response after the Worker**, including synthetic responses. Gateway does not inspect modified request bytes a second time before sending them upstream.

For example, a Worker cannot use redaction to bypass request-side Data Loss Prevention (DLP) inspection. Data the Worker adds to a permitted request does not receive another request-side DLP scan.

A normal Workers `fetch()` is an independent subrequest. Use `next()` to continue the user's request with Gateway's routing and egress configuration. Do not assume a `fetch()` call uses the user's Gateway policies or egress IP.

## Prerequisites

- Have a [Cloudflare account](https://developers.cloudflare.com/fundamentals/account/create-account/) with Workers set up. If you have not used Workers before, follow the [Workers getting started guide](https://developers.cloudflare.com/workers/get-started/guide/). Use the same account for Workers and Gateway.
- Set up [Gateway HTTP filtering](https://developers.cloudflare.com/cloudflare-one/traffic-controls/get-started/http/) and route your test device's traffic through Gateway.
- Enable [TLS decryption](https://developers.cloudflare.com/cloudflare-one/traffic-controls/http-policies/tls-decryption/) and install a trusted [root certificate](https://developers.cloudflare.com/cloudflare-one/team-and-resources/devices/user-side-certificates/) for HTTPS traffic.
- Use a deployed Worker in the same Cloudflare account as your Gateway policy. Configure a [Workers subdomain](https://developers.cloudflare.com/workers/configuration/routing/workers-dev/) for the account. The Worker does not need a public route.

## Set up Forward

1. Create a Worker project.npmyarnpnpm

   ```
   npx wrangler init gateway-worker
   ```

   ```
   yarn wrangler init gateway-worker
   ```

   ```
   pnpm wrangler init gateway-worker
   ```

   Use the following configuration. Disabling `workers_dev` and `preview_urls` keeps this Worker from being exposed through those public URLs.

   ```jsonc
   {
     "$schema": "./node_modules/wrangler/config-schema.json",
     "name": "gateway-worker",
     "main": "src/index.ts",
     // Set this to today's date
     "compatibility_date": "2026-11-06",
     "workers_dev": false,
     "preview_urls": false
   }
   ```

   ```toml
   name = "gateway-worker"
   main = "src/index.ts"
   # Set this to today's date
   compatibility_date = "2026-11-06"
   workers_dev = false
   preview_urls = false
   ```

2. Replace `src/index.ts` with the required named Worker entrypoint.

   Gateway invokes `GatewayMiddleware.handle()` through [Workers RPC](https://developers.cloudflare.com/workers/runtime-apis/rpc/). Keep the exported class name `GatewayMiddleware` exactly as shown.

   Install [`@cloudflare/zt-types` ↗︎](https://www.npmjs.com/package/@cloudflare/zt-types) version `2026.10.1` or later as a development dependency in this Worker project. It supplies the [Gateway context and continuation types](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/available-context/#typescript-types):npmyarnpnpmbun

   ```
   npm i -D @cloudflare/zt-types
   ```

   ```
   yarn add -D @cloudflare/zt-types
   ```

   ```
   pnpm add -D @cloudflare/zt-types
   ```

   ```
   bun add -d @cloudflare/zt-types
   ```

   Keep the type-only imports in TypeScript examples. They are removed when TypeScript compiles. Import the runtime `WorkerEntrypoint` class from `cloudflare:workers`. The JavaScript version omits the type imports and annotations and does not need this package.

   This example passes the request through and adds a response header so you can verify the Worker ran. The [context reference](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/available-context/) describes the fields you can use in other scripts.

   *src/index.jsjs*

   

   ```js
   import { WorkerEntrypoint } from "cloudflare:workers";

   export class GatewayMiddleware extends WorkerEntrypoint {
   	async handle(request, context, next) {
   		const response = await next(request);
   		const headers = new Headers(response.headers);
   		headers.set("x-gateway-worker", "gateway-worker");
   		return new Response(response.body, {
   			status: response.status,
   			statusText: response.statusText,
   			headers,
   		});
   	}
   }
   ```

   *src/index.tsts*

   

   ```ts
   import { WorkerEntrypoint } from "cloudflare:workers";
   import type {
     GatewayMiddlewareContext,
     GatewayMiddlewareNext,
   } from "@cloudflare/zt-types";

   export class GatewayMiddleware extends WorkerEntrypoint {
     async handle(
       request: Request,
       context: GatewayMiddlewareContext,
       next: GatewayMiddlewareNext,
     ): Promise<Response> {
       const response = await next(request);
       const headers = new Headers(response.headers);
       headers.set("x-gateway-worker", "gateway-worker");
       return new Response(response.body, {
         status: response.status,
         statusText: response.statusText,
         headers,
       });
     }
   }
   ```

3. Deploy the Worker to the account that contains your Gateway policies.npmyarnpnpm

   ```
   npx wrangler deploy
   ```

   ```
   yarn wrangler deploy
   ```

   ```
   pnpm wrangler deploy
   ```

4. In Cloudflare One, go to **Traffic controls** > **Firewall policies** > **HTTP**. [Go to **Firewall policies** ↗](https://one.dash.cloudflare.com/?to=/:account/gateway/policies)
5. Select **Add a policy**, name the policy, and scope it to one test host and a test user or device group. Use request metadata selectors such as **Host**, **URL**, and **HTTP Method**. You can also add identity and device posture conditions.
6. Select **Forward** as the action, select your deployed Worker, and select **Create policy**. Gateway invokes its `GatewayMiddleware` entrypoint, rather than a default `fetch()` handler.
7. From the test device, request the selected host and check for the `x-gateway-worker: gateway-worker` response header. A matching filtering Block policy still takes precedence over Forward. Refer to [Troubleshooting](#troubleshooting) if the header is absent.

### Configure with the API

Use the [Create a Zero Trust Gateway rule](https://developers.cloudflare.com/api/resources/zero_trust/subresources/gateway/subresources/rules/methods/create/) endpoint with `action: "forward"` and the Worker's ID in `rule_settings.forward.worker_id`. The Worker ID is a 32-character lowercase hexadecimal identifier. Use its ID, rather than its name or a `workers.dev` URL.

*Create a Zero Trust Gateway rulebash*

```bash
curl "https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/gateway/rules" \
	--request POST \
	--header "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
	--json '{
		"name": "Forward example.com to a Worker",
		"enabled": true,
		"precedence": 1000,
		"action": "forward",
		"filters": [
				"http"
		],
		"traffic": "http.request.host == \"example.com\"",
		"rule_settings": {
				"forward": {
						"worker_id": "<WORKER_ID>"
				}
		}
	}'
```

For deployed Worker identifiers, refer to [List Workers](https://developers.cloudflare.com/api/resources/workers/subresources/beta/subresources/workers/methods/list/).

## Known limitations

Gateway resolves the selected Worker only when a Forward policy is saved. Deleting the Worker does not remove or disable the policy. Matching requests then fail with HTTP `500` and do not reach the origin. Before deleting a Worker, update all Forward policies that use it to select another Worker, or delete those policies.

Gateway drops HTTP traffic sent from the Cloudflare compute platform through [Workers VPC](https://developers.cloudflare.com/workers-vpc/) if it matches a Forward policy. Gateway does not invoke the selected Worker. Exclude this traffic from your Forward policies.

WebSocket traffic connects directly to the original upstream service and bypasses the selected Worker. The Worker does not receive the WebSocket upgrade request or messages, so its logic does not apply to those connections.

Proxy HTTP `CONNECT` tunnels connect directly to the original upstream service and bypass the selected Worker.

| Area | Behavior |
| --- | --- |
| Policy matching | Forward uses request metadata, identity, and device posture conditions. Body inspection results, DLP matches, and response selectors cannot select a Worker. |
| Decryption | HTTPS traffic must be decrypted. Gateway does not invoke a Worker for traffic matching Do Not Inspect. |
| Protocols | Regular HTTP requests are supported. Gateway does not invoke a Worker for CONNECT tunnels, WebSocket connections, protocol upgrades, or HTTP trailers. |
| Destination | `next()` preserves the original destination URL and host. |
| Continuation | Call `next()` zero or one time. Repeated or concurrent calls are unsupported. |
| Response | Return a final status from `200` to `599`. Return no body for HEAD requests or statuses `204`, `205`, and `304`. |
| Streaming | Request and response bodies stream through Gateway with backpressure and cancellation. Avoid buffering entire bodies. |
| Failure | Invocation errors, Worker errors, or failures in communication between Gateway and the Worker fail closed. Gateway does not bypass the Worker and forward the original request. |
| Limits | Existing [Gateway limits](https://developers.cloudflare.com/cloudflare-one/account-limits/) and [Workers limits](https://developers.cloudflare.com/workers/platform/limits/) apply. Workers usage is subject to normal [Workers pricing](https://developers.cloudflare.com/workers/platform/pricing/). |

Use `this.ctx.waitUntil()` for background tasks that can run after returning the response. These tasks remain subject to [Workers execution limits](https://developers.cloudflare.com/workers/platform/limits/#duration) and are not a durable job queue. Use [Queues](https://developers.cloudflare.com/queues/) or [Workflows](https://developers.cloudflare.com/workflows/) for work that requires retries or more time.

## Troubleshooting

If your Worker does not run, confirm that the device uses Gateway HTTP filtering, HTTPS is decrypted, and the request is permitted by filtering policies. Check whether a Do Not Inspect policy or an earlier Forward policy matches the request.

If requests fail after enabling Forward, confirm that the Worker exports `GatewayMiddleware`, its `handle()` method returns a `Response`, and it calls `next()` at most once. Check for exceptions, unsupported status codes, and bodies on responses that must have no body. Use [Workers Logs](https://developers.cloudflare.com/workers/observability/logs/workers-logs/) to inspect invocation errors. Disable the test Forward policy while correcting a failing script.

For identity and device posture errors, refer to [Handle identity and posture errors](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/available-context/#handle-identity-and-posture-errors).

If matching requests return HTTP `500` after a Worker was deleted, update the Forward policy to select an existing Worker, or delete the policy. Saving a policy that still references the deleted Worker fails with API error `2294`.

Start with a [Worker template](https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/worker-templates/) once the pass-through example works.

Was this helpful?

YesNo

## On this page

[![](https://developers.cloudflare.com/_astro/logo.te5VL_aD.svg)Docs](https://developers.cloudflare.com/)

```json
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/forward-traffic/#page","headline":"Forward traffic","description":"Configure the Gateway Forward action to run a Worker on permitted HTTP requests and responses.","url":"https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/forward-traffic/","inLanguage":"en","image":"https://developers.cloudflare.com/cloudflare-one/traffic-controls/integrate-workers/forward-traffic/og.png?v=8ed88a14e05ed330","dateModified":"2026-10-15","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
```
