
Cloudflare Knowledge
- 116 installs
- 50 repo stars
- Updated June 18, 2026
- josiahsiegel/claude-plugin-marketplace
Configure Cloudflare DNS, CDN, WAF, Workers, and Zero Trust while wiring edge services into apps and production traffic paths.
About
Provides practical Cloudflare platform knowledge for engineers integrating DNS, CDN, security, and edge compute into SaaS and API products, covering routing, caching, Workers, and operational deployment patterns.
- DNS and CDN configuration
- Workers and edge compute
- WAF and Zero Trust patterns
- Cache and performance tuning
- Terraform and API deployment
Cloudflare Knowledge by the numbers
- 116 all-time installs (skills.sh)
- +4 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #544 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/josiahsiegel/claude-plugin-marketplace --skill cloudflare-knowledgeAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 116 |
|---|---|
| repo stars | ★ 50 |
| Last updated | June 18, 2026 |
| Repository | josiahsiegel/claude-plugin-marketplace ↗ |
What it does
Configure Cloudflare DNS, CDN, WAF, Workers, and Zero Trust while wiring edge services into apps and production traffic paths.
Files
Cloudflare Knowledge Skill
Comprehensive Cloudflare platform knowledge covering Workers, edge storage, AI, MCP, and Zero Trust. Acts as a lean orchestrator over deep references in references/.
Activation Triggers
Activate this skill when users ask about:
- Cloudflare Workers development, Wrangler CLI,
wrangler.jsoncconfiguration - Storage services (R2, D1, KV, Durable Objects, Queues, Hyperdrive)
- Workers AI inference (LLM, TTS, STT, image, embeddings, vision)
- MCP server development on Workers
- Zero Trust (tunnels, WARP, Access policies)
- Workflows and durable execution, Vectorize, Pages, cron triggers
- CI/CD with GitHub Actions or Workers Builds
- Observability, load balancing, cost optimization
Reference Map
Load only the reference(s) the current task needs:
| Topic | File | When to load |
|---|---|---|
All Wrangler CLI commands, complete wrangler.jsonc schema, GitHub Actions, Workers Builds | references/wrangler-cli-and-config.md | Initializing projects, configuring bindings, writing CI pipelines, troubleshooting wrangler |
| KV, R2, D1, Durable Objects, Queues, Hyperdrive — characteristics, TypeScript APIs, best practices, WebSocket Hibernation, multipart upload | references/storage-services-deep-dive.md | Picking a storage service, writing handler code for any binding, designing schema or partitioning |
| Workers AI catalog (text/TTS/STT/image/vision/embeddings), invocation examples, MCP server on Workers, Cloudflare Tunnel install + Access policies + WARP | references/ai-workers-usage.md | Invoking AI models, building an MCP server, setting up cloudflared, configuring Zero Trust ingress |
| Workers AI model selection (which model for which task, context windows, perf) | references/ai-workers-models.md | Deciding between Llama, Mistral, Qwen, DeepSeek, Whisper variants, etc. |
| Deeper MCP server development (transport types, auth, tool schemas) | references/mcp-server-development.md | Building production MCP servers, debugging transport |
| Deeper Zero Trust setup (org policies, identity providers, posture checks) | references/zero-trust-setup.md | Production Zero Trust rollout |
| Cost comparison vs AWS/Azure/GCP, pricing tables, optimization tactics | references/cost-comparison.md | Budget planning, plan selection, cost optimization |
| Integrating non-Cloudflare services (Stripe, OpenAI, GitHub, third-party APIs from Workers) | references/third-party-integrations.md | Wiring external APIs into a Worker |
Platform Overview
Cloudflare is a global edge computing platform with 300+ data centers providing:
- Workers — Serverless JavaScript/TypeScript/Python/WASM at the edge
- Pages — Static site and full-stack app hosting
- R2 — S3-compatible object storage with zero egress fees
- D1 — Serverless SQLite database (strongly consistent, 10 GB max)
- KV — Eventually consistent key-value store
- Durable Objects — Stateful coordination with WebSocket Hibernation
- Queues — Async message processing with DLQ
- Hyperdrive — Database connection pooling for remote Postgres/MySQL
- Workers AI — LLM/TTS/STT/image/embeddings/vision at the edge
- Zero Trust — Identity-based security platform
- Vectorize — Vector database for RAG
- Workflows — Durable multi-step execution
Core Workflow
1. Scaffold — npm create cloudflare@latest then npx wrangler login. Wrangler CLI details: wrangler-cli-and-config.md. 2. Pick a storage primitive — KV for config/sessions, R2 for blobs, D1 for relational, Durable Objects for coordination, Queues for async, Hyperdrive for remote SQL. Characteristics and trade-offs: storage-services-deep-dive.md. 3. Add bindings to `wrangler.jsonc` — KV namespaces, R2 buckets, D1 databases, DO, Queues, AI, Vectorize, service bindings, cron triggers, routes, observability. Full schema: wrangler-cli-and-config.md. 4. Implement handlers — fetch, scheduled, queue, email. Per-binding APIs: storage-services-deep-dive.md. AI invocations: ai-workers-usage.md. 5. Develop locally — npx wrangler dev (use --remote for remote bindings; trigger crons via /__scheduled?cron=*+*+*+*+*). 6. Deploy — npx wrangler deploy [--env staging]. Roll back with npx wrangler rollback. CI/CD recipes (GitHub Actions, Workers Builds): wrangler-cli-and-config.md.
Quick Decision Guide
| Task | Choice | Reference |
|---|---|---|
| Store user sessions, config flags | KV (eventually consistent) | storage-services-deep-dive.md |
| Store media, backups, datasets | R2 (zero egress, 5 TB objects) | storage-services-deep-dive.md |
| Relational queries, ACID | D1 (SQLite, strong consistency) | storage-services-deep-dive.md |
| Real-time coordination, chat, counters | Durable Objects (+ WebSocket Hibernation) | storage-services-deep-dive.md |
| Background jobs, decoupling | Queues (at-least-once, DLQ) | storage-services-deep-dive.md |
| Remote Postgres/MySQL with low latency | Hyperdrive | storage-services-deep-dive.md |
| LLM/embedding/TTS/STT at the edge | Workers AI | ai-workers-usage.md + ai-workers-models.md |
| Expose internal app without opening firewall | Cloudflare Tunnel (cloudflared) | ai-workers-usage.md (quickstart) + zero-trust-setup.md (production) |
| Build MCP server on Workers | @cloudflare/mcp-server | ai-workers-usage.md (quickstart) + mcp-server-development.md (deep) |
| Integrate Stripe, OpenAI, GitHub, etc. | Third-party API patterns | third-party-integrations.md |
| Plan budget vs AWS/Azure/GCP | Pricing comparison | cost-comparison.md |
Best Practices
Performance
1. Use edge caching — cache API responses via caches.default. 2. Minimize cold starts — keep Workers small, prefer dynamic imports. 3. Use Service Bindings — zero-cost Worker-to-Worker calls. 4. Batch operations — combine KV/R2/D1 operations. 5. Use Hyperdrive for remote PostgreSQL/MySQL.
Security
1. Use wrangler secret put for credentials, never hardcode. 2. Validate and sanitize all user input. 3. Always use HTTPS; enforce on routes. 4. Implement rate limiting (Workers Rate Limiting API or WAF rules). 5. Use Zero Trust Access for internal services (see zero-trust-setup.md).
Cost Optimization
1. Use Static Assets (free, unlimited static file serving). 2. Sample logs via observability.logs.head_sampling_rate for high-traffic Workers. 3. Use KV/R2 for caching to reduce D1 or external API calls. 4. Batch Queue messages to reduce per-message overhead. 5. Choose model size to fit task in Workers AI — see ai-workers-models.md. 6. Full pricing tables and cross-cloud comparison: cost-comparison.md.
Quick Reference
| Task | Command |
|---|---|
| New project | npm create cloudflare@latest |
| Local dev | npx wrangler dev |
| Deploy | npx wrangler deploy |
| Create D1 | npx wrangler d1 create <name> |
| Create KV | npx wrangler kv namespace create <NAME> |
| Create R2 | npx wrangler r2 bucket create <name> |
| Set secret | npx wrangler secret put <NAME> |
| Create queue | npx wrangler queues create <name> |
| Create tunnel | cloudflared tunnel create <name> |
| Create Hyperdrive | npx wrangler hyperdrive create <name> --connection-string=... |
Full command surface (every flag, every subcommand) is in references/wrangler-cli-and-config.md.
AI Workers Models Reference (2025-2026)
Text Generation Models
Large Language Models
| Model ID | Parameters | Context | Best For | Notes |
|---|---|---|---|---|
| @cf/meta/llama-3.3-70b-instruct-fp8-fast | 70B | 128K | General, reasoning | Latest Llama |
| @cf/meta/llama-3.1-70b-instruct | 70B | 128K | General purpose | Stable |
| @cf/meta/llama-3.1-8b-instruct | 8B | 128K | Fast inference | Good quality |
| @cf/mistral/mistral-7b-instruct-v0.2 | 7B | 32K | Fast, efficient | Low latency |
| @cf/qwen/qwen2.5-72b-instruct | 72B | 128K | Multilingual | Excellent Chinese |
| @cf/deepseek/deepseek-r1-distill-llama-70b | 70B | 64K | Complex reasoning | Chain-of-thought |
| @cf/google/gemma-7b-it | 7B | 8K | Lightweight | Google model |
Usage
interface Env {
AI: Ai;
}
// Basic generation
const response = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Explain serverless computing." },
],
max_tokens: 1024,
temperature: 0.7,
top_p: 0.9,
});
console.log(response.response);
// Streaming
const stream = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "user", content: "Write a poem about the edge." },
],
stream: true,
});
return new Response(stream, {
headers: {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
});
// JSON mode
const structured = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "system", content: "Respond with valid JSON only." },
{ role: "user", content: "List 3 programming languages with their use cases." },
],
response_format: { type: "json_object" },
});---
Text-to-Speech (TTS) Models
| Model ID | Languages | Quality | Latency | Notes |
|---|---|---|---|---|
| @deepgram/aura-2-en | English | Excellent | Medium | Context-aware, natural |
| @deepgram/aura-1 | English | Good | Fast | Reliable |
| @cf/myshell-ai/melotts | en, fr, es, zh, ja, ko | Good | Fast | Multi-lingual |
Usage
// Aura-2 (Best quality English TTS)
const audio = await env.AI.run("@deepgram/aura-2-en", {
text: "Hello! This is a demonstration of Cloudflare Workers AI text-to-speech capabilities.",
});
// Returns ArrayBuffer containing audio/wav
return new Response(audio, {
headers: {
"Content-Type": "audio/wav",
"Content-Disposition": "attachment; filename='speech.wav'",
},
});
// MeloTTS (Multi-lingual)
const frenchAudio = await env.AI.run("@cf/myshell-ai/melotts", {
text: "Bonjour! Comment allez-vous aujourd'hui?",
language: "fr",
});
// Supported languages: en, fr, es, zh, ja, ko
const japaneseAudio = await env.AI.run("@cf/myshell-ai/melotts", {
text: "こんにちは、世界!",
language: "ja",
});Real-Time TTS with WebSocket
// For real-time voice applications
export class VoiceAgent {
state: DurableObjectState;
async fetch(request: Request) {
if (request.headers.get("Upgrade") === "websocket") {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.state.acceptWebSocket(server);
return new Response(null, { status: 101, webSocket: client });
}
return new Response("Expected WebSocket", { status: 400 });
}
async webSocketMessage(ws: WebSocket, message: string) {
// Generate speech from text message
const audio = await this.env.AI.run("@deepgram/aura-2-en", {
text: message,
});
// Send audio back to client
ws.send(audio);
}
}---
Speech-to-Text (STT) Models
| Model ID | Languages | Speed | Quality | Notes |
|---|---|---|---|---|
| @cf/openai/whisper-large-v3-turbo | 100+ | Fast | Excellent | Recommended |
| @cf/openai/whisper | 100+ | Slower | Excellent | Original |
Usage
// Basic transcription
export default {
async fetch(request: Request, env: Env) {
if (request.method !== "POST") {
return new Response("POST audio data to transcribe", { status: 405 });
}
const audioData = await request.arrayBuffer();
const result = await env.AI.run("@cf/openai/whisper-large-v3-turbo", {
audio: audioData,
});
return Response.json({
text: result.text,
segments: result.segments, // Timestamped segments
});
},
};
// With language hint
const result = await env.AI.run("@cf/openai/whisper-large-v3-turbo", {
audio: audioData,
source_lang: "es", // Spanish
});
// Process audio from R2
const object = await env.MY_BUCKET.get("recordings/meeting.mp3");
if (object) {
const audioData = await object.arrayBuffer();
const transcript = await env.AI.run("@cf/openai/whisper-large-v3-turbo", {
audio: audioData,
});
// Store transcript
await env.MY_BUCKET.put("transcripts/meeting.txt", transcript.text);
}Response Format
interface WhisperResponse {
text: string; // Full transcript
segments: Array<{
start: number; // Start time in seconds
end: number; // End time in seconds
text: string; // Segment text
}>;
}---
Image Generation Models
| Model ID | Max Resolution | Steps | Notes |
|---|---|---|---|
| @cf/black-forest-labs/flux-1-schnell | 1024x1024 | 4 | Fast, good quality |
| @cf/stabilityai/stable-diffusion-xl-base-1.0 | 1024x1024 | 20+ | Detailed |
Usage
// FLUX.1 Schnell (Fast)
const image = await env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
prompt: "A majestic mountain landscape at sunset, photorealistic, 8k",
num_steps: 4, // 1-8
});
return new Response(image, {
headers: { "Content-Type": "image/png" },
});
// Stable Diffusion XL
const sdImage = await env.AI.run("@cf/stabilityai/stable-diffusion-xl-base-1.0", {
prompt: "A cyberpunk cityscape with neon lights, digital art style",
negative_prompt: "blurry, low quality, distorted",
num_steps: 20,
guidance: 7.5,
width: 1024,
height: 1024,
});
// Image-to-Image
const modifiedImage = await env.AI.run("@cf/stabilityai/stable-diffusion-xl-base-1.0", {
prompt: "Convert to watercolor painting style",
image: originalImageArrayBuffer,
strength: 0.75, // 0-1, higher = more change
});---
Vision/Captioning Models
| Model ID | Capabilities | Notes |
|---|---|---|
| @cf/meta/llama-3.2-11b-vision-instruct | Image understanding, Q&A, captioning | Recommended |
| @cf/llava-hf/llava-1.5-7b-hf | Visual Q&A | Lighter |
Usage
// Image captioning
const caption = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: imageArrayBuffer,
prompt: "Describe this image in detail.",
});
// Visual Q&A
const answer = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: imageArrayBuffer,
prompt: "What objects are visible in this image? List them.",
});
// Image analysis for accessibility
const altText = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: imageArrayBuffer,
prompt: "Generate a concise alt text for this image suitable for screen readers.",
});
// Multi-image comparison (with base64)
const image1 = await env.MY_BUCKET.get("img1.jpg");
const image2 = await env.MY_BUCKET.get("img2.jpg");
const comparison = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: [await image1?.arrayBuffer(), await image2?.arrayBuffer()],
prompt: "Compare these two images and describe the differences.",
});---
Embedding Models
| Model ID | Dimensions | Best For | Notes |
|---|---|---|---|
| @cf/baai/bge-large-en-v1.5 | 1024 | Best quality | Recommended |
| @cf/baai/bge-base-en-v1.5 | 768 | Balanced | Good quality |
| @cf/baai/bge-small-en-v1.5 | 384 | Fast | Lightweight |
| @cf/sentence-transformers/all-minilm-l6-v2 | 384 | General | Fast |
Usage
// Single text
const embedding = await env.AI.run("@cf/baai/bge-large-en-v1.5", {
text: "Cloudflare Workers enables serverless computing at the edge.",
});
// Returns { data: [{ embedding: [0.1, 0.2, ...] }] }
// Batch embeddings
const embeddings = await env.AI.run("@cf/baai/bge-large-en-v1.5", {
text: [
"First document text",
"Second document text",
"Third document text",
],
});
// Returns { data: [{ embedding: [...] }, { embedding: [...] }, { embedding: [...] }] }
// Store in Vectorize
for (let i = 0; i < texts.length; i++) {
await env.MY_VECTORS.insert({
id: `doc-${i}`,
values: embeddings.data[i].embedding,
metadata: { text: texts[i] },
});
}
// Query Vectorize
const queryEmbedding = await env.AI.run("@cf/baai/bge-large-en-v1.5", {
text: "What is serverless?",
});
const results = await env.MY_VECTORS.query(queryEmbedding.data[0].embedding, {
topK: 5,
returnMetadata: true,
});---
RAG Pattern with Vectorize
interface Env {
AI: Ai;
VECTORS: VectorizeIndex;
}
export default {
async fetch(request: Request, env: Env) {
const { query } = await request.json();
// 1. Embed the query
const queryEmbedding = await env.AI.run("@cf/baai/bge-large-en-v1.5", {
text: query,
});
// 2. Search vector database
const matches = await env.VECTORS.query(queryEmbedding.data[0].embedding, {
topK: 3,
returnMetadata: true,
});
// 3. Build context from matches
const context = matches.matches
.map((m) => m.metadata?.text)
.join("\n\n");
// 4. Generate response with context
const response = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{
role: "system",
content: `Answer the question based on the following context:\n\n${context}`,
},
{ role: "user", content: query },
],
});
return Response.json({
answer: response.response,
sources: matches.matches.map((m) => m.id),
});
},
};---
---
AI Workers vs Third-Party: Cost Scenarios
When Cloudflare AI Workers SAVES Money
Scenario 1: High-Volume English TTS
Use Case: 1M characters/day English TTS for automated phone system
Cloudflare Aura-2:
Cost: $0.030/1K chars × 1,000K = $30/day = $900/month
Quality: Excellent, context-aware
ElevenLabs Direct (Pro Plan):
Cost: $99/month base + overage at $0.18/1K chars
For 30M chars/month: $99 + (30M - 500K) × $0.18/1K = ~$5,409/month
fal.ai ElevenLabs:
Cost: $0.10/1K chars × 30,000K = $3,000/month
SAVINGS: 83-94% by using Cloudflare AI WorkersScenario 2: Multilingual TTS (Supported Languages)
Use Case: 100K chars/day in French, Spanish, Chinese, Japanese, Korean
Cloudflare MeloTTS:
Cost: ~$0.0002/min (roughly 100 mins/day) = $0.60/month
ElevenLabs Multilingual v2:
Cost: ~$0.165/1K × 3,000K = $495/month
SAVINGS: 99.8% by using Cloudflare AI WorkersScenario 3: LLM Inference for RAG
Use Case: Customer support bot, 10M tokens/month input + 2M output
Cloudflare Llama 3.3 70B:
Cost: (10M + 2M) × $0.27/1M = $3.24/month
OpenAI GPT-4o-mini:
Cost: 10M × $0.15/1M + 2M × $0.60/1M = $2.70/month
OpenAI GPT-4o:
Cost: 10M × $2.50/1M + 2M × $10/1M = $45/month
Anthropic Claude 3.5 Sonnet:
Cost: 10M × $3.00/1M + 2M × $15/1M = $60/month
NOTE: Cloudflare is 1.2x GPT-4o-mini but 14x cheaper than GPT-4o
For quality comparable to GPT-4o, Llama 3.3 70B offers massive savings.Scenario 4: Speech-to-Text Transcription
Use Case: Transcribe 500 hours of audio/month
Cloudflare Whisper large-v3-turbo:
Cost: $0.0052/min × 30,000 min = $156/month
OpenAI Whisper:
Cost: $0.006/min × 30,000 min = $180/month
AssemblyAI:
Cost: $0.0065/min × 30,000 min = $195/month
SAVINGS: 13-20% by using Cloudflare AI WorkersScenario 5: Embeddings at Scale
Use Case: Embed 100M tokens for search index
Cloudflare BGE-large:
Cost: ~$0.01/1M tokens × 100 = $1.00
OpenAI text-embedding-3-small:
Cost: $0.02/1M × 100 = $2.00
OpenAI text-embedding-3-large:
Cost: $0.13/1M × 100 = $13.00
Cohere embed-v3:
Cost: $0.10/1M × 100 = $10.00
SAVINGS: 50-92% by using Cloudflare AI WorkersWhen Third-Party is WORTH the Extra Cost
Scenario 1: Premium Voice Quality Requirements
Use Case: Audiobook narration, premium customer experience
Why ElevenLabs is worth it:
- Voice cloning (custom brand voice)
- Emotional expressiveness control
- 29+ languages with native quality
- Professional studio-grade output
- Voice consistency across long content
When to pay premium:
- Brand voice consistency is critical
- Content will be published/permanent
- Customer experience justifies cost
- Need voice cloning capabilitiesScenario 2: Real-Time Voice Agents
Use Case: Live conversational AI with <100ms latency
ElevenLabs Turbo v2.5:
- Latency: ~75ms
- Real-time streaming WebSocket
- Optimized for conversations
Cloudflare Aura-2:
- Latency: ~200-500ms (request/response)
- No native WebSocket streaming
Verdict: For real-time voice agents, ElevenLabs is necessary.
Use Cloudflare for batch processing, ElevenLabs for live.Scenario 3: Languages Not Supported by Cloudflare
Use Case: TTS in Portuguese, Italian, Hindi, Arabic
Cloudflare MeloTTS supports: en, fr, es, zh, ja, ko (6 languages)
ElevenLabs supports: 32 languages
For unsupported languages, third-party is required.
Cost-Effective Path:
1. Use Cloudflare for supported languages (99.8% savings)
2. Use ElevenLabs only for unsupported languages
3. Route via AI Gateway for caching/loggingScenario 4: Cutting-Edge Reasoning Models
Use Case: Complex mathematical reasoning, code generation
If you need GPT-4o/Claude-level reasoning:
- Cloudflare has DeepSeek-R1-Distill (good for CoT)
- But GPT-4o/Claude may outperform on edge cases
Cost-Performance Tradeoff:
- Cloudflare Llama 3.3 70B: $0.27/1M - 90% of tasks
- Fall back to GPT-4o: $2.50/1M - complex 10%
Hybrid Strategy: Route 90% to Cloudflare, 10% to OpenAI
Average cost: ~$0.50/1M vs $2.50/1M (80% savings)Cost Decision Matrix
| Requirement | Cloudflare AI Workers | Third-Party |
|---|---|---|
| English TTS, high volume | ✅ Aura-2 (82% savings) | ❌ Overkill |
| Premium voice quality | ⚠️ Good, not premium | ✅ ElevenLabs |
| Voice cloning | ❌ Not available | ✅ ElevenLabs/F5-TTS |
| Real-time voice (<100ms) | ❌ Too slow | ✅ ElevenLabs Turbo |
| Multilingual TTS (6 langs) | ✅ MeloTTS (99% savings) | ❌ Overkill |
| TTS in Portuguese/Arabic | ❌ Not supported | ✅ ElevenLabs |
| STT transcription | ✅ Whisper (15% savings) | ⚠️ Similar price |
| LLM (most tasks) | ✅ Llama 3.3 (10-20x savings) | ❌ Overkill |
| LLM (cutting-edge) | ⚠️ May need hybrid | ✅ GPT-4o/Claude |
| Embeddings | ✅ BGE (50-90% savings) | ❌ Overkill |
| Image generation | ✅ FLUX.1 (3x savings) | ❌ DALL-E 3 costly |
| Vision/captioning | ✅ Llama 3.2 Vision | ⚠️ GPT-4V for complex |
---
Hybrid Architecture: Best of Both Worlds
Pattern: AI Gateway Router
Route requests to optimal provider based on requirements:
interface Env {
AI: Ai;
ELEVENLABS_API_KEY: string;
CF_ACCOUNT_ID: string;
AI_GATEWAY_ID: string;
}
type TTSProvider = "cloudflare" | "elevenlabs";
interface TTSRequest {
text: string;
language?: string;
voice_quality?: "standard" | "premium";
voice_id?: string; // For ElevenLabs custom voice
}
async function generateTTS(request: TTSRequest, env: Env): Promise<ArrayBuffer> {
const provider = selectProvider(request);
if (provider === "cloudflare") {
return await cloudflareNativeTTS(request, env);
} else {
return await elevenLabsViaTGateway(request, env);
}
}
function selectProvider(request: TTSRequest): TTSProvider {
// Premium quality or custom voice = ElevenLabs
if (request.voice_quality === "premium" || request.voice_id) {
return "elevenlabs";
}
// Unsupported language = ElevenLabs
const cloudflareLanguages = ["en", "fr", "es", "zh", "ja", "ko"];
if (request.language && !cloudflareLanguages.includes(request.language)) {
return "elevenlabs";
}
// Default to Cloudflare for cost savings
return "cloudflare";
}
async function cloudflareNativeTTS(request: TTSRequest, env: Env): Promise<ArrayBuffer> {
if (request.language === "en") {
// Use Aura-2 for English (best quality)
return await env.AI.run("@deepgram/aura-2-en", { text: request.text });
} else {
// Use MeloTTS for other supported languages
return await env.AI.run("@cf/myshell-ai/melotts", {
text: request.text,
language: request.language || "en",
});
}
}
async function elevenLabsViaGateway(request: TTSRequest, env: Env): Promise<ArrayBuffer> {
const voiceId = request.voice_id || "JBFqnCBsd6RMkjVDRZzb"; // Default voice
const gatewayUrl = `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/elevenlabs/v1/text-to-speech/${voiceId}?output_format=mp3_44100_128`;
const response = await fetch(gatewayUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({
text: request.text,
model_id: request.voice_quality === "premium"
? "eleven_multilingual_v2"
: "eleven_turbo_v2_5",
}),
});
return await response.arrayBuffer();
}Pattern: Cache-First AI
Dramatically reduce AI costs with R2 caching:
interface Env {
AI: Ai;
AUDIO_CACHE: R2Bucket;
}
async function getCachedTTS(text: string, env: Env): Promise<ArrayBuffer> {
// Create deterministic cache key
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(text)
);
const cacheKey = `tts/${Array.from(new Uint8Array(hash.slice(0, 8)))
.map(b => b.toString(16).padStart(2, "0"))
.join("")}.wav`;
// Check R2 cache first
const cached = await env.AUDIO_CACHE.get(cacheKey);
if (cached) {
console.log(`Cache HIT: ${cacheKey}`);
return await cached.arrayBuffer();
}
// Generate new audio
console.log(`Cache MISS: ${cacheKey}`);
const audio = await env.AI.run("@deepgram/aura-2-en", { text });
// Store in cache (fire-and-forget)
env.AUDIO_CACHE.put(cacheKey, audio, {
httpMetadata: { contentType: "audio/wav" },
});
return audio;
}
// Cost analysis:
// - First request: $0.030/1K chars (Aura-2)
// - Subsequent: $0.00000036 (R2 GET)
// - Break-even: 1 cache hit saves $0.029+
// - For 100 requests of same text: 99.7% savings---
AI Workers Neuron Pricing Deep Dive
Cloudflare charges $0.011 per 1,000 Neurons. Understanding neuron consumption helps optimize costs.
Neuron Consumption by Model Type
| Model Category | Neurons per Unit | Cost Equivalent |
|---|---|---|
| Llama 3.3 70B | ~24,545 per 1M tokens | $0.27/1M tokens |
| Llama 3.1 8B | ~4,545 per 1M tokens | $0.05/1M tokens |
| Qwen 2.5 72B | ~31,818 per 1M tokens | $0.35/1M tokens |
| DeepSeek-R1-Distill | ~12,727 per 1M tokens | $0.14/1M tokens |
| Whisper STT | ~472 per minute | $0.0052/min |
| Aura-2 TTS | ~2,727 per 1K chars | $0.030/1K chars |
| BGE Embeddings | ~909 per 1M tokens | $0.01/1M tokens |
| FLUX.1 Image | ~1,818 per image | $0.02/image |
Free Tier Utilization
Workers AI includes 10,000 free neurons per day.
Daily free allocation examples:
- ~400K tokens of Llama 3.1 8B inference
- ~21 minutes of Whisper transcription
- ~3,600 characters of Aura-2 TTS
- ~11M tokens of BGE embeddings
Cost Optimization Formula
Monthly AI Cost =
(Total Neurons - (10,000 × 30 days)) × $0.011 / 1000
Example: 5M neurons/month
Free: 300,000 neurons
Billable: 4,700,000 neurons
Cost: 4,700 × $0.011 = $51.70/month---
Pricing Notes
AI Workers pricing varies by model (via neuron consumption):
- Text generation: $0.05-0.35 per 1M tokens (model dependent)
- TTS Aura-2: $0.030 per 1K characters
- TTS Aura-1: $0.015 per 1K characters
- TTS MeloTTS: $0.0002 per audio minute
- STT Whisper: $0.0052 per minute of audio
- Image FLUX.1: ~$0.02 per image
- Embeddings BGE: ~$0.01 per 1M tokens
Check Cloudflare dashboard for current pricing.
Best Practices
1. Use streaming for long text generation responses 2. Cache audio in R2 - one cache hit saves 99%+ cost 3. Batch embeddings - single request for multiple texts 4. Use appropriate model size - Llama 8B is 5x cheaper than 70B 5. Set max_tokens to limit costs and improve latency 6. Handle errors gracefully - AI models can fail 7. Route to Cloudflare first - use third-party only when necessary 8. Use AI Gateway for third-party - enables caching and logging 9. Monitor neuron usage - stay within free tier when possible 10. Hybrid architecture - route 90% to Cloudflare, 10% to premium
Workers AI Usage Examples
Reference for invoking Workers AI models from a Worker — text generation, streaming, TTS, STT, image generation, embeddings, vision. For the full model catalog (which model to pick), see ai-workers-models.md.
Available Models (2025-2026)
Text Generation
| Model | Context | Best For |
|---|---|---|
| @cf/meta/llama-3.3-70b-instruct-fp8-fast | 128K | General, reasoning |
| @cf/mistral/mistral-7b-instruct-v0.2 | 32K | Fast, efficient |
| @cf/qwen/qwen2.5-72b-instruct | 128K | Multilingual |
| @cf/deepseek/deepseek-r1-distill-llama-70b | 64K | Complex reasoning |
Text-to-Speech (TTS)
| Model | Languages | Notes |
|---|---|---|
| @deepgram/aura-2-en | English | Best quality, context-aware |
| @deepgram/aura-1 | English | Fast, good quality |
| @cf/myshell-ai/melotts | en, fr, es, zh, ja, ko | Multi-lingual |
Speech-to-Text (STT)
| Model | Languages | Notes |
|---|---|---|
| @cf/openai/whisper-large-v3-turbo | 100+ | Fast, accurate |
| @cf/openai/whisper | 100+ | Original Whisper |
Image Generation
| Model | Resolution | Notes |
|---|---|---|
| @cf/black-forest-labs/flux-1-schnell | Up to 1024x1024 | Fast |
| @cf/stabilityai/stable-diffusion-xl-base-1.0 | Up to 1024x1024 | Detailed |
Vision/Captioning
| Model | Capabilities |
|---|---|
| @cf/meta/llama-3.2-11b-vision-instruct | Image understanding, captioning |
| @cf/llava-hf/llava-1.5-7b-hf | Visual Q&A |
Embeddings
| Model | Dimensions | Notes |
|---|---|---|
| @cf/baai/bge-large-en-v1.5 | 1024 | Best quality |
| @cf/baai/bge-small-en-v1.5 | 384 | Faster |
Usage Examples
interface Env {
AI: Ai;
}
// Text generation
const response = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "What is Cloudflare?" },
],
max_tokens: 512,
temperature: 0.7,
});
// Streaming
const stream = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [...],
stream: true,
});
return new Response(stream, {
headers: { "Content-Type": "text/event-stream" },
});
// Text-to-Speech
const audio = await env.AI.run("@deepgram/aura-2-en", {
text: "Hello, this is a test.",
});
return new Response(audio, {
headers: { "Content-Type": "audio/wav" },
});
// Speech-to-Text
const transcript = await env.AI.run("@cf/openai/whisper-large-v3-turbo", {
audio: audioArrayBuffer,
});
// Returns { text: "...", segments: [...] }
// Image generation
const image = await env.AI.run("@cf/black-forest-labs/flux-1-schnell", {
prompt: "A futuristic cityscape at sunset",
num_steps: 4,
});
return new Response(image, {
headers: { "Content-Type": "image/png" },
});
// Embeddings
const embeddings = await env.AI.run("@cf/baai/bge-large-en-v1.5", {
text: ["Hello world", "Cloudflare Workers"],
});
// Returns { data: [{ embedding: [...] }, { embedding: [...] }] }
// Image captioning
const caption = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: imageArrayBuffer,
prompt: "Describe this image in detail.",
});MCP Servers on Workers
Building an MCP server on Workers:
import { McpServer } from "@cloudflare/mcp-server";
interface Env {
DB: D1Database;
}
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
});
// Define tools
server.addTool({
name: "query_database",
description: "Query the D1 database",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "SQL query to execute" },
},
required: ["query"],
},
handler: async ({ query }, { env }) => {
const result = await env.DB.prepare(query).all();
return {
content: [{ type: "text", text: JSON.stringify(result.results) }],
};
},
});
// Define resources
server.addResource({
uri: "db://tables",
name: "Database Tables",
description: "List of all tables",
handler: async ({ env }) => {
const tables = await env.DB.prepare(
"SELECT name FROM sqlite_master WHERE type='table'"
).all();
return {
contents: [{ uri: "db://tables", text: JSON.stringify(tables.results) }],
};
},
});
export default {
async fetch(request: Request, env: Env) {
return server.handleRequest(request, env);
},
};MCP Transport Types
1. Streamable HTTP (Recommended, March 2025+) — Single HTTP endpoint, bidirectional messaging, standard for remote MCP 2. stdio (Local only) — Standard input/output, for local MCP connections 3. SSE (Deprecated) — Use Streamable HTTP instead
Cloudflare's Managed MCP Servers
Available at https://mcp.cloudflare.com/:
- Workers management
- R2 bucket operations
- D1 database queries
- DNS management
- Analytics access
Connect from Claude/Cursor:
{
"mcpServers": {
"cloudflare": {
"url": "https://mcp.cloudflare.com/sse",
"transport": "sse"
}
}
}For detailed MCP server development guidance, see mcp-server-development.md.
Zero Trust: Cloudflare Tunnel
Expose internal services securely without opening firewall ports.
Installation:
# macOS
brew install cloudflared
# Windows
winget install Cloudflare.cloudflared
# Linux
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
sudo chmod +x cloudflared && sudo mv cloudflared /usr/local/bin/Setup:
# Login
cloudflared tunnel login
# Create tunnel
cloudflared tunnel create my-tunnel
# Create config file (~/.cloudflared/config.yml)
cat << EOF > ~/.cloudflared/config.yml
tunnel: <tunnel-id>
credentials-file: $HOME/.cloudflared/<tunnel-id>.json
ingress:
- hostname: app.example.com
service: http://localhost:3000
- hostname: api.example.com
service: http://localhost:8080
- service: http_status:404
EOF
# Add DNS
cloudflared tunnel route dns my-tunnel app.example.com
# Run
cloudflared tunnel run my-tunnelRun as Service:
# Linux
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
# macOS
sudo cloudflared service install
sudo launchctl load /Library/LaunchDaemons/com.cloudflare.cloudflared.plistAccess Policies
Configure in Cloudflare dashboard (Zero Trust > Access > Applications):
Application:
name: Internal App
type: Self-hosted
domain: app.example.com
Policy:
name: Allow Company
action: Allow
include:
- email_domain: company.com
require:
- country: USWARP Client
- Device client for Zero Trust enrollment
- Routes traffic through Cloudflare network
- Enables identity-based access policies
- Split tunneling for selective routing
For deeper Zero Trust setup, see zero-trust-setup.md.
Cloudflare Workers AI Cost Comparison Guide (2025-2026)
TTS (Text-to-Speech) Cost Comparison
Cost Per 1,000 Characters
| Provider | Model | Cost/1K chars | Quality | Latency | Languages |
|---|---|---|---|---|---|
| Cloudflare | MeloTTS | ~$0.0002/min* | Good | Fast | 6 (en,fr,es,zh,ja,ko) |
| Cloudflare | Aura-1 | $0.015 | Good | Fast | English |
| Cloudflare | Aura-2 | $0.030 | Excellent | Fast | English, Spanish |
| OpenAI | tts-1 | $0.015 | Good | Fast | Multi |
| OpenAI | tts-1-hd | $0.030 | Excellent | Medium | Multi |
| fal.ai | Kokoro | $0.020 | Good | Fast | English |
| fal.ai | F5-TTS | $0.050 | Good | Medium | Multi (clone) |
| fal.ai | ElevenLabs | $0.100 | Premium | Medium | 29 |
| ElevenLabs | Turbo v2.5 | ~$0.083** | Premium | ~75ms | 32 |
| ElevenLabs | Multilingual v2 | ~$0.165** | Premium | ~150ms | 29 |
MeloTTS is priced per audio minute ($0.0002/min), not characters *ElevenLabs direct pricing varies by subscription plan
Monthly Cost Estimates (100K characters/day)
| Provider | Model | Monthly Cost | Notes |
|---|---|---|---|
| Cloudflare MeloTTS | ~$0.60 | Based on ~100 min audio/month | |
| Cloudflare Aura-1 | $45 | ||
| Cloudflare Aura-2 | $90 | Best quality on Cloudflare | |
| OpenAI tts-1 | $45 | ||
| OpenAI tts-1-hd | $90 | ||
| fal.ai Kokoro | $60 | No subscription | |
| fal.ai ElevenLabs | $300 | Premium quality, no subscription | |
| ElevenLabs Pro | $99 | 500K chars included | |
| ElevenLabs Scale | $330 | 2M chars included |
Cost Decision Tree
Is English sufficient?
├── Yes → Is premium voice quality required?
│ ├── Yes → Cloudflare Aura-2 ($0.030/1K)
│ └── No → Cloudflare Aura-1 ($0.015/1K)
└── No → Is voice cloning required?
├── Yes → ElevenLabs or F5-TTS
└── No → Do you need 6+ languages?
├── Yes → ElevenLabs Multilingual
└── No → Cloudflare MeloTTS ($0.0002/min)---
LLM Inference Cost Comparison
Cost Per 1M Tokens (Input)
| Provider | Model | Input/1M | Output/1M | Context | Notes |
|---|---|---|---|---|---|
| Cloudflare | Llama 3.3 70B | $0.27 | $0.27 | 128K | Best value large |
| Cloudflare | Llama 3.1 8B | $0.05 | $0.05 | 128K | Fast, cheap |
| Cloudflare | DeepSeek-R1-Distill | $0.14 | $0.14 | 64K | Reasoning |
| Cloudflare | Qwen 2.5 72B | $0.35 | $0.35 | 128K | Multilingual |
| OpenAI | GPT-4o | $2.50 | $10.00 | 128K | Premium |
| OpenAI | GPT-4o-mini | $0.15 | $0.60 | 128K | Balanced |
| OpenAI | GPT-3.5-turbo | $0.50 | $1.50 | 16K | Legacy |
| Anthropic | Claude 3.5 Sonnet | $3.00 | $15.00 | 200K | Premium |
| Anthropic | Claude 3 Haiku | $0.25 | $1.25 | 200K | Fast |
Cloudflare Neuron Pricing
Cloudflare charges $0.011 per 1,000 Neurons with 10,000 free daily neurons.
Converting to familiar terms:
- Llama 3.3 70B: ~24,545 neurons per 1M tokens
- Llama 3.1 8B: ~4,545 neurons per 1M tokens
- Whisper: ~472 neurons per minute
---
STT (Speech-to-Text) Cost Comparison
Cost Per Minute of Audio
| Provider | Model | Cost/min | Quality | Languages |
|---|---|---|---|---|
| Cloudflare | Whisper Large v3 Turbo | $0.0052 | Excellent | 100+ |
| Cloudflare | Deepgram Nova-3 | $0.0052 | Excellent | Multi |
| Cloudflare | Deepgram Nova-3 WS | $0.0092 | Excellent | Multi |
| OpenAI | Whisper | $0.006 | Excellent | 100+ |
| Deepgram | Nova-2 | $0.0043 | Excellent | Multi |
| AssemblyAI | Universal | $0.0065 | Excellent | Multi |
Monthly Cost Estimates (100 hours of audio)
| Provider | Model | Monthly Cost |
|---|---|---|
| Cloudflare Whisper | $31.20 | |
| Cloudflare Deepgram | $31.20 | |
| OpenAI Whisper | $36.00 | |
| Deepgram Nova-2 | $25.80 |
---
Image Generation Cost Comparison
Cost Per Image
| Provider | Model | Cost/image | Resolution | Speed |
|---|---|---|---|---|
| Cloudflare | FLUX.1 Schnell | ~$0.01 | 1024x1024 | Fast (4 steps) |
| Cloudflare | SDXL | ~$0.02 | 1024x1024 | Medium (20 steps) |
| OpenAI | DALL-E 3 | $0.04-0.12 | 1024x1024 | Medium |
| fal.ai | FLUX.2 | $0.025 | 1024x1024 | Fast |
| fal.ai | SDXL | $0.01 | 1024x1024 | Medium |
---
Embedding Cost Comparison
Cost Per 1M Tokens
| Provider | Model | Cost/1M | Dimensions | Quality |
|---|---|---|---|---|
| Cloudflare | BGE Large | ~$0.01 | 1024 | Best |
| Cloudflare | BGE Base | ~$0.008 | 768 | Good |
| Cloudflare | BGE Small | ~$0.005 | 384 | Fast |
| OpenAI | text-embedding-3-large | $0.13 | 3072 | Best |
| OpenAI | text-embedding-3-small | $0.02 | 1536 | Good |
| Cohere | embed-v3 | $0.10 | 1024 | Best |
---
Platform Costs
Cloudflare Workers
| Resource | Free | Paid ($5/month) |
|---|---|---|
| Requests | 100K/day | 10M/month |
| CPU time | 10ms | 30s |
| Workers AI neurons | None | 10K/day free |
| KV reads | 100K/day | 10M/month |
| KV writes | 1K/day | 1M/month |
| R2 storage | 10GB | 10GB free, $0.015/GB |
| R2 operations | 1M Class A | 10M Class A |
| D1 rows read | 5M/day | 25B/month |
| D1 rows written | 100K/day | 50M/month |
Durable Objects
| Resource | Cost |
|---|---|
| Requests | $0.15/million |
| Duration | $12.50/million GB-s |
| WebSocket messages | $0.15/million |
WebSocket Cost Example:
- 1M concurrent connections for 1 hour
- ~$11,500 (128MB × 3600s × 1M / 1B × $12.50)
---
Cost Optimization Strategies
1. Use Cloudflare Native When Possible
Savings potential: 50-90%
ElevenLabs Multilingual: $0.165/1K chars
Cloudflare Aura-2: $0.030/1K chars
Savings: 82%2. Cache Aggressively with R2
For TTS with R2 caching:
- R2 storage: $0.015/GB/month
- R2 Class A ops: $4.50/million
- R2 Class B ops: $0.36/million
Break-even analysis:
- 1KB audio file
- Cloudflare Aura-2: $0.030 to generate
- R2 retrieval: $0.00000036
- After 1 cache hit, you save $0.029+
3. Use AI Gateway for Third-Party APIs
Benefits:
- Response caching (reduce API calls by 30-50%)
- Request logging (100K free, $0.60/million after)
- Rate limiting (prevent overage charges)
4. Right-Size Your Models
| Use Case | Recommended Model | Why |
|---|---|---|
| Simple Q&A | Llama 3.1 8B | 5x cheaper than 70B |
| Complex reasoning | DeepSeek-R1-Distill | Better than 70B for logic |
| Multilingual | Qwen 2.5 72B | Best non-English |
| English TTS | Aura-1 | 50% cheaper than Aura-2 |
| Multilingual TTS | MeloTTS | 100x cheaper than ElevenLabs |
5. Batch Requests
Embeddings example:
// Bad: 100 separate requests
for (const text of texts) {
await env.AI.run("@cf/baai/bge-large-en-v1.5", { text });
}
// Good: 1 batched request
await env.AI.run("@cf/baai/bge-large-en-v1.5", { text: texts });6. Use Queue for Non-Real-Time Tasks
Queue pricing:
- $0.40/million operations
- No CPU time limits
Move expensive AI tasks to queues:
// Worker receives request
await env.AI_QUEUE.send({ task: "generate-audio", text });
return new Response("Accepted", { status: 202 });
// Queue consumer processes without timeout pressure---
Cost Monitoring
Cloudflare Dashboard
1. Workers & Pages → Usage
- Requests, CPU time, duration
2. AI → Workers AI
- Neurons consumed per model
3. AI Gateway → Analytics
- Third-party API costs
Setting Alerts
# Use Cloudflare API to check usage
curl -X GET "https://api.cloudflare.com/client/v4/accounts/{account_id}/ai/usage" \
-H "Authorization: Bearer {api_token}"Cost Estimation Formula
Monthly Cost =
(Requests × $0.30/million) +
(AI Neurons × $0.011/1000) +
(R2 Storage × $0.015/GB) +
(R2 Ops × $4.50/million Class A) +
(D1 Reads × $0.001/million) +
(External API calls × provider rate)---
Summary: When to Use What
| Scenario | First Choice | Fallback | Avoid |
|---|---|---|---|
| English TTS | Cloudflare Aura | OpenAI tts-1 | ElevenLabs (cost) |
| Premium TTS | ElevenLabs | OpenAI tts-1-hd | - |
| Multilingual TTS | MeloTTS | ElevenLabs | OpenAI (quality) |
| Voice cloning | ElevenLabs | F5-TTS | - |
| Real-time voice | ElevenLabs Turbo | Cloudflare Aura | Multilingual v2 |
| Transcription | Cloudflare Whisper | Deepgram | AssemblyAI (cost) |
| LLM (cheap) | Llama 3.1 8B | GPT-4o-mini | GPT-4o |
| LLM (quality) | Llama 3.3 70B | Claude 3.5 | - |
| Embeddings | Cloudflare BGE | OpenAI small | OpenAI large |
| Images | FLUX.1 Schnell | SDXL | DALL-E 3 (cost) |
MCP Server Development on Cloudflare
Overview
Model Context Protocol (MCP) is an open standard that connects AI systems with external applications. Cloudflare supports building and deploying MCP servers on Workers, enabling AI assistants to interact with your services.
Core Concepts
MCP Components
1. MCP Hosts: AI assistants (Claude, Cursor, custom agents) that need external capabilities 2. MCP Clients: Clients embedded within hosts that connect to MCP servers 3. MCP Servers: Applications that expose tools, prompts, and resources
Transport Types
1. Streamable HTTP (Recommended for remote, March 2025+)
- Single HTTP endpoint for bidirectional messaging
- Standard for remote MCP connections
2. stdio (Local only)
- Standard input/output communication
- For local MCP connections
3. SSE (Deprecated)
- Server-Sent Events
- Legacy, use Streamable HTTP instead
---
Building MCP Server on Workers
Basic Setup
npm create cloudflare@latest my-mcp-server
cd my-mcp-server
npm install @cloudflare/mcp-serverwrangler.jsonc
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-mcp-server",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"compatibility_flags": ["nodejs_compat"]
}Basic MCP Server
import { McpServer } from "@cloudflare/mcp-server";
interface Env {
// Your bindings
}
const server = new McpServer({
name: "my-mcp-server",
version: "1.0.0",
description: "My custom MCP server for Cloudflare services",
});
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return server.handleRequest(request, env, ctx);
},
};---
Defining Tools
Tools are functions that AI assistants can invoke.
Basic Tool
server.addTool({
name: "get_current_time",
description: "Get the current UTC time",
parameters: {
type: "object",
properties: {},
},
handler: async () => {
return {
content: [
{
type: "text",
text: new Date().toISOString(),
},
],
};
},
});Tool with Parameters
server.addTool({
name: "search_products",
description: "Search for products in the catalog",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query",
},
category: {
type: "string",
description: "Product category filter",
enum: ["electronics", "clothing", "books", "home"],
},
limit: {
type: "number",
description: "Maximum number of results",
default: 10,
},
},
required: ["query"],
},
handler: async ({ query, category, limit }, { env }) => {
// Access your D1 database or other services
const products = await searchProducts(env.DB, query, category, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(products, null, 2),
},
],
};
},
});Tool with Complex Response
server.addTool({
name: "analyze_image",
description: "Analyze an image and return description",
parameters: {
type: "object",
properties: {
url: {
type: "string",
description: "URL of the image to analyze",
},
},
required: ["url"],
},
handler: async ({ url }, { env }) => {
// Fetch image
const response = await fetch(url);
const imageData = await response.arrayBuffer();
// Analyze with Workers AI
const analysis = await env.AI.run("@cf/meta/llama-3.2-11b-vision-instruct", {
image: imageData,
prompt: "Describe this image in detail.",
});
return {
content: [
{
type: "text",
text: analysis.response,
},
],
isError: false,
};
},
});---
Defining Resources
Resources are data sources that AI assistants can read.
Static Resource
server.addResource({
uri: "config://app-settings",
name: "Application Settings",
description: "Current application configuration",
mimeType: "application/json",
handler: async () => {
return {
contents: [
{
uri: "config://app-settings",
text: JSON.stringify({
version: "1.0.0",
features: ["search", "analytics", "export"],
}),
},
],
};
},
});Dynamic Resource
server.addResource({
uri: "db://users/{id}",
name: "User Profile",
description: "Get user profile by ID",
mimeType: "application/json",
handler: async ({ uri }, { env }) => {
const id = uri.split("/").pop();
const user = await env.DB.prepare(
"SELECT * FROM users WHERE id = ?"
).bind(id).first();
return {
contents: [
{
uri,
text: JSON.stringify(user),
},
],
};
},
});List Resources
server.addResource({
uri: "db://tables",
name: "Database Tables",
description: "List all tables in the database",
handler: async ({ env }) => {
const tables = await env.DB.prepare(
"SELECT name FROM sqlite_master WHERE type='table'"
).all();
return {
contents: [
{
uri: "db://tables",
text: JSON.stringify(tables.results),
},
],
};
},
});---
Defining Prompts
Prompts are reusable templates for AI interactions.
server.addPrompt({
name: "summarize_data",
description: "Summarize data from the database",
arguments: [
{
name: "table",
description: "Table name to summarize",
required: true,
},
{
name: "format",
description: "Output format (brief, detailed)",
required: false,
},
],
handler: async ({ table, format = "brief" }, { env }) => {
const data = await env.DB.prepare(`SELECT * FROM ${table} LIMIT 100`).all();
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Please provide a ${format} summary of this data:\n\n${JSON.stringify(data.results)}`,
},
},
],
};
},
});---
OAuth Authorization
MCP uses OAuth 2.1 for authorization.
Setup OAuth Provider
import { McpServer, OAuthProvider } from "@cloudflare/mcp-server";
const oauth = new OAuthProvider({
clientId: "your-client-id",
clientSecret: env.OAUTH_CLIENT_SECRET,
authorizationEndpoint: "https://auth.example.com/authorize",
tokenEndpoint: "https://auth.example.com/token",
scopes: ["read", "write"],
});
const server = new McpServer({
name: "my-secure-mcp-server",
version: "1.0.0",
oauth,
});
// Tools will have access to the authenticated user
server.addTool({
name: "get_my_data",
description: "Get data for the authenticated user",
handler: async (params, { env, user }) => {
// user contains OAuth user info
const data = await fetchUserData(user.id);
return {
content: [{ type: "text", text: JSON.stringify(data) }],
};
},
});Using Cloudflare Access
// Verify Cloudflare Access JWT
async function verifyAccess(request: Request, env: Env) {
const jwt = request.headers.get("CF-Access-JWT-Assertion");
if (!jwt) return null;
// Verify with Access public keys
const response = await fetch(
`https://${env.TEAM_DOMAIN}/cdn-cgi/access/certs`
);
const certs = await response.json();
// Verify JWT signature and claims
// ... verification logic
return decodedToken;
}---
Complete Example: Database MCP Server
import { McpServer } from "@cloudflare/mcp-server";
interface Env {
DB: D1Database;
AI: Ai;
}
const server = new McpServer({
name: "database-mcp",
version: "1.0.0",
description: "MCP server for database operations",
});
// Tool: Query database
server.addTool({
name: "query_database",
description: "Execute a read-only SQL query",
parameters: {
type: "object",
properties: {
query: {
type: "string",
description: "SQL query (SELECT only)",
},
},
required: ["query"],
},
handler: async ({ query }, { env }) => {
// Security: Only allow SELECT
if (!query.trim().toUpperCase().startsWith("SELECT")) {
return {
content: [{ type: "text", text: "Error: Only SELECT queries allowed" }],
isError: true,
};
}
try {
const result = await env.DB.prepare(query).all();
return {
content: [
{
type: "text",
text: JSON.stringify(result.results, null, 2),
},
],
};
} catch (error) {
return {
content: [{ type: "text", text: `Error: ${error.message}` }],
isError: true,
};
}
},
});
// Tool: Describe table
server.addTool({
name: "describe_table",
description: "Get schema information for a table",
parameters: {
type: "object",
properties: {
table: {
type: "string",
description: "Table name",
},
},
required: ["table"],
},
handler: async ({ table }, { env }) => {
const schema = await env.DB.prepare(
`PRAGMA table_info(${table})`
).all();
return {
content: [
{
type: "text",
text: JSON.stringify(schema.results, null, 2),
},
],
};
},
});
// Tool: Generate SQL
server.addTool({
name: "generate_sql",
description: "Generate SQL query from natural language",
parameters: {
type: "object",
properties: {
description: {
type: "string",
description: "Natural language description of the query",
},
},
required: ["description"],
},
handler: async ({ description }, { env }) => {
// Get schema context
const tables = await env.DB.prepare(
"SELECT name FROM sqlite_master WHERE type='table'"
).all();
const schemaInfo = [];
for (const table of tables.results) {
const schema = await env.DB.prepare(
`PRAGMA table_info(${table.name})`
).all();
schemaInfo.push({ table: table.name, columns: schema.results });
}
// Use AI to generate SQL
const response = await env.AI.run("@cf/meta/llama-3.3-70b-instruct-fp8-fast", {
messages: [
{
role: "system",
content: `You are a SQL expert. Generate a SQLite query based on the user's request.
Schema:
${JSON.stringify(schemaInfo, null, 2)}
Only respond with the SQL query, no explanation.`,
},
{
role: "user",
content: description,
},
],
});
return {
content: [{ type: "text", text: response.response }],
};
},
});
// Resource: List tables
server.addResource({
uri: "db://tables",
name: "Database Tables",
description: "List all tables in the database",
handler: async ({ env }) => {
const tables = await env.DB.prepare(
"SELECT name FROM sqlite_master WHERE type='table'"
).all();
return {
contents: [
{
uri: "db://tables",
text: JSON.stringify(tables.results.map((t) => t.name)),
},
],
};
},
});
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
return server.handleRequest(request, env, ctx);
},
};---
Connecting to MCP Server
From Claude Desktop
// ~/.claude/config.json
{
"mcpServers": {
"my-server": {
"url": "https://my-mcp-server.your-subdomain.workers.dev",
"transport": "streamable-http"
}
}
}From Cursor
// .cursor/mcp.json
{
"servers": {
"my-server": {
"url": "https://my-mcp-server.your-subdomain.workers.dev",
"transport": "streamable-http"
}
}
}Cloudflare's Managed MCP Servers
Connect to Cloudflare's built-in MCP servers:
{
"mcpServers": {
"cloudflare": {
"url": "https://mcp.cloudflare.com/sse",
"transport": "sse"
}
}
}Available tools:
- Workers management (deploy, list, logs)
- R2 bucket operations
- D1 database queries
- DNS management
- Analytics access
---
Best Practices
Security
1. Validate all inputs - Never trust user/AI input 2. Use OAuth for authenticated access 3. Limit tool permissions - Only allow necessary operations 4. Log all tool invocations for audit
Performance
1. Use caching for frequently accessed resources 2. Set timeouts for external API calls 3. Batch operations when possible
Error Handling
server.addTool({
name: "risky_operation",
handler: async (params, { env }) => {
try {
const result = await riskyOperation(params);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
isError: false,
};
} catch (error) {
// Return structured error
return {
content: [
{
type: "text",
text: JSON.stringify({
error: error.message,
code: error.code || "UNKNOWN",
suggestion: "Try with different parameters",
}),
},
],
isError: true,
};
}
},
});---
Testing
Local Testing
npx wrangler dev
# In another terminal
curl -X POST http://localhost:8787/mcp \
-H "Content-Type: application/json" \
-d '{"method": "tools/list"}'MCP Inspector
Use the MCP Inspector tool for interactive testing:
npx @modelcontextprotocol/inspector http://localhost:8787---
Deployment
# Deploy
npx wrangler deploy
# Your MCP server is now available at:
# https://my-mcp-server.your-subdomain.workers.devCloudflare Storage Services Deep Dive
Reference for KV, R2, D1, Durable Objects, Queues, and Hyperdrive — characteristics, APIs, and patterns.
KV (Key-Value Store)
Characteristics:
- Eventually consistent (up to 60s propagation)
- Max value size: 25 MiB
- Max key size: 512 bytes
- Best for: Configuration, session data, caching
- Free tier: 100,000 reads/day, 1,000 writes/day
interface Env {
MY_KV: KVNamespace;
}
// Write operations
await env.MY_KV.put("key", "string value");
await env.MY_KV.put("key", JSON.stringify(object));
await env.MY_KV.put("key", arrayBuffer);
// With TTL (seconds)
await env.MY_KV.put("session", data, { expirationTtl: 3600 });
// With absolute expiration
await env.MY_KV.put("session", data, { expiration: Math.floor(Date.now() / 1000) + 3600 });
// With metadata
await env.MY_KV.put("user:123", userData, {
metadata: { type: "user", version: 2 }
});
// Read operations
const value = await env.MY_KV.get("key"); // Returns string or null
const json = await env.MY_KV.get("key", "json"); // Parses JSON
const buffer = await env.MY_KV.get("key", "arrayBuffer");
const stream = await env.MY_KV.get("key", "stream");
// With metadata
const { value, metadata } = await env.MY_KV.getWithMetadata("key");
// List keys
const list = await env.MY_KV.list();
const filtered = await env.MY_KV.list({ prefix: "user:", limit: 100 });
// Pagination: use list.cursor for next page
// Delete
await env.MY_KV.delete("key");R2 (Object Storage)
Characteristics:
- S3-compatible API
- Zero egress fees
- Max object size: 5 TB
- Single upload max: 5 GB (use multipart for larger)
- Best for: Media files, backups, data lakes, large files
interface Env {
MY_BUCKET: R2Bucket;
}
// Put object
await env.MY_BUCKET.put("path/to/file.json", JSON.stringify(data), {
httpMetadata: {
contentType: "application/json",
cacheControl: "max-age=3600",
},
customMetadata: {
uploadedBy: "worker",
version: "1.0",
},
});
// Put with checksums
await env.MY_BUCKET.put("file.bin", data, {
md5: expectedMd5, // Validates on upload
sha256: expectedSha256,
});
// Get object
const object = await env.MY_BUCKET.get("path/to/file.json");
if (object) {
const text = await object.text();
const json = await object.json();
const buffer = await object.arrayBuffer();
const blob = await object.blob();
const stream = object.body; // ReadableStream
// Metadata
console.log(object.key, object.size, object.etag);
console.log(object.httpMetadata.contentType);
console.log(object.customMetadata.uploadedBy);
}
// Head (metadata only)
const head = await env.MY_BUCKET.head("path/to/file.json");
// List objects
const list = await env.MY_BUCKET.list();
const filtered = await env.MY_BUCKET.list({
prefix: "uploads/",
delimiter: "/",
limit: 1000,
});
// Delete
await env.MY_BUCKET.delete("path/to/file.json");
await env.MY_BUCKET.delete(["file1.json", "file2.json"]); // Batch delete
// Multipart upload (for files > 5GB)
const upload = await env.MY_BUCKET.createMultipartUpload("large-file.zip");
const part1 = await upload.uploadPart(1, chunk1);
const part2 = await upload.uploadPart(2, chunk2);
await upload.complete([part1, part2]);
// Or abort
await upload.abort();D1 (SQLite Database)
Characteristics:
- Serverless SQLite
- Strong consistency
- Max database size: 10 GB (GA)
- Best for: Relational data, complex queries, ACID transactions
interface Env {
DB: D1Database;
}
// Prepared statements (recommended)
const stmt = env.DB.prepare("SELECT * FROM users WHERE id = ?");
const { results } = await stmt.bind(userId).all();
const user = await stmt.bind(userId).first();
const value = await stmt.bind(userId).first("name"); // Single column
// Insert/Update
const { meta } = await env.DB.prepare(
"INSERT INTO users (name, email) VALUES (?, ?)"
).bind(name, email).run();
console.log(meta.last_row_id, meta.changes);
// Batch operations (single transaction)
const results = await env.DB.batch([
env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Alice"),
env.DB.prepare("INSERT INTO users (name) VALUES (?)").bind("Bob"),
env.DB.prepare("UPDATE counters SET value = value + 1 WHERE name = 'users'"),
]);
// Raw execution
await env.DB.exec("PRAGMA table_info(users)");
// Transaction pattern (using batch)
await env.DB.batch([
env.DB.prepare("UPDATE accounts SET balance = balance - ? WHERE id = ?").bind(100, fromId),
env.DB.prepare("UPDATE accounts SET balance = balance + ? WHERE id = ?").bind(100, toId),
]);D1 Best Practices
-- Create indexes for WHERE clause columns
CREATE INDEX idx_users_email ON users(email);
-- Use EXPLAIN QUERY PLAN to verify index usage
EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = 'test@example.com';
-- Batch large migrations
DELETE FROM logs WHERE created_at < '2024-01-01' LIMIT 1000;
-- Run after schema changes
PRAGMA optimize;Durable Objects
Characteristics:
- Single-threaded, globally unique instances
- Built-in SQLite storage
- WebSocket support with Hibernation
- Best for: Real-time coordination, chat, games, counters
// Durable Object class
export class Counter {
state: DurableObjectState;
value: number = 0;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
// Restore state from storage
this.state.blockConcurrencyWhile(async () => {
this.value = (await this.state.storage.get("value")) || 0;
});
}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case "/increment":
this.value++;
await this.state.storage.put("value", this.value);
return Response.json({ value: this.value });
case "/value":
return Response.json({ value: this.value });
default:
return new Response("Not found", { status: 404 });
}
}
}
// Worker that uses the Durable Object
export default {
async fetch(request: Request, env: Env) {
const id = env.COUNTER.idFromName("global");
const stub = env.COUNTER.get(id);
return stub.fetch(request);
},
};WebSocket Hibernation
export class ChatRoom {
state: DurableObjectState;
constructor(state: DurableObjectState, env: Env) {
this.state = state;
}
async fetch(request: Request): Promise<Response> {
if (request.headers.get("Upgrade") === "websocket") {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
// Use Hibernation API
this.state.acceptWebSocket(server);
return new Response(null, { status: 101, webSocket: client });
}
return new Response("Expected WebSocket", { status: 400 });
}
// Called when hibernated DO receives WebSocket message
async webSocketMessage(ws: WebSocket, message: string | ArrayBuffer) {
// Broadcast to all connected clients
for (const client of this.state.getWebSockets()) {
if (client !== ws && client.readyState === WebSocket.READY_STATE_OPEN) {
client.send(message);
}
}
}
async webSocketClose(ws: WebSocket, code: number, reason: string) {
// Handle disconnect
}
async webSocketError(ws: WebSocket, error: unknown) {
// Handle error
ws.close(1011, "Internal error");
}
}Queues
Characteristics:
- Async message processing
- At-least-once delivery
- Automatic retries with dead letter queues
- Best for: Decoupling, background jobs, event processing
// Producer
interface Env {
MY_QUEUE: Queue;
}
export default {
async fetch(request: Request, env: Env) {
// Send single message
await env.MY_QUEUE.send({ type: "email", to: "user@example.com" });
// Send with options
await env.MY_QUEUE.send(
{ type: "process", id: 123 },
{ contentType: "json" }
);
// Batch send
await env.MY_QUEUE.sendBatch([
{ body: { id: 1 } },
{ body: { id: 2 } },
{ body: { id: 3 } },
]);
return new Response("Queued");
},
};
// Consumer
interface QueueMessage {
type: string;
id?: number;
to?: string;
}
export default {
async queue(batch: MessageBatch<QueueMessage>, env: Env): Promise<void> {
for (const message of batch.messages) {
try {
console.log(`Processing: ${JSON.stringify(message.body)}`);
await processMessage(message.body);
message.ack(); // Mark as processed
} catch (e) {
console.error(`Failed: ${e}`);
message.retry(); // Will retry (up to max_retries)
}
}
},
};Hyperdrive
Hyperdrive accelerates database connections by maintaining connection pools close to your database.
Setup
# Create Hyperdrive config
npx wrangler hyperdrive create my-db \
--connection-string="postgres://user:pass@host:5432/database"
# Add to wrangler.jsoncUsage
import { Client } from "pg";
interface Env {
MY_DB: Hyperdrive;
}
export default {
async fetch(request: Request, env: Env) {
// Connect using Hyperdrive connection string
const client = new Client({
connectionString: env.MY_DB.connectionString,
});
await client.connect();
const result = await client.query("SELECT * FROM users WHERE id = $1", [1]);
// No need to call client.end() - Hyperdrive manages pooling
return Response.json(result.rows);
},
};When to Use Hyperdrive
Use Hyperdrive when:
- Connecting to remote PostgreSQL/MySQL databases
- High-latency database connections (different regions)
- Frequent identical read queries (caching)
- Many concurrent database connections needed
Don't use Hyperdrive when:
- Using D1 (already edge-native)
- Local development (use direct connection)
- Need prepared statements across requests (transaction mode limitation)
- Using Durable Objects storage
Performance Benefits
Without Hyperdrive:
Worker -> TCP handshake (1 RTT)
-> TLS negotiation (3 RTTs)
-> DB authentication (3 RTTs)
-> Query (1 RTT)
Total: 8 round-trips before first queryWith Hyperdrive:
Worker -> Hyperdrive pool (cached connection)
-> Query (1 RTT to pool, reuses DB connection)
Total: 1 round-trip to queryThird-Party AI Service Integrations with Cloudflare Workers
Overview
Cloudflare Workers can integrate with external AI services like ElevenLabs, OpenAI, Anthropic, and fal.ai. This guide covers integration patterns, cost comparisons, and gotchas.
---
ElevenLabs TTS Integration
Method 1: Direct API Integration
Call ElevenLabs API directly from Workers:
interface Env {
ELEVENLABS_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { text, voice_id = "JBFqnCBsd6RMkjVDRZzb" } = await request.json();
const response = await fetch(
`https://api.elevenlabs.io/v1/text-to-speech/${voice_id}?output_format=mp3_44100_128`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({
text,
model_id: "eleven_multilingual_v2",
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
},
}),
}
);
if (!response.ok) {
return Response.json(
{ error: await response.text() },
{ status: response.status }
);
}
return new Response(response.body, {
headers: {
"Content-Type": "audio/mpeg",
"Content-Disposition": "attachment; filename=speech.mp3",
},
});
},
};Method 2: Via Cloudflare AI Gateway
Route ElevenLabs requests through AI Gateway for caching, logging, and rate limiting:
interface Env {
ELEVENLABS_API_KEY: string;
CF_ACCOUNT_ID: string;
AI_GATEWAY_ID: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { text, voice_id = "JBFqnCBsd6RMkjVDRZzb" } = await request.json();
// Route through AI Gateway
const gatewayUrl = `https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/elevenlabs/v1/text-to-speech/${voice_id}?output_format=mp3_44100_128`;
const response = await fetch(gatewayUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({
text,
model_id: "eleven_multilingual_v2",
}),
});
return new Response(response.body, {
headers: { "Content-Type": "audio/mpeg" },
});
},
};AI Gateway Benefits:
- Request logging and analytics
- Response caching (reduce API costs)
- Rate limiting
- Fallback to alternative providers
- Unified billing dashboard
Method 3: ElevenLabs WebSocket Streaming
For real-time TTS with Durable Objects:
import { DurableObject } from "cloudflare:workers";
interface Env {
ELEVENLABS_API_KEY: string;
TTS_SESSION: DurableObjectNamespace;
}
export class TTSSession extends DurableObject {
private elevenLabsWs: WebSocket | null = null;
async fetch(request: Request): Promise<Response> {
if (request.headers.get("Upgrade") === "websocket") {
const pair = new WebSocketPair();
const [client, server] = Object.values(pair);
this.ctx.acceptWebSocket(server);
await this.connectToElevenLabs();
return new Response(null, { status: 101, webSocket: client });
}
return new Response("Expected WebSocket", { status: 400 });
}
private async connectToElevenLabs() {
const voiceId = "JBFqnCBsd6RMkjVDRZzb";
const modelId = "eleven_turbo_v2_5";
// ElevenLabs WebSocket endpoint
const wsUrl = `wss://api.elevenlabs.io/v1/text-to-speech/${voiceId}/stream-input?model_id=${modelId}&inactivity_timeout=60`;
this.elevenLabsWs = new WebSocket(wsUrl, {
headers: {
"xi-api-key": this.env.ELEVENLABS_API_KEY,
},
});
this.elevenLabsWs.addEventListener("message", (event) => {
// Forward audio chunks to connected clients
for (const ws of this.ctx.getWebSockets()) {
ws.send(event.data);
}
});
}
async webSocketMessage(ws: WebSocket, message: string) {
// Forward text to ElevenLabs
if (this.elevenLabsWs?.readyState === WebSocket.OPEN) {
this.elevenLabsWs.send(
JSON.stringify({
text: message,
try_trigger_generation: true,
})
);
}
}
async webSocketClose() {
// Send EOS to ElevenLabs
if (this.elevenLabsWs?.readyState === WebSocket.OPEN) {
this.elevenLabsWs.send(JSON.stringify({ text: "" })); // EOS signal
this.elevenLabsWs.close();
}
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const id = env.TTS_SESSION.idFromName("session");
const stub = env.TTS_SESSION.get(id);
return stub.fetch(request);
},
};Method 4: Cloudflare Realtime Agents SDK
For voice agents with ElevenLabs:
import {
DeepgramSTT,
ElevenLabsTTS,
RealtimeAgent,
} from "@cloudflare/realtime-agents";
export class VoiceAgent extends RealtimeAgent {
async onStart() {
// Pipeline: Audio -> STT -> LLM -> TTS -> Audio
await this.initPipeline([
this.transport,
new DeepgramSTT(this.env.DEEPGRAM_API_KEY),
this.textHandler.bind(this),
new ElevenLabsTTS(this.env.ELEVENLABS_API_KEY, {
voice_id: "JBFqnCBsd6RMkjVDRZzb",
model_id: "eleven_turbo_v2_5",
}),
this.transport,
]);
}
async textHandler(text: string): Promise<string> {
// Process with LLM
const response = await this.env.AI.run(
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
{
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: text },
],
}
);
return response.response;
}
}---
ElevenLabs + Cloudflare Workers AI LLM
Connect ElevenLabs Conversational AI agents to Cloudflare Workers AI as the LLM:
Configure in ElevenLabs Dashboard
1. Add Cloudflare API token as secret in AI Agent settings 2. Select "Custom LLM" from dropdown 3. Server URL: https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1/ 4. Model ID: @cf/deepseek-ai/deepseek-r1-distill-qwen-32b
Test the Connection
curl https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai/v1/chat/completions \
-X POST \
-H "Authorization: Bearer {API_TOKEN}" \
-d '{
"model": "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Hello, how are you?"}
],
"stream": false
}'Recommended models for ElevenLabs agents:
@cf/deepseek-ai/deepseek-r1-distill-qwen-32b- Strong reasoning, function calling@cf/meta/llama-3.3-70b-instruct-fp8-fast- General purpose, fast
---
fal.ai Integration
ElevenLabs via fal.ai
fal.ai provides ElevenLabs as a hosted service with simplified billing:
interface Env {
FAL_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { text, voice = "Rachel" } = await request.json();
const response = await fetch(
"https://fal.run/fal-ai/elevenlabs/tts/multilingual-v2",
{
method: "POST",
headers: {
Authorization: `Key ${env.FAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
text,
voice,
model_id: "eleven_multilingual_v2",
}),
}
);
const result = await response.json();
return Response.json(result);
},
};Other fal.ai TTS Options
// Kokoro TTS - $0.02/1K chars (cheapest quality option)
const kokoroResponse = await fetch(
"https://fal.run/fal-ai/kokoro/american-english",
{
method: "POST",
headers: {
Authorization: `Key ${env.FAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ text }),
}
);
// F5-TTS - $0.05/1K chars (zero-shot voice cloning)
const f5Response = await fetch("https://fal.run/fal-ai/f5-tts", {
method: "POST",
headers: {
Authorization: `Key ${env.FAL_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
gen_text: text,
ref_audio_url: "https://example.com/reference-voice.mp3",
}),
});---
OpenAI TTS Integration
interface Env {
OPENAI_API_KEY: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const { text, voice = "alloy" } = await request.json();
const response = await fetch("https://api.openai.com/v1/audio/speech", {
method: "POST",
headers: {
Authorization: `Bearer ${env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "tts-1", // or "tts-1-hd" for higher quality
input: text,
voice, // alloy, echo, fable, onyx, nova, shimmer
}),
});
return new Response(response.body, {
headers: { "Content-Type": "audio/mpeg" },
});
},
};---
Gotchas and Limitations
Cloudflare Workers Limits
| Limit | Free | Paid |
|---|---|---|
| CPU time | 10ms | 30s (soft), 15min (Cron) |
| Memory | 128MB | 128MB |
| Concurrent connections | 6 | 6 |
| Subrequest limit | 50 | 1000 |
| WebSocket duration | 100s | 100s (Enterprise: custom) |
ElevenLabs WebSocket Gotchas
1. Inactivity timeout: Default 20 seconds, max 180 seconds
- Send single space
" "to keep alive - Empty string
""sends EOS and closes connection
2. Workers WebSocket limitation: Cannot store WebSocket in global variable
- Use Durable Objects for persistent connections
3. Streaming buffer: Workers have 128MB memory limit
- Stream audio directly, don't buffer entire response
ElevenLabs API Gotchas
1. Rate limits: Vary by plan (check ElevenLabs dashboard) 2. Character counting: Includes spaces and punctuation 3. Model latency:
- Turbo v2.5: ~75ms (real-time)
- Multilingual v2: ~150-300ms (higher quality)
Cost Considerations
1. Cloudflare AI Gateway: Free logging, but 100K log limit on free tier 2. ElevenLabs via fal.ai: No subscription, pure pay-per-use 3. Direct ElevenLabs: Monthly minimums, better for high volume
---
Architecture Patterns
Pattern 1: Fallback Chain
Use AI Gateway to fallback between providers:
async function textToSpeech(text: string, env: Env): Promise<Response> {
// Try Cloudflare Aura first (cheapest)
try {
const audio = await env.AI.run("@deepgram/aura-2-en", { text });
return new Response(audio, {
headers: { "Content-Type": "audio/wav", "X-TTS-Provider": "cloudflare" },
});
} catch (e) {
console.log("Cloudflare TTS failed, trying ElevenLabs");
}
// Fallback to ElevenLabs
const response = await fetch(
`https://gateway.ai.cloudflare.com/v1/${env.CF_ACCOUNT_ID}/${env.AI_GATEWAY_ID}/elevenlabs/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({ text, model_id: "eleven_turbo_v2_5" }),
}
);
return new Response(response.body, {
headers: { "Content-Type": "audio/mpeg", "X-TTS-Provider": "elevenlabs" },
});
}Pattern 2: Quality-Based Routing
Route based on quality requirements:
type Quality = "fast" | "standard" | "premium";
async function selectTTSProvider(
text: string,
quality: Quality,
env: Env
): Promise<Response> {
switch (quality) {
case "fast":
// Cloudflare MeloTTS - cheapest, multilingual
return new Response(
await env.AI.run("@cf/myshell-ai/melotts", { text }),
{ headers: { "Content-Type": "audio/wav" } }
);
case "standard":
// Cloudflare Aura-2 - good quality, English
return new Response(
await env.AI.run("@deepgram/aura-2-en", { text }),
{ headers: { "Content-Type": "audio/wav" } }
);
case "premium":
// ElevenLabs - best quality, voice cloning
const response = await fetch(
"https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({
text,
model_id: "eleven_multilingual_v2",
}),
}
);
return new Response(response.body, {
headers: { "Content-Type": "audio/mpeg" },
});
}
}Pattern 3: Cache Audio with R2
Cache generated audio to reduce costs:
interface Env {
AI: Ai;
AUDIO_CACHE: R2Bucket;
ELEVENLABS_API_KEY: string;
}
async function getCachedOrGenerate(
text: string,
provider: string,
env: Env
): Promise<Response> {
// Create cache key from text hash
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(`${provider}:${text}`)
);
const cacheKey = Array.from(new Uint8Array(hash))
.map((b) => b.toString(16).padStart(2, "0"))
.join("");
// Check cache
const cached = await env.AUDIO_CACHE.get(cacheKey);
if (cached) {
return new Response(cached.body, {
headers: {
"Content-Type": cached.httpMetadata?.contentType || "audio/mpeg",
"X-Cache": "HIT",
},
});
}
// Generate audio
let audio: ArrayBuffer;
let contentType: string;
if (provider === "cloudflare") {
audio = await env.AI.run("@deepgram/aura-2-en", { text });
contentType = "audio/wav";
} else {
const response = await fetch(
"https://api.elevenlabs.io/v1/text-to-speech/JBFqnCBsd6RMkjVDRZzb",
{
method: "POST",
headers: {
"Content-Type": "application/json",
"xi-api-key": env.ELEVENLABS_API_KEY,
},
body: JSON.stringify({ text, model_id: "eleven_multilingual_v2" }),
}
);
audio = await response.arrayBuffer();
contentType = "audio/mpeg";
}
// Cache for 7 days
await env.AUDIO_CACHE.put(cacheKey, audio, {
httpMetadata: { contentType },
customMetadata: { text: text.substring(0, 100), provider },
});
return new Response(audio, {
headers: { "Content-Type": contentType, "X-Cache": "MISS" },
});
}---
Best Practices
1. Use AI Gateway for Third-Party APIs
- Centralizes logging and analytics
- Enables caching to reduce costs
- Provides rate limiting protection
- Simplifies fallback configuration
2. Stream Large Responses
- Don't buffer entire audio files in memory
- Use
response.bodystreams directly - Workers have 128MB memory limit
3. Cache Aggressively
- Audio generation is expensive
- Use R2 for long-term caching
- Use KV for metadata/mappings
4. Monitor Costs
- Set up billing alerts
- Track per-provider usage
- Use AI Gateway analytics
5. Handle Failures Gracefully
- Implement retry with exponential backoff
- Have fallback providers
- Return cached/default audio on failure
Wrangler CLI and Configuration Reference
Complete Wrangler CLI command reference and wrangler.jsonc configuration schema.
Project Setup
# Create new project
npm create cloudflare@latest my-worker
# Initialize in existing directory
npx wrangler init
# Login
npx wrangler login
npx wrangler whoamiDevelopment
# Local development
npx wrangler dev
npx wrangler dev --remote # Use remote bindings
npx wrangler dev --local # Fully local
# Test cron trigger locally
curl "http://localhost:8787/__scheduled?cron=*+*+*+*+*"Deployment
# Deploy to production
npx wrangler deploy
# Deploy to environment
npx wrangler deploy --env staging
# List versions
npx wrangler versions list
# Rollback
npx wrangler rollbackD1 Database
# Create database
npx wrangler d1 create my-database
# Execute SQL
npx wrangler d1 execute my-database --local --file=schema.sql
npx wrangler d1 execute my-database --remote --command="SELECT * FROM users"
# Interactive shell
npx wrangler d1 execute my-database --local --command=".tables"
# Export
npx wrangler d1 export my-database --remote --output=backup.sqlR2 Buckets
# Create bucket
npx wrangler r2 bucket create my-bucket
# List buckets
npx wrangler r2 bucket list
# Upload/download
npx wrangler r2 object put my-bucket/file.txt --file=local.txt
npx wrangler r2 object get my-bucket/file.txt --file=download.txt
# Delete
npx wrangler r2 object delete my-bucket/file.txtKV Namespaces
# Create namespace
npx wrangler kv namespace create MY_KV
npx wrangler kv namespace create MY_KV --preview # Preview namespace
# List namespaces
npx wrangler kv namespace list
# Key operations
npx wrangler kv key put --binding MY_KV key "value"
npx wrangler kv key get --binding MY_KV key
npx wrangler kv key list --binding MY_KV
npx wrangler kv key delete --binding MY_KV key
# Bulk upload
npx wrangler kv bulk put --binding MY_KV data.jsonSecrets
# Set secret
npx wrangler secret put API_KEY
# (prompts for value)
# List secrets
npx wrangler secret list
# Delete secret
npx wrangler secret delete API_KEYQueues
# Create queue
npx wrangler queues create my-queue
# List queues
npx wrangler queues listHyperdrive
# Create Hyperdrive config
npx wrangler hyperdrive create my-hyperdrive --connection-string="postgres://..."
# List configs
npx wrangler hyperdrive list
# Update
npx wrangler hyperdrive update my-hyperdrive --connection-string="postgres://..."Wrangler Configuration (wrangler.jsonc)
Complete configuration reference:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"compatibility_flags": ["nodejs_compat"],
// Account settings
"account_id": "<optional-account-id>",
// Build settings
"minify": true,
"node_compat": true,
// Environment variables
"vars": {
"API_URL": "https://api.example.com"
},
// KV Namespaces
"kv_namespaces": [
{
"binding": "MY_KV",
"id": "<namespace-id>",
"preview_id": "<preview-namespace-id>"
}
],
// R2 Buckets
"r2_buckets": [
{
"binding": "MY_BUCKET",
"bucket_name": "my-bucket",
"preview_bucket_name": "my-bucket-preview",
"jurisdiction": "eu"
}
],
// D1 Databases
"d1_databases": [
{
"binding": "DB",
"database_id": "<database-id>",
"database_name": "my-database"
}
],
// Durable Objects
"durable_objects": {
"bindings": [
{
"name": "MY_DO",
"class_name": "MyDurableObject"
}
]
},
"migrations": [
{
"tag": "v1",
"new_classes": ["MyDurableObject"]
},
{
"tag": "v2",
"new_sqlite_classes": ["MyDurableObjectWithSQL"]
}
],
// Queues
"queues": {
"producers": [
{
"binding": "MY_QUEUE",
"queue": "my-queue"
}
],
"consumers": [
{
"queue": "my-queue",
"max_batch_size": 10,
"max_batch_timeout": 30,
"max_retries": 3,
"dead_letter_queue": "my-dlq"
}
]
},
// Hyperdrive
"hyperdrive": [
{
"binding": "MY_DB_POOL",
"id": "<hyperdrive-config-id>"
}
],
// Workers AI
"ai": {
"binding": "AI"
},
// Vectorize
"vectorize": [
{
"binding": "MY_VECTORS",
"index_name": "my-index"
}
],
// Browser Rendering
"browser": {
"binding": "BROWSER"
},
// Service Bindings (Worker-to-Worker)
"services": [
{
"binding": "OTHER_WORKER",
"service": "other-worker-name"
}
],
// Cron Triggers
"triggers": {
"crons": ["0 * * * *", "0 6 * * *"]
},
// Routes
"routes": [
{
"pattern": "example.com/*",
"zone_name": "example.com"
}
],
// Observability
"observability": {
"logs": {
"enabled": true,
"invocation_logs": true,
"head_sampling_rate": 1
}
},
// Environments
"env": {
"staging": {
"name": "my-worker-staging",
"vars": {
"API_URL": "https://staging-api.example.com"
}
},
"production": {
"name": "my-worker-production",
"routes": [
{
"pattern": "api.example.com/*",
"zone_name": "example.com"
}
]
}
}
}CI/CD
GitHub Actions
name: Deploy Worker
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Deploy to Cloudflare
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}Workers Builds (Native Git Integration)
1. Connect GitHub/GitLab in Cloudflare dashboard 2. Select repository and branch 3. Configure build command (optional) 4. Automatic deployment on push 5. Preview URLs for pull requests
Cloudflare Zero Trust Setup Guide
Overview
Cloudflare Zero Trust is a security platform that replaces traditional VPNs with identity-based access control. It includes:
- Cloudflare Tunnel: Expose internal services without opening firewall ports
- Cloudflare Access: Identity-aware proxy for application access
- Cloudflare WARP: Device client for secure connectivity
- Gateway: DNS filtering and web security
---
Cloudflare Tunnel
Installation
# macOS
brew install cloudflared
# Windows
winget install Cloudflare.cloudflared
# Linux (Debian/Ubuntu)
curl -L https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-archive-keyring.gpg >/dev/null
echo "deb [signed-by=/usr/share/keyrings/cloudflare-archive-keyring.gpg] https://pkg.cloudflare.com/cloudflared $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install cloudflared
# Linux (Direct download)
curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o cloudflared
chmod +x cloudflared
sudo mv cloudflared /usr/local/bin/
# Docker
docker pull cloudflare/cloudflared:latestQuick Start
# 1. Login to Cloudflare
cloudflared tunnel login
# Opens browser for authentication
# Saves certificate to ~/.cloudflared/cert.pem
# 2. Create a tunnel
cloudflared tunnel create my-tunnel
# Outputs: Created tunnel my-tunnel with id <TUNNEL_ID>
# Saves credentials to ~/.cloudflared/<TUNNEL_ID>.json
# 3. Create DNS record
cloudflared tunnel route dns my-tunnel app.example.com
# 4. Run the tunnel
cloudflared tunnel run my-tunnelConfiguration File
Create ~/.cloudflared/config.yml:
# Tunnel ID and credentials
tunnel: <TUNNEL_ID>
credentials-file: /home/user/.cloudflared/<TUNNEL_ID>.json
# Ingress rules (processed top to bottom)
ingress:
# Route specific hostname to service
- hostname: app.example.com
service: http://localhost:3000
# Route with path matching
- hostname: api.example.com
path: /v1/*
service: http://localhost:8080
# WebSocket support
- hostname: ws.example.com
service: ws://localhost:9000
# TCP service (SSH, database, etc.)
- hostname: ssh.example.com
service: ssh://localhost:22
# With origin configuration
- hostname: secure.example.com
service: https://localhost:8443
originRequest:
noTLSVerify: true # Accept self-signed certs
connectTimeout: 30s
tlsTimeout: 10s
httpHostHeader: internal-hostname
# Catch-all (required, must be last)
- service: http_status:404Advanced Configuration
tunnel: <TUNNEL_ID>
credentials-file: /path/to/credentials.json
# Logging
loglevel: info
logfile: /var/log/cloudflared.log
# Metrics
metrics: localhost:2000
# Origin settings (global defaults)
originRequest:
connectTimeout: 30s
noHappyEyeballs: false
tcpKeepAlive: 30s
keepAliveConnections: 100
keepAliveTimeout: 1m30s
httpHostHeader: ""
originServerName: ""
caPool: ""
noTLSVerify: false
disableChunkedEncoding: false
proxyAddress: 127.0.0.1
proxyPort: 0
proxyType: ""
http2Origin: false
ingress:
# Load balancing multiple backends
- hostname: api.example.com
service: http://localhost:8080
originRequest:
noTLSVerify: true
- hostname: api.example.com
service: http://localhost:8081 # Failover
# Health checks
- hostname: health.example.com
service: hello_world # Built-in health endpoint
- service: http_status:404Running as a Service
Linux (systemd)
# Install service
sudo cloudflared service install
# Start service
sudo systemctl start cloudflared
sudo systemctl enable cloudflared
# View logs
sudo journalctl -u cloudflared -fmacOS
# Install service
sudo cloudflared service install
# Start service
sudo launchctl start com.cloudflare.cloudflared
# View logs
tail -f /var/log/cloudflared.logWindows
# Install service
cloudflared.exe service install
# Start service
sc start cloudflaredDocker
# docker-compose.yml
version: '3'
services:
cloudflared:
image: cloudflare/cloudflared:latest
command: tunnel run
environment:
- TUNNEL_TOKEN=<YOUR_TUNNEL_TOKEN>
restart: unless-stoppedTunnel Dashboard Management
Instead of CLI-managed tunnels, you can create tunnels in the Cloudflare dashboard:
1. Go to Zero Trust > Access > Tunnels 2. Create a tunnel 3. Get the tunnel token 4. Run with token:
cloudflared tunnel run --token <TUNNEL_TOKEN>---
Cloudflare Access
Application Setup
In Cloudflare Zero Trust dashboard:
1. Go to Access > Applications 2. Create Application > Self-hosted
# Application configuration
name: Internal Dashboard
type: Self-hosted
session_duration: 24h
domain: dashboard.example.com
# Optional: subdomain matching
include_subdomains: trueAccess Policies
# Policy 1: Allow company emails
name: Company Employees
action: Allow
include:
- email_domain: company.com
# Policy 2: Require specific group
name: Engineering Team
action: Allow
include:
- group: engineering
require:
- login_method: google-oauth
# Policy 3: Block specific IPs
name: Block Bad IPs
action: Block
include:
- ip: 192.168.1.0/24
# Policy 4: Require device posture
name: Secure Devices Only
action: Allow
include:
- email_domain: company.com
require:
- device_posture:
- serial_number_check
- disk_encryptionService Auth (Machine-to-Machine)
For automated access without user authentication:
# Service Auth policy
name: CI/CD Access
action: Service Auth
include:
- service_token: <token_id>Generate service token: 1. Go to Access > Service Auth 2. Create Service Token 3. Use in requests:
curl -H "CF-Access-Client-Id: <client_id>" \
-H "CF-Access-Client-Secret: <client_secret>" \
https://internal-api.example.com/dataJWT Validation in Workers
interface Env {
TEAM_DOMAIN: string;
}
async function validateAccessJWT(request: Request, env: Env): Promise<{ email: string } | null> {
const jwt = request.headers.get("CF-Access-JWT-Assertion");
if (!jwt) return null;
try {
// Fetch Access public keys
const certsUrl = `https://${env.TEAM_DOMAIN}/cdn-cgi/access/certs`;
const certsResponse = await fetch(certsUrl);
const { public_certs } = await certsResponse.json();
// Verify JWT (simplified - use proper JWT library)
const [header, payload, signature] = jwt.split(".");
const decodedPayload = JSON.parse(atob(payload));
// Verify claims
if (decodedPayload.iss !== `https://${env.TEAM_DOMAIN}`) {
return null;
}
if (decodedPayload.exp < Date.now() / 1000) {
return null;
}
return { email: decodedPayload.email };
} catch {
return null;
}
}
export default {
async fetch(request: Request, env: Env) {
const user = await validateAccessJWT(request, env);
if (!user) {
return new Response("Unauthorized", { status: 401 });
}
return new Response(`Hello, ${user.email}!`);
},
};---
WARP Client
Deployment Methods
1. Manual Installation
- Download from https://one.one.one.one
- Install and configure
2. MDM Deployment
- Download MSI/PKG installers
- Configure via MDM policy
3. Gateway Configuration
- Download profile from Zero Trust dashboard
- Distribute to users
WARP Configuration
<!-- macOS MDM Profile -->
<dict>
<key>organization</key>
<string>your-team-name</string>
<key>enable</key>
<true/>
<key>gateway_unique_id</key>
<string>your-gateway-id</string>
<key>service_mode</key>
<string>warp</string>
<key>onboarding</key>
<false/>
</dict>Split Tunneling
Include/exclude specific IPs or domains from WARP:
# In Zero Trust Dashboard > Settings > WARP Client > Split Tunnels
# Exclude mode (default): These bypass WARP
exclude:
- 10.0.0.0/8 # Internal network
- 172.16.0.0/12 # Internal network
- 192.168.0.0/16 # Internal network
- localhost # Local development
# Include mode: Only these go through WARP
include:
- company-api.com
- 10.100.0.0/16 # Only corporate networkDevice Posture Checks
# Disk encryption check
- rule_name: Require FileVault
type: file
value: true
platform: macOS
# OS version check
- rule_name: Minimum macOS version
type: os_version
operator: >=
version: "13.0"
platform: macOS
# Firewall check
- rule_name: Firewall enabled
type: firewall
value: true
# Serial number check
- rule_name: Company devices only
type: serial_number
value:
- ABC123
- DEF456---
Gateway (DNS Filtering)
DNS Policies
# Block malware
- name: Block Malware
action: Block
traffic: dns
selector: Security Risks
- Malware
- Phishing
- Spam
# Block social media
- name: Block Social Media
action: Block
traffic: dns
selector: Content Categories
- Social Networks
# Allow specific domains
- name: Allow Company Domains
action: Allow
traffic: dns
selector: Domain
- "*.company.com"
# Custom block
- name: Block Gaming
action: Block
traffic: dns
selector: Domain
- "*.steam.com"
- "*.epicgames.com"HTTP Policies
# Block file uploads
- name: Block Uploads
action: Block
traffic: http
selector: Upload Mime Type
- application/zip
- application/x-rar
# Inspect SSL
- name: Inspect Traffic
action: Do Not Inspect
traffic: http
selector: Domain
- "*.banking.com" # Don't inspect banking---
Troubleshooting
Tunnel Issues
# Check tunnel status
cloudflared tunnel info my-tunnel
# Test connectivity
cloudflared tunnel run my-tunnel --loglevel debug
# Verify DNS
dig app.example.com
# Check credentials
ls -la ~/.cloudflared/Access Issues
# Test Access policy
curl -I https://app.example.com
# Check JWT
curl -H "Cookie: CF_Authorization=<jwt>" https://app.example.com
# Verify identity
curl https://your-team.cloudflareaccess.com/cdn-cgi/access/get-identityWARP Issues
# Check WARP status
warp-cli status
# Reconnect
warp-cli disconnect && warp-cli connect
# Reset registration
warp-cli registration delete
warp-cli register---
Architecture Diagrams
Basic Tunnel Setup
[User]
|
v
[Cloudflare Edge]
|
v (Tunnel connection, outbound only)
[cloudflared] --> [Internal Service]
localhost:3000Zero Trust Architecture
[Remote User with WARP]
|
v
[Cloudflare Edge]
|
+-- [Gateway: DNS/HTTP Filtering]
|
+-- [Access: Identity Check]
|
v (If authorized)
[Tunnel] --> [Internal Apps]Multiple Services
[Cloudflare Edge]
|
v
[cloudflared]
|
+---------------------+---------------------+
| | |
v v v
[Web App] [API Server] [Database]
:3000 :8080 :5432---
Best Practices
Security
1. Use Access policies for all internal applications 2. Enable device posture checks for sensitive apps 3. Rotate service tokens regularly 4. Log all access for audit trails 5. Use short session durations for sensitive apps
Performance
1. Run cloudflared close to services (same network) 2. Use multiple tunnels for high availability 3. Enable HTTP/2 for better performance 4. Monitor tunnel metrics at localhost:2000
Reliability
1. Run as system service for auto-restart 2. Use dashboard-managed tunnels for easier management 3. Set up monitoring for tunnel health 4. Have fallback access methods