Diagnose and create Cloudflare VPC Services for Workers to access private APIs in AWS, Azure, GCP, or on-premise networks. Use when troubleshooting dns_error, configuring cloudflared tunnels, setting up VPC service bindings, or routing Workers to internal services.
From cce-cloudflarenpx claudepluginhub nodnarbnitram/claude-code-extensions --plugin cce-cloudflareThis skill is limited to using the following tools:
README.mdreferences/README.mdreferences/api-patterns.mdscripts/README.mdscripts/list-vpc-services.shscripts/set-api-token.shscripts/tail-worker.shtemplates/vpc-service-hostname.jsontemplates/vpc-service-ip.jsontemplates/wrangler-vpc.jsoncSearches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Implements structured self-debugging workflow for AI agent failures: capture errors, diagnose patterns like loops or context overflow, apply contained recoveries, and generate introspection reports.
Enable Workers to securely access private APIs and services through encrypted tunnels without public internet exposure.
This skill prevents 5 common errors and saves ~60% tokens.
| Metric | Without Skill | With Skill |
|---|---|---|
| Setup Time | 45+ min | 10 min |
| Common Errors | 5 | 0 |
| Token Usage | ~8000 | ~3000 |
dns_error from outdated cloudflared version or wrong protocol# Check cloudflared version on remote infrastructure (K8s, EC2, etc.)
# Must be 2025.7.0 or later
cloudflared --version
# Verify QUIC protocol is configured (not http2)
# Check tunnel config or Cloudflare dashboard
Why this matters: Workers VPC requires cloudflared 2025.7.0+ with QUIC protocol. Older versions or http2 protocol cause dns_error.
# Use Cloudflare API or dashboard to create VPC service
# See templates/vpc-service-ip.json or templates/vpc-service-hostname.json
Why this matters: The VPC service defines the actual target (IP/hostname) that the tunnel routes to. The fetch() URL only sets Host header and SNI.
// wrangler.jsonc
{
"vpc_services": [
{
"binding": "PRIVATE_API",
"service_id": "<YOUR_SERVICE_ID>",
"remote": true
}
]
}
Why this matters: The binding name becomes the environment variable used in Worker code: env.PRIVATE_API.fetch().
❌ Wrong:
// Port is ignored, relative URL fails
const response = await env.VPC_SERVICE.fetch("/api/users:8080");
✅ Correct:
// Absolute URL, port configured in VPC service
const response = await env.VPC_SERVICE.fetch("https://internal-api.company.local/api/users");
Why: The VPC service configuration determines actual routing. The fetch() URL only populates the Host header and SNI value.
| Issue | Root Cause | Solution |
|---|---|---|
dns_error | cloudflared < 2025.7.0 or http2 protocol | Update cloudflared, configure QUIC, allow UDP 7844 |
| Requests go to public internet | Using public hostname in fetch() | Use internal VPC hostname |
| Connection refused | Wrong port in VPC service config | Configure correct http_port/https_port in service |
| Timeout | Tunnel not running or wrong tunnel_id | Verify tunnel status, check tunnel_id |
| 404 errors | Incorrect path routing | Verify internal service path matches fetch() path |
{
"name": "my-worker",
"main": "src/index.ts",
"compatibility_date": "2024-01-01",
"vpc_services": [
{
"binding": "PRIVATE_API",
"service_id": "daf43e8c-a81a-4242-9912-4a2ebe4fdd79",
"remote": true
},
{
"binding": "PRIVATE_DATABASE",
"service_id": "453b6067-1327-420d-89b3-2b6ad16e6551",
"remote": true
}
]
}
Key settings:
binding: Environment variable name for accessing the serviceservice_id: UUID from VPC service creationremote: Must be true for VPC servicesexport default {
async fetch(request, env) {
const response = await env.PRIVATE_API.fetch(
"https://internal-api.company.local/users"
);
return response;
}
};
const response = await env.PRIVATE_API.fetch(
"https://internal-api.company.local/users",
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${env.API_TOKEN}`
},
body: JSON.stringify({ name: "John", email: "john@example.com" })
}
);
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (url.pathname.startsWith('/api/users')) {
return env.USER_SERVICE.fetch(
`https://user-api.internal${url.pathname}`
);
} else if (url.pathname.startsWith('/api/orders')) {
return env.ORDER_SERVICE.fetch(
`https://orders-api.internal${url.pathname}`
);
}
return new Response('Not Found', { status: 404 });
}
};
Located in templates/:
wrangler-vpc.jsonc - Ready-to-use wrangler config with VPC bindingsvpc-service-ip.json - IP-based VPC service API payloadvpc-service-hostname.json - Hostname-based VPC service API payloadCopy these templates as starting points for your implementation.
Located in scripts/:
list-vpc-services.sh - List VPC services via Cloudflare APItail-worker.sh - Debug VPC connections with live logsset-api-token.sh - Set secrets for private API authLocated in references/:
api-patterns.md - Comprehensive fetch() patterns and examples| Package | Version | Purpose |
|---|---|---|
| wrangler | latest | Deploy Workers with VPC bindings |
| cloudflared | 2025.7.0+ | Tunnel daemon (on remote infrastructure) |
| Package | Version | Purpose |
|---|---|---|
| @cloudflare/workers-types | latest | TypeScript types for Workers |
Symptoms: Worker returns dns_error when calling env.VPC_SERVICE.fetch()
Solution:
Symptoms: Logs show requests hitting public endpoints instead of internal
Solution:
// Use internal hostname
const response = await env.VPC_SERVICE.fetch(
"https://internal-api.vpc.local/endpoint" // Internal
// NOT "https://api.company.com/endpoint" // Public
);
Symptoms: Requests hang and eventually timeout
Solution:
Before using this skill, verify: