Troubleshooting

This page covers common issues you may encounter when running bext, along with diagnostic commands and fixes.

Diagnostic Commands

Start here. These commands give you a snapshot of the system state:

# Pre-flight check: validates config, checks ports, TLS paths, permissions
bext-server check

# Show loaded configuration (merged from file + env + defaults)
bext-server config show

# Print the route table for the current app
bext-server routes

# Cache hit rates, sizes, and per-app breakdown
bext-server cache stats

# Running apps, render worker count, memory usage
bext-server ps

# Health check (DB, cache, isolates, plugins)
bext-server health

# Current metrics snapshot
bext-server metrics

For verbose output, add --log-level debug or --log-level trace:

bext-server --log-level debug run

TLS Certificate Errors

ACME challenge failing

Symptom: error: ACME HTTP-01 challenge failed for domain.com

Causes and fixes:

1. Port 80 not reachable. ACME HTTP-01 requires incoming traffic on port 80. Check firewall rules and confirm bext is listening on port 80 (it binds automatically when tls.auto_acme = true).

# Check if port 80 is open
ss -tlnp | grep :80

2. DNS not pointing to this server. The domain must resolve to the server's public IP. Verify with:

dig +short domain.com
curl -I http://domain.com/.well-known/acme-challenge/test

3. Another process on port 80. If nginx or Apache is already bound to port 80, stop it or use bext's nginx takeover mode.

Certificate not renewing

Certificates auto-renew 30 days before expiry. If renewal fails, check the logs:

bext-server --log-level debug run 2>&1 | grep -i acme

Common cause: the ACME account rate limit was hit. Wait 1 hour and restart.

Using custom certificates

If auto-ACME is not suitable, provide cert files directly:

[tls]
auto_acme = false
cert_path = "/etc/ssl/certs/domain.pem"
key_path = "/etc/ssl/private/domain.key"

Ensure the files are readable by the bext process user and the certificate chain is complete (leaf + intermediates).

SSR Failures

V8 pool exhaustion

Symptom: Requests queue up, latency spikes, logs show jsc pool: all workers busy, waiting...

Fix: Increase the render worker count:

[render]
workers = 8    # default is 4, increase for high-traffic SSR

Monitor with render_pool_active and render_pool_idle metrics. If render_pool_idle consistently drops to 0, add more workers.

SSR render timeout

Symptom: error: SSR render timed out after 10000ms

Your React components are taking too long to render server-side. Common causes:

- Fetching data inside components during SSR (move to getServerSideProps or server actions)

- Infinite loops in rendering logic

- Very large component trees

Increase the timeout as a stopgap, then fix the underlying issue:

[render]
timeout_ms = 15000    # default is 10000

Out of memory (OOM) kills

Symptom: render workers getting killed, logs show jsc worker OOM: exceeded 256MB limit

Each render worker has a memory limit to prevent a single render from consuming all RAM:

[render]
worker_memory_limit_mb = 512    # default is 256

If renders consistently hit this limit, audit your SSR code for memory leaks (large data structures, unbounded caches in module scope).

Cache Issues

Stale content after deploy

Symptom: Old content still being served after deploying new code.

The ISR cache and compression cache need to be cleared:

# Purge all caches for an app
bext-server cache purge --app my-app

# Or purge everything
bext-server cache purge --all

If using Redis L2, the purge command clears both L1 and L2. Automated deploys via bext deploy purge caches automatically.

Cache stampede under load

Symptom: High CPU spikes when cache entries expire under heavy traffic.

bext's stampede guard coalesces concurrent requests for the same uncached URL. Verify it is active:

bext-server cache stats --json | jq '.stampede_coalesced'

If stampede is still occurring, ensure stale-while-revalidate (SWR) is configured. SWR serves the stale entry while one background request refreshes it:

[cache.isr]
default_swr_ms = 3_600_000    # serve stale for up to 1 hour while revalidating

ISR cache growing too large on disk

The ISR cache is in-memory by default (bounded by max_entries). If you have Redis L2 enabled, Redis may grow. Set a TTL on L2 entries:

[cache.isr]
l2_ttl_ms = 300_000    # Redis entries expire after 5 minutes

Plugin Issues

Plugin timeout

Symptom: warn: plugin "my-plugin" timed out after 5000ms

Plugins have a default execution timeout to prevent runaway code:

[plugins]
timeout_ms = 10000    # increase from default 5000

For WASM plugins, check if the plugin is doing expensive I/O. For QuickJS plugins, check for infinite loops.

Plugin crash / panic

Symptom: error: plugin "my-plugin" panicked: ...

Plugins run in isolated sandboxes, so a crash does not take down the server. The plugin is disabled for subsequent requests until reloaded.

Reload plugins without restarting the server:

kill -HUP $(cat /run/bext.pid)

Check the plugin's logs at debug level to see the panic backtrace.

Plugin memory limits

WASM plugins have configurable memory limits:

[plugins.wasm]
max_memory_mb = 64    # per-plugin memory limit

High CPU Usage

Transform pipeline

If CPU is high and the http_request_duration_seconds metric shows long tails, the transform pipeline (TypeScript compilation, JSX transforms) may be the bottleneck. In production, transforms should happen at build time, not per-request:

# Build once for production
bext-server build

# Then run the built output
bext-server run

Image optimization

On-the-fly image resizing is CPU-intensive. Enable the image cache to avoid reprocessing:

[image]
cache = true
cache_dir = "/var/cache/bext/images"
max_width = 2048
quality = 80

Pre-generate common sizes during build for high-traffic images.

Disk Space

Log files growing

Enable log rotation:

[logging]
rotate = true
rotate_max_size_mb = 100
rotate_max_files = 10
rotate_compress = true

Or use logrotate externally:

/var/log/bext/*.log {
    daily
    rotate 14
    compress
    missingok
    notifempty
    postrotate
        kill -USR1 $(cat /run/bext.pid)
    endscript
}

The USR1 signal tells bext to reopen log files after rotation.

Image cache growth

Set a maximum size for the image optimization cache:

[image]
cache_max_size_mb = 2048    # evict LRU entries above 2GB

Getting Help

If these steps do not resolve your issue:

1. Run bext-server check --fail-on-warnings and include the output 2. Set log level to debug and capture the relevant log lines 3. Check the GitHub Issues for known problems 4. Open a new issue with your bext version (bext-server version), OS, config file (redact secrets), and the full error output