All skills
Skillintermediate

Gotchas and Troubleshooting

Common pitfalls, limitations, and solutions for TCP Sockets in Cloudflare Workers.

Claude Code Knowledge Pack7/10/2026

Overview

Gotchas and Troubleshooting

Common pitfalls, limitations, and solutions for TCP Sockets in Cloudflare Workers.

Platform Limits

Connection Limits

LimitValue
Max concurrent sockets per request6 (hard limit)
Socket lifetimeRequest duration
Connection timeoutPlatform-dependent, no setting

Problem: Exceeding 6 connections throws error

Solution: Process in batches of 6

for (let i = 0; i < hosts.length; i += 6) {
  const batch = hosts.slice(i, i + 6).map(h => connect({ hostname: h, port: 443 }));
  await Promise.all(batch.map(async s => { /* use */ await s.close(); }));
}

Blocked Destinations

Cloudflare IPs (1.1.1.1), localhost (127.0.0.1), port 25 (SMTP), Worker's own URL blocked for security.

Solution: Use public IPs or Tunnel hostnames: connect({ hostname: "db.internal.company.net", port: 5432 })

Scope Requirements

Problem: Sockets created in global scope fail

Cause: Sockets tied to request lifecycle

Solution: Create inside handler: export default { async fetch() { const socket = connect(...); } }

Common Errors

Error: "proxy request failed"

Causes: Blocked destination (Cloudflare IP, localhost, port 25), DNS failure, network unreachable

Solution: Validate destinations, use Tunnel hostnames, catch errors with try/catch

Error: "TCP Loop detected"

Cause: Worker connecting to itself

Solution: Connect to external service, not Worker's own hostname

Error: "Port 25 prohibited"

Cause: SMTP port blocked

Solution: Use Email Workers API for email

Error: "socket is not open"

Cause: Read/write after close

Solution: Always use try/finally to ensure proper closure order

Error: Connection timeout

Cause: No built-in timeout

Solution: Use Promise.race():

const socket = connect(addr, opts);
const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout')), 5000));
await Promise.race([socket.opened, timeout]);

TLS/SSL Issues

StartTLS Timing

Problem: Calling startTls() too early

Solution: Send protocol-specific STARTTLS command, wait for server OK, then call socket.startTls()

Certificate Validation

Problem: Self-signed certs fail

Solution: Use proper certs or Tunnel (handles TLS termination)

Performance Issues

Not Using Connection Pooling

Problem: New connection overhead per request

Solution: Use Hyperdrive for databases (built-in pooling)

Not Using Smart Placement

Problem: High latency to backend

Solution: Enable: { "placement": { "mode": "smart" } } in wrangler.jsonc

Forgetting to Close Sockets

Problem: Resource leaks

Solution: Always use try/finally:

const socket = connect({ hostname: "api.internal", port: 443 });
try {
  // Use socket
} finally {
  await socket.close();
}

Data Handling Issues

Assuming Single Read Gets All Data

Problem: Only reading once may miss chunked data

Solution: Loop reader.read() until done === true (see patterns.md)

Text Encoding Issues

Problem: Using wrong encoding

Solution: Specify encoding: new TextDecoder('iso-8859-1').decode(data)

Security Issues

SSRF Vulnerability

Problem: User-controlled destinations allow access to internal services

Solution: Validate against strict allowlist:

const ALLOWED = ['api1.internal.net', 'api2.internal.net'];
const host = new URL(req.url).searchParams.get('host');
if (!host || !ALLOWED.includes(host)) return new Response('Forbidden', { status: 403 });

When to Use Alternatives

Use CaseAlternativeReason
PostgreSQL/MySQLHyperdriveConnection pooling, caching
HTTP/HTTPSfetch()Simpler, built-in
HTTP with SSRF protectionVPC Services (beta 2025+)Declarative bindings

Debugging Tips

  1. Log connection details: const info = await socket.opened; console.log(info.remoteAddress);
  2. Test with public services first: Use tcpbin.com:4242 echo server
  3. Verify Tunnel: cloudflared tunnel info <name> and cloudflared tunnel route ip list

Related