Files
dotfiles/.agents/skills/cloudflare-deploy/references/turn/configuration.md
2026-03-17 16:53:22 -07:00

4.1 KiB

TURN Configuration

Setup and configuration for Cloudflare TURN service in Workers and applications.

Environment Variables

# .env
CLOUDFLARE_ACCOUNT_ID=your_account_id
CLOUDFLARE_API_TOKEN=your_api_token
TURN_KEY_ID=your_turn_key_id
TURN_KEY_SECRET=your_turn_key_secret

Validate with zod:

import { z } from 'zod';

const envSchema = z.object({
  CLOUDFLARE_ACCOUNT_ID: z.string().min(1),
  CLOUDFLARE_API_TOKEN: z.string().min(1),
  TURN_KEY_ID: z.string().min(1),
  TURN_KEY_SECRET: z.string().min(1)
});

export const config = envSchema.parse(process.env);

wrangler.jsonc

{
  "name": "turn-credentials-api",
  "main": "src/index.ts",
  "compatibility_date": "2025-01-01",
  "vars": {
    "TURN_KEY_ID": "your-turn-key-id"  // Non-sensitive, can be in vars
  },
  "env": {
    "production": {
      "kv_namespaces": [
        {
          "binding": "CREDENTIALS_CACHE",
          "id": "your-kv-namespace-id"
        }
      ]
    }
  }
}

Store secrets separately:

wrangler secret put TURN_KEY_SECRET

Cloudflare Worker Integration

Worker Binding Types

interface Env {
  TURN_KEY_ID: string;
  TURN_KEY_SECRET: string;
  CREDENTIALS_CACHE?: KVNamespace;
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    // See patterns.md for implementation
  }
}

Basic Worker Example

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    if (request.url.endsWith('/turn-credentials')) {
      // Validate client auth
      const authHeader = request.headers.get('Authorization');
      if (!authHeader) {
        return new Response('Unauthorized', { status: 401 });
      }

      const response = await fetch(
        `https://rtc.live.cloudflare.com/v1/turn/keys/${env.TURN_KEY_ID}/credentials/generate`,
        {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${env.TURN_KEY_SECRET}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ ttl: 3600 })
        }
      );

      if (!response.ok) {
        return new Response('Failed to generate credentials', { status: 500 });
      }

      const data = await response.json();

      // Filter port 53 for browser clients
      const filteredUrls = data.iceServers.urls.filter(
        (url: string) => !url.includes(':53')
      );

      return Response.json({
        iceServers: [
          { urls: 'stun:stun.cloudflare.com:3478' },
          {
            urls: filteredUrls,
            username: data.iceServers.username,
            credential: data.iceServers.credential
          }
        ]
      });
    }

    return new Response('Not found', { status: 404 });
  }
};

IP Allowlisting (Enterprise/Firewall)

For strict firewalls, allowlist these IPs for turn.cloudflare.com:

Type Address Protocol
IPv4 141.101.90.1/32 All
IPv4 162.159.207.1/32 All
IPv6 2a06:98c1:3200::1/128 All
IPv6 2606:4700:48::1/128 All

IMPORTANT: These IPs may change with 14-day notice. Monitor DNS:

# Check A and AAAA records
dig turn.cloudflare.com A
dig turn.cloudflare.com AAAA

Set up automated monitoring to detect IP changes and update allowlists within 14 days.

IPv6 Support

  • Client-to-TURN: Both IPv4 and IPv6 supported
  • Relay addresses: IPv4 only (no RFC 6156 support)
  • TCP relaying: Not supported (RFC 6062)

Clients can connect via IPv6, but relayed traffic uses IPv4 addresses.

TLS Configuration

Supported TLS Versions

  • TLS 1.1
  • TLS 1.2
  • TLS 1.3
  • AEAD-AES128-GCM-SHA256
  • AEAD-AES256-GCM-SHA384
  • AEAD-CHACHA20-POLY1305-SHA256
  • ECDHE-ECDSA-AES128-GCM-SHA256
  • ECDHE-RSA-AES128-GCM-SHA256
  • ECDHE-RSA-AES128-SHA (also TLS 1.1)
  • AES128-GCM-SHA256

See Also

  • api.md - TURN key creation, credential generation API
  • patterns.md - Full Worker implementation patterns
  • gotchas.md - Security best practices, troubleshooting