Solutions to common issues and errors in Bungate.
- Authentication Issues
- Load Balancing Problems
- Performance Issues
- Clustering Issues
- TLS/HTTPS Problems
- Common Errors
- Debug Mode
- Getting Help
Problem: Receiving 401 Unauthorized even with valid API key
Checklist:
// ✅ 1. Verify API key is in configured list
auth: {
apiKeys: ['your-api-key-here'], // Exact match required
apiKeyHeader: 'X-API-Key',
}
// ✅ 2. Check header name matches (case-insensitive in HTTP)
// Both work:
curl -H "X-API-Key: key1" http://localhost:3000/api
curl -H "x-api-key: key1" http://localhost:3000/api
// ✅ 3. Check for spaces or hidden characters
const apiKey = process.env.API_KEY?.trim()
// ✅ 4. Enable debug logging
auth: {
apiKeys: ['key1'],
apiKeyHeader: 'X-API-Key',
apiKeyValidator: async (key: string) => {
console.log('Received key:', key)
console.log('Expected keys:', ['key1'])
console.log('Match:', key === 'key1')
return ['key1'].includes(key)
},
}Test manually:
# Valid key
curl -v -H "X-API-Key: key1" http://localhost:3000/api/data
# Check response headers for clues
curl -i -H "X-API-Key: key1" http://localhost:3000/api/data
# Test with different header name
curl -v -H "Authorization: Bearer key1" http://localhost:3000/api/dataIf you must use JWT, check:
# 1. Verify token is valid
# Use jwt.io to decode and verify
# 2. Check expiration
# JWT must not be expired
# 3. Verify issuer/audience
# Must match configuration
# 4. Check algorithm
# Must be in allowed algorithms listProblem: Want either JWT or API key, but both are required
Explanation: When both secret and apiKeys are configured, API key becomes required.
Solution: Create separate routes:
// JWT route
gateway.addRoute({
pattern: '/api/jwt/*',
target: 'http://backend:3000',
auth: {
secret: process.env.JWT_SECRET,
jwtOptions: { algorithms: ['HS256'] },
},
})
// API key route
gateway.addRoute({
pattern: '/api/key/*',
target: 'http://backend:3000',
auth: {
apiKeys: ['key1', 'key2'],
apiKeyHeader: 'X-API-Key',
},
})Problem: Public routes requiring authentication
Solution: Check route order and excludePaths:
// ❌ Auth applies to all routes
const gateway = new BunGateway({
auth: {
secret: process.env.JWT_SECRET,
jwtOptions: { algorithms: ['HS256'] },
// Missing excludePaths!
},
})
// ✅ Exclude public routes
const gateway = new BunGateway({
auth: {
secret: process.env.JWT_SECRET,
jwtOptions: { algorithms: ['HS256'] },
excludePaths: [
'/health',
'/metrics',
'/public/*', // Wildcard support
'/auth/login',
'/auth/register',
],
},
})
// ✅ Or don't set gateway-level auth
const gateway = new BunGateway({
server: { port: 3000 },
})
// Add auth per route
gateway.addRoute({
pattern: '/api/protected/*',
target: 'http://api:3000',
auth: { apiKeys: ['key1'] },
})
gateway.addRoute({
pattern: '/public/*',
target: 'http://api:3000',
// No auth
})Problem: One server receives more traffic than others
Debug:
// 1. Check target health
const status = gateway.getTargetStatus()
console.log('Healthy targets:', status.filter(t => t.healthy))
status.forEach(target => {
console.log(`${target.url}: ${target.healthy ? '✓' : '✗'} (${target.connections} connections)`)
})
// 2. Try different strategy
loadBalancer: {
strategy: 'least-connections', // Instead of round-robin
targets: [/* ... */],
}
// 3. Verify weights
loadBalancer: {
strategy: 'weighted',
targets: [
{ url: 'http://api1:3000', weight: 50 },
{ url: 'http://api2:3000', weight: 50 }, // Equal
],
}
// 4. Check health check frequency
healthCheck: {
enabled: true,
interval: 10000, // More frequent
timeout: 5000,
path: '/health',
}Problem: Healthy servers marked as unhealthy
Solutions:
// 1. Increase timeouts
healthCheck: {
enabled: true,
timeout: 10000, // Increase from 5000
interval: 15000,
unhealthyThreshold: 5, // More failures required
healthyThreshold: 2, // Less successes required
}
// 2. Check health endpoint
// Test manually:
curl http://backend:3000/health
// Should respond quickly (< 5s)
// Should return 200 OK
// 3. Verify network connectivity
// From gateway container/host:
ping backend-server
telnet backend-server 3000
// 4. Add custom validation
healthCheck: {
enabled: true,
path: '/health',
validator: async (response) => {
console.log('Health check response:', response.status)
if (response.status !== 200) return false
const data = await response.json()
console.log('Health data:', data)
return data.status === 'healthy'
},
}Problem: Circuit breaker opens frequently
Solutions:
// 1. Increase failure threshold
circuitBreaker: {
enabled: true,
failureThreshold: 10, // Increase from 5
timeout: 30000, // Longer timeout
resetTimeout: 60000, // Wait longer before retry
}
// 2. Check backend performance
// Profile backend service
// Add logging to identify slow endpoints
// 3. Add fallback handler
hooks: {
onError: async (req, error) => {
console.error('Circuit breaker triggered:', error)
// Return cached data
return new Response(
JSON.stringify({ cached: true, data: getCachedData() }),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
},
}Problem: Users lose session data
Solutions:
// 1. Enable sticky sessions
loadBalancer: {
strategy: 'least-connections',
targets: [/* ... */],
stickySession: {
enabled: true,
cookieName: 'session_id',
ttl: 3600000, // 1 hour
secure: true, // HTTPS only
httpOnly: true,
sameSite: 'lax',
},
}
// 2. Use IP hash strategy
loadBalancer: {
strategy: 'ip-hash',
targets: [/* ... */],
}
// 3. Use external session store
// Store sessions in Redis/database:
import { Redis } from 'ioredis'
const redis = new Redis()
// Store session
await redis.set(`session:${userId}`, JSON.stringify(sessionData), 'EX', 3600)
// Get session
const sessionData = await redis.get(`session:${userId}`)Problem: Slow response times through gateway
Debug:
// 1. Add request timing
gateway.addRoute({
pattern: '/api/*',
target: 'http://backend:3000',
middlewares: [
async (req, next) => {
const start = Date.now()
const response = await next()
const duration = Date.now() - start
console.log(`${req.method} ${req.url}: ${duration}ms`)
return response
},
],
})
// 2. Check backend performance
// Profile backend service
// Add APM (Application Performance Monitoring)
// 3. Enable metrics
const gateway = new BunGateway({
metrics: { enabled: true },
})
// Check http://localhost:3000/metrics
// 4. Optimize load balancing
loadBalancer: {
strategy: 'latency', // Route to fastest server
targets: [/* ... */],
}Solutions:
// 1. Increase timeouts (if needed)
gateway.addRoute({
pattern: '/api/*',
target: 'http://backend:3000',
timeout: 60000, // 60 seconds
proxy: {
timeout: 60000,
},
})
// 2. Reduce health check frequency
healthCheck: {
interval: 30000, // Less frequent (30s)
timeout: 3000,
}
// 3. Enable clustering
cluster: {
enabled: true,
workers: 4,
}
// 4. Use connection pooling (enabled by default)
// 5. Enable caching
middlewares: [
async (req, next) => {
const cacheKey = req.url
const cached = await cache.get(cacheKey)
if (cached) return new Response(cached)
const response = await next()
const body = await response.text()
await cache.set(cacheKey, body, 60) // Cache 60s
return new Response(body, response)
},
]Problem: Gateway consuming too much memory
Solutions:
// 1. Reduce worker count
cluster: {
enabled: true,
workers: 2, // Instead of 8
}
// 2. Monitor memory
setInterval(() => {
const usage = process.memoryUsage()
console.log({
heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + 'MB',
heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + 'MB',
rss: Math.round(usage.rss / 1024 / 1024) + 'MB',
})
}, 60000)
// 3. Check for memory leaks
// Use Bun's built-in profiler
// bun --inspect gateway.ts
// 4. Limit request body size
security: {
sizeLimits: {
maxBodySize: 10 * 1024 * 1024, // 10 MB
maxHeaderSize: 16 * 1024, // 16 KB
},
}
// 5. Set memory limits (Docker)
// docker run --memory=512m ...Problem: Requests timing out
Solutions:
// 1. Increase timeouts
gateway.addRoute({
pattern: '/api/*',
target: 'http://backend:3000',
timeout: 120000, // 2 minutes
proxy: {
timeout: 120000,
},
})
// 2. Check backend performance
// Backend might be slow
curl -w "@curl-format.txt" -o /dev/null -s http://backend:3000/api/slow
// curl-format.txt:
// time_namelookup: %{time_namelookup}\n
// time_connect: %{time_connect}\n
// time_total: %{time_total}\n
// 3. Add circuit breaker
circuitBreaker: {
enabled: true,
failureThreshold: 5,
timeout: 10000,
resetTimeout: 30000,
}
// 4. Check network connectivity
ping backend-server
traceroute backend-serverProblem: Workers restart repeatedly
Debug:
// 1. Add error handlers
process.on('uncaughtException', (error) => {
console.error('Uncaught exception:', error)
// Don't exit immediately in production
})
process.on('unhandledRejection', (reason) => {
console.error('Unhandled rejection:', reason)
})
// 2. Check logs
tail -f bungate.log
// 3. Monitor restarts
const cluster = new ClusterManager(
{
enabled: true,
workers: 4,
maxRestarts: 10,
},
logger,
'./gateway.ts'
)
cluster.on('worker-exit', (workerId, code, signal) => {
console.error(`Worker ${workerId} crashed (code: ${code}, signal: ${signal})`)
})
// 4. Increase restart thresholds
cluster: {
enabled: true,
workers: 4,
maxRestarts: 20, // More restarts allowed
respawnThreshold: 10, // Higher threshold
respawnThresholdTime: 120000, // Longer window (2 min)
}Problem: SIGUSR2 doesn't trigger restart
Debug:
# 1. Find master process
ps aux | grep bungate
# Look for master process (not worker)
# 2. Send signal to master
kill -USR2 <MASTER_PID>
# 3. Check logs
tail -f bungate.log
# Should see: "Received SIGUSR2, starting rolling restart..."
# 4. Verify signal handling
# Check that gateway is running in cluster mode
ps aux | grep "bungate"
# Should see multiple processesProblem: Error: listen EADDRINUSE
Solutions:
# 1. Find process using port
lsof -ti:3000
# 2. Kill process
lsof -ti:3000 | xargs kill -9
# 3. Or use different port
const gateway = new BunGateway({
server: { port: 3001 },
})
# 4. Check for zombie processes
ps aux | grep bungate
kill -9 <PID>
# 5. Wait a moment and retry
# Port might be in TIME_WAIT state
netstat -an | grep 3000Problem: SSL/TLS certificate errors
Solutions:
// 1. Verify certificate files exist
import { existsSync } from 'fs'
if (!existsSync('./cert.pem')) {
console.error('Certificate file not found')
}
if (!existsSync('./key.pem')) {
console.error('Key file not found')
}
// 2. Check certificate validity
// openssl x509 -in cert.pem -text -noout
// 3. Verify certificate matches key
// openssl x509 -noout -modulus -in cert.pem | openssl md5
// openssl rsa -noout -modulus -in key.pem | openssl md5
// Should match
// 4. Check certificate chain
security: {
tls: {
enabled: true,
cert: './cert.pem',
key: './key.pem',
ca: './ca.pem', // Add CA certificate if needed
},
}
// 5. Test manually
curl -v https://localhost
openssl s_client -connect localhost:443Problem: HTTP requests not redirecting to HTTPS
Solution:
// Enable HTTP redirect
security: {
tls: {
enabled: true,
cert: './cert.pem',
key: './key.pem',
redirectHTTP: true, // Enable redirect
redirectPort: 80, // HTTP port
},
}
// Test redirect
curl -v http://localhost
// Should return 301/302 with Location: https://localhostProblem: TLS handshake errors
Solutions:
// 1. Set minimum TLS version
security: {
tls: {
enabled: true,
cert: './cert.pem',
key: './key.pem',
minVersion: 'TLSv1.2', // Lower from TLSv1.3 if needed
},
}
// 2. Add more cipher suites
security: {
tls: {
enabled: true,
cert: './cert.pem',
key: './key.pem',
cipherSuites: [
'TLS_AES_256_GCM_SHA384',
'TLS_CHACHA20_POLY1305_SHA256',
'TLS_AES_128_GCM_SHA256',
// Add more for compatibility
],
},
}
// 3. Test with openssl
openssl s_client -connect localhost:443 -tls1_3
openssl s_client -connect localhost:443 -tls1_2Cause: Auth configuration missing required fields
Solution:
// ✅ Provide secret
auth: {
secret: process.env.JWT_SECRET || 'fallback-secret',
jwtOptions: { algorithms: ['HS256'] },
}
// OR provide jwksUri
auth: {
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
jwtOptions: { algorithms: ['RS256'] },
}Solution:
# Install bungate
bun add bungate
# Verify installation
cat package.json | grep bungate
# Clear cache and reinstall
rm -rf node_modules bun.lockb
bun installCause: All load balancer targets are unhealthy
Debug:
// 1. Check target status
const status = gateway.getTargetStatus()
console.log(status)
// 2. Test targets manually
curl http://api-server-1:3001/health
curl http://api-server-2:3001/health
// 3. Adjust health check settings
healthCheck: {
enabled: true,
interval: 15000,
timeout: 10000, // Longer timeout
path: '/health',
unhealthyThreshold: 5, // More failures required
}
// 4. Check backend logs
docker logs api-server-1Solution:
// Increase body size limit
security: {
sizeLimits: {
maxBodySize: 50 * 1024 * 1024, // 50 MB
},
}import { BunGateway, PinoLogger } from 'bungate'
const logger = new PinoLogger({
level: 'debug', // Show all logs
prettyPrint: true, // Human-readable
})
const gateway = new BunGateway({
server: {
port: 3000,
development: true, // Enable dev mode
},
logger,
})gateway.addRoute({
pattern: '/api/*',
target: 'http://backend:3000',
middlewares: [
async (req, next) => {
// Log request
console.log('→', req.method, req.url)
console.log('Headers:', Object.fromEntries(req.headers))
const start = Date.now()
const response = await next()
const duration = Date.now() - start
// Log response
console.log('←', response.status, `(${duration}ms)`)
return response
},
],
})# Show request/response details
curl -v http://localhost:3000/api/data
# Show timing information
curl -w "@curl-format.txt" http://localhost:3000/api/data
# curl-format.txt:
# time_namelookup: %{time_namelookup}s\n
# time_connect: %{time_connect}s\n
# time_appconnect: %{time_appconnect}s\n
# time_pretransfer: %{time_pretransfer}s\n
# time_redirect: %{time_redirect}s\n
# time_starttransfer: %{time_starttransfer}s\n
# ----------\n
# time_total: %{time_total}s\n
# Test with specific headers
curl -v \
-H "X-API-Key: key1" \
-H "Content-Type: application/json" \
-d '{"test": "data"}' \
http://localhost:3000/api/data- Check this guide for your specific issue
- Enable debug logging and check logs
- Test manually with curl
- Verify configuration against examples
- Check system resources (CPU, memory, disk)
When reporting issues, include:
// 1. Bungate version
console.log('Bungate version:', require('bungate/package.json').version)
// 2. Bun version
// bun --version
// 3. Operating system
// uname -a
// 4. Configuration (sanitized)
const config = {
server: { port: 3000 },
cluster: { enabled: true, workers: 4 },
// ... (remove secrets)
}
// 5. Error logs
// Full error stack trace
// 6. Steps to reproduce
// 1. Start gateway with config
// 2. Send request: curl ...
// 3. Observe error: ...- Search existing issues - Your problem might already be solved
- Provide minimal reproduction - Simplest code that shows the issue
- Be specific - Include versions, configurations, error messages
- Follow up - Respond to questions and confirm solutions
Still stuck? Open a new issue with detailed information.