Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Comprehensive Cloudflare platform skill covering Workers, D1, R2, KV, AI, Durable Objects, and security.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/turn/gotchas.md
1# TURN Gotchas & Troubleshooting23Common mistakes, security best practices, and troubleshooting for Cloudflare TURN.45## Quick Reference67| Issue | Solution | Details |8|-------|----------|---------|9| Credentials not working | Check TTL ≤ 48hrs | [See Troubleshooting](#issue-turn-credentials-not-working) |10| Connection drops after ~48hrs | Implement credential refresh | [See Connection Drops](#issue-connection-drops-after-48-hours) |11| Port 53 fails in browser | Filter server-side | [See Port 53](#using-port-53-in-browsers) |12| High packet loss | Check rate limits | [See Rate Limits](#limits-per-turn-allocation) |13| Connection fails after maintenance | Implement ICE restart | [See ICE Restart](#ice-restart-required-scenarios) |1415## Critical Constraints1617| Constraint | Value | Consequence if Violated |18|------------|-------|-------------------------|19| Max credential TTL | 48 hours (172800s) | API rejects request |20| Credential revocation delay | ~seconds | Billing stops immediately, connection drops shortly |21| IP allowlist update window | 14 days (if IPs change) | Connection fails if IPs change |22| Packet rate | 5-10k pps per allocation | Packet drops |23| Data rate | 50-100 Mbps per allocation | Packet drops |24| Unique IP rate | >5 new IPs/sec | Packet drops |2526## Limits Per TURN Allocation2728**Per user** (not account-wide):2930- **IP addresses**: >5 new unique IPs per second31- **Packet rate**: 5-10k packets per second (inbound/outbound)32- **Data rate**: 50-100 Mbps (inbound/outbound)33- **MTU**: No specific limit34- **Burst rates**: Higher than documented3536Exceeding limits results in **packet drops**.3738## Common Mistakes3940### Setting TTL > 48 hours4142```typescript43// ❌ BAD: API will reject44const creds = await generate({ ttl: 604800 }); // 7 days4546// ✅ GOOD:47const creds = await generate({ ttl: 86400 }); // 24 hours48```4950### Hardcoding IPs without monitoring5152```typescript53// ❌ BAD: IPs can change with 14-day notice54const iceServers = [{ urls: 'turn:141.101.90.1:3478' }];5556// ✅ GOOD: Use DNS57const iceServers = [{ urls: 'turn:turn.cloudflare.com:3478' }];58```5960### Using port 53 in browsers6162```typescript63// ❌ BAD: Blocked by Chrome/Firefox64urls: ['turn:turn.cloudflare.com:53']6566// ✅ GOOD: Filter port 5367urls: urls.filter(url => !url.includes(':53'))68```6970### Not handling credential expiry7172```typescript73// ❌ BAD: Credentials expire but call continues → connection drops74const creds = await fetchCreds();75const pc = new RTCPeerConnection({ iceServers: creds });7677// ✅ GOOD: Refresh before expiry78setInterval(() => refreshCredentials(pc), 3000000); // 50 min79```8081### Missing ICE restart support8283```typescript84// ❌ BAD: No recovery from TURN maintenance85pc.addEventListener('iceconnectionstatechange', () => {86console.log('State changed:', pc.iceConnectionState);87});8889// ✅ GOOD: Implement ICE restart90pc.addEventListener('iceconnectionstatechange', async () => {91if (pc.iceConnectionState === 'failed') {92await refreshCredentials(pc);93pc.restartIce();94}95});96```9798### Exposing TURN key secret client-side99100```typescript101// ❌ BAD: Secret exposed to client102const secret = 'your-turn-key-secret';103const response = await fetch(`https://rtc.live.cloudflare.com/v1/turn/...`, {104headers: { 'Authorization': `Bearer ${secret}` }105});106107// ✅ GOOD: Generate credentials server-side108const response = await fetch('/api/turn-credentials');109```110111## ICE Restart Required Scenarios112113These events require ICE restart (see [patterns.md](./patterns.md#ice-restart-pattern)):1141151. **TURN server maintenance** (occasional on Cloudflare's network)1162. **Network topology changes** (anycast routing changes)1173. **Credential refresh** during long sessions (>1 hour)1184. **Connection failure** (iceConnectionState === 'failed')119120Implement in all production apps:121122```typescript123pc.addEventListener('iceconnectionstatechange', async () => {124if (pc.iceConnectionState === 'failed' ||125pc.iceConnectionState === 'disconnected') {126await refreshTURNCredentials(pc);127pc.restartIce();128const offer = await pc.createOffer({ iceRestart: true });129await pc.setLocalDescription(offer);130// Send offer to peer via signaling...131}132});133```134135Reference: [RFC 8445 Section 2.4](https://datatracker.ietf.org/doc/html/rfc8445#section-2.4)136137## Security Checklist138139- [ ] Credentials generated server-side only (never client-side)140- [ ] TURN_KEY_SECRET in wrangler secrets, not vars141- [ ] TTL ≤ expected session duration (and ≤ 48 hours)142- [ ] Rate limiting on credential generation endpoint143- [ ] Client authentication before issuing credentials144- [ ] Credential revocation API for compromised sessions145- [ ] No hardcoded IPs (or DNS monitoring in place)146- [ ] Port 53 filtered for browser clients147148## Troubleshooting149150### Issue: TURN credentials not working151152**Check:**153- Key ID and secret are correct154- Credentials haven't expired (check TTL)155- TTL doesn't exceed 172800 seconds (48 hours)156- Server can reach rtc.live.cloudflare.com157- Network allows outbound HTTPS158159**Solution:**160```typescript161// Validate before using162if (ttl > 172800) {163throw new Error('TTL cannot exceed 48 hours');164}165```166167### Issue: Slow connection establishment168169**Solutions:**170- Ensure proper ICE candidate gathering171- Check network latency to Cloudflare edge172- Verify firewall allows WebRTC ports (3478, 5349, 443)173- Consider using TURN over TLS (port 443) for corporate networks174175### Issue: High packet loss176177**Check:**178- Not exceeding rate limits (5-10k pps)179- Not exceeding bandwidth limits (50-100 Mbps)180- Not connecting to too many unique IPs (>5/sec)181- Client network quality182183### Issue: Connection drops after ~48 hours184185**Cause**: Credentials expired (48hr max)186187**Solution**:188- Set TTL to expected session duration189- Implement credential refresh with setConfiguration()190- Use ICE restart if connection fails191192```typescript193// Refresh credentials before expiry194const refreshInterval = ttl * 1000 - 60000; // 1 min early195setInterval(async () => {196await refreshTURNCredentials(pc);197}, refreshInterval);198```199200### Issue: Port 53 URLs in browser fail silently201202**Cause**: Chrome/Firefox block port 53203204**Solution**: Filter port 53 URLs server-side:205206```typescript207const filtered = urls.filter(url => !url.includes(':53'));208```209210### Issue: Hardcoded IPs stop working211212**Cause**: Cloudflare changed IP addresses (14-day notice)213214**Solution**:215- Use DNS hostnames (`turn.cloudflare.com`)216- Monitor DNS changes with automated alerts217- Update allowlists within 14 days if using IP allowlisting218219## Cost Optimization2202211. Use appropriate TTLs (don't over-provision)2222. Implement credential caching2233. Set `iceTransportPolicy: 'all'` to try direct first (use `'relay'` only when necessary)2244. Monitor bandwidth usage2255. Free when used with Cloudflare Calls SFU226227## See Also228229- [api.md](./api.md) - Credential generation API, revocation230- [configuration.md](./configuration.md) - IP allowlisting, monitoring231- [patterns.md](./patterns.md) - ICE restart, credential refresh patterns232