Zentinel includes a comprehensive testing framework validating performance, stability, and resilience. All results below are from actual test runs.
Test Coverage Overview
| Category | Tests | Status |
|---|---|---|
| Unit Tests | 322 tests across all crates | Passing |
| Integration Tests | Full proxy stack with agents | Passing |
| Chaos Tests | 10 failure scenarios | Passing |
| Soak Tests | 24-72 hour stability tests | Passing |
Proxy Comparison Benchmarks
Five reverse proxies benchmarked head-to-head on the same hardware, same backend, same load. Native binaries only — no Docker overhead.
Test Environment
Methodology: Each proxy is started as a native binary, warmed up, then benchmarked for 60 seconds with oha. Resource usage (CPU, memory) is sampled every second via ps. Proxies are tested sequentially with 10-second cooldowns between runs to avoid contention.
Throughput
Latency Distribution
Resource Efficiency
Summary
| Proxy | RPS | p50 | p99 | Peak Mem | Avg CPU | Success |
|---|---|---|---|---|---|---|
| Loading... | ||||||
Component Latency
| Operation | Latency |
|---|---|
| Core rate limiting | < 100μs |
| Geo filtering (MaxMind/IP2Location) | < 100μs |
| Token bucket algorithm | ~50μs |
| Agent IPC (Unix socket round-trip) | 100-500μs |
| Distributed rate limit (Redis) | 1-5ms |
Core operations like rate limiting and geo filtering are sub-100μs, meaning they add negligible overhead to request processing. The agent IPC overhead (100-500μs) is the primary cost of extensibility, which is why latency-critical features like rate limiting were moved into the core.
WAF Engine Benchmarks (zentinel-modsec)
Pure Rust ModSecurity implementation with full OWASP CRS compatibility (v0.1.4). Benchmarks run on macOS ARM64 using Criterion.
phase:2, but the benchmark stopped after phase 1, and the attack payloads did not match its pattern. That was true of both engines, so the comparison measured transaction setup overhead rather than rule evaluation, and reported it as throughput. Body-processing and rule-parsing figures were unaffected — those always ran the rule. Reported as zentinel-modsec#15 and corrected in #16, which also added assertions so an empty hot path now fails the benchmark. Operator, transformation and rule-parsing figures were unaffected. Re-measured again in August 2026 after #17 and #18 landed (populating MULTIPART_PART_HEADERS and fixing chain semantics), which add real per-request work: throughput moved from 797K to 676K req/s.Throughput
| Traffic Type | Time/Request | Throughput |
|---|---|---|
| Clean traffic | 2.69 µs | 372K req/s |
| Attack traffic | 2.17 µs | 460K req/s |
| Mixed (80/20) | 2.55 µs | 392K req/s |
Detection Operators
| Operator | Match | No Match | Description |
|---|---|---|---|
@pm | 25 ns | 18 ns | Multi-pattern (Aho-Corasick) |
@contains | 59 ns | 48 ns | String contains |
@detectXSS | 57 ns | 171 ns | XSS detection |
@rx | 80 ns | 28 ns | Regex matching |
@detectSQLi | 123 ns | 291 ns | SQL injection detection |
Rule Parsing
Optimized with lazy regex compilation for fast rule loading.
| Rule Type | Parse Time |
|---|---|
| Simple rule | 1.21 µs |
| SQLi detection rule | 1.73 µs |
| XSS detection rule | 1.46 µs |
| Chain rule | 2.45 µs |
| Complex rule (transforms) | 2.75 µs |
Rule parsing is now 3.9x faster than libmodsecurity for complex rules.
Transformations
| Transformation | Latency |
|---|---|
t:lowercase | 17 ns |
t:base64Decode | 39 ns |
t:urlDecode | 61 ns |
t:cmdLine | 84 ns |
t:normalizePath | 173 ns |
t:htmlEntityDecode | 225 ns |
Body Processing Throughput
| Body Size | Processing Time | Throughput |
|---|---|---|
| 0 bytes | 1.04 µs | - |
| 100 bytes | 3.67 µs | 27 MB/s |
| 1 KB | 27.2 µs | 37 MB/s |
| 10 KB | 263 µs | 38 MB/s |
| 100 KB | 2.43 ms | 41 MB/s |
Key Findings:
- Sub-microsecond transaction processing enables >900K requests/second
- XSS detection is faster than SQLi (57ns vs 123ns) due to optimized Aho-Corasick + RegexSet
- Pattern matching (@pm) uses Aho-Corasick for O(n) multi-pattern search
- Body processing scales linearly at ~40 MB/s throughput
- Pure Rust - zero C/C++ dependencies, full OWASP CRS compatibility
Rust vs C++: zentinel-modsec vs libmodsecurity
| Benchmark | zentinel-modsec | libmodsecurity | Speedup |
|---|---|---|---|
| Clean request | 1.34 µs | 5.65 µs | 4.2x faster |
| SQLi attack request | 1.40 µs | 16.03 µs | 11.5x faster |
| POST body with SQLi | 1.45 µs | 12.91 µs | 8.9x faster |
| Clean traffic throughput | 1.48 µs | 5.97 µs | 4.0x faster |
| Attack traffic throughput | 1.43 µs | 16.06 µs | 11.3x faster |
| Metric | zentinel-modsec | libmodsecurity |
|---|---|---|
| Clean request throughput | 676K req/s | 168K req/s |
| Attack detection throughput | 701K req/s | 62K req/s |
Rule Parsing Comparison
| Rule Type | zentinel-modsec | libmodsecurity | Speedup |
|---|---|---|---|
| Simple rule | 1.24 µs | 3.55 µs | 2.9x faster |
| Complex rule | 2.73 µs | 10.58 µs | 3.9x faster |
- Zero-copy parsing -
Cow<str>avoids allocations when data is unchanged - PHF (Perfect Hash Functions) - O(1) operator/variable name lookup vs linear search
- Lazy regex compilation - defer compilation to first use, not parse time
- Aho-Corasick - O(n) multi-pattern matching for
@pmoperator - RegexSet - single-pass evaluation of multiple regex patterns
- No FFI overhead - no memory allocation crossing language boundaries
- No std::string copies - Rust’s ownership model eliminates defensive copying
Soak Test Results
Extended duration tests validate stability and detect memory leaks or resource exhaustion.
1-Hour Soak Test (2026-01-01)
| Metric | Value |
|---|---|
| Duration | 1 hour |
| Total Requests | 1,000,000 |
| Throughput | 775 RPS |
| Average Latency | 13.9ms |
| p50 Latency | 1.1ms |
| p95 Latency | 32.3ms |
| p99 Latency | 61.5ms |
| Success Rate | 99.95% |
Memory Analysis
| Metric | Value |
|---|---|
| Initial Memory | 12 MB |
| Final Memory | 1 MB |
| Memory Growth | -91% |
| Verdict | No memory leak detected |
Key Findings:
- Memory decreased over time, demonstrating efficient Rust memory management
- Throughput remained stable throughout the test
- 99% of requests completed in under 62ms
- Connection errors (0.05%) occurred only during startup/shutdown phases
Leak Detection Methodology
Our analysis uses linear regression to detect memory growth patterns:
| Threshold | Verdict | Action |
|---|---|---|
| Growth < 10% | NO LEAK | Pass |
| Growth 10-20% | WARNING | Investigate |
| Growth > 20% | LEAK DETECTED | Fail CI |
Analysis Method:
- Linear regression on memory samples over time
- R-squared correlation to detect consistent growth
- Monotonic increase ratio (>80% samples increasing = suspicious)
Test Configuration
From tests/soak/soak-config.kdl:
system {
worker-threads 2
max-connections 10000
graceful-shutdown-timeout-secs 30
}
| Parameter | Value |
|---|---|
| Duration | 24 hours (standard), 72 hours (extended) |
| Sustained RPS | 100 requests/second |
| Concurrent Connections | 10 |
| Sampling Interval | 60 seconds |
| Backend | Simple HTTP echo service |
Chaos Testing
Fault injection validates resilience under failure conditions. All scenarios are in tests/chaos/.
Test Scenarios (10 Total)
Agent Failure Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Agent Crash | Kill agent process | Fail-open: traffic continues; Fail-closed: 503 |
| Agent Timeout | Freeze agent (1s timeout) | Request fails after timeout, no hang |
| Circuit Breaker | 5+ consecutive failures | CB opens, fast-fails requests, recovers |
Upstream Failure Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Backend Crash | Kill primary backend | Health check detects in ~15s, returns 502/503 |
| Backend Failover | Primary down, secondary up | Traffic routes to secondary |
| All Backends Down | Kill all backends | Graceful 503, no crash |
| Backend 5xx | Backend returns errors | Retry policy triggers, metrics recorded |
Resilience Tests
| Scenario | What We Test | Expected Behavior |
|---|---|---|
| Fail-Open Mode | Agent failure on /failopen/* | Traffic continues to backend |
| Fail-Closed Mode | Agent failure on /protected/* | Traffic blocked with 503 |
| Health Recovery | Backend restart after failure | Auto-recovery in ~15s |
| Memory Stability | 20 chaos cycles | No memory leaks |
Circuit Breaker Behavior
The circuit breaker protects the system from cascading failures when agents become unhealthy. It operates as a state machine with three states:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ 5 failures ┌──────────┐ 10s timeout │
│ │ CLOSED │ ─────────────> │ OPEN │ ─────────────┐ │
│ │ (normal) │ │(fast-fail│ │ │
│ └────▲─────┘ └──────────┘ │ │
│ │ ▼ │
│ │ 2 successes ┌───────────┐ │
│ └───────────────────── │ HALF-OPEN │ ◄───────────┘ │
│ │ (probe) │ │
│ └───────────┘ │
│ │ │
│ │ failure │
│ └──────────> back to OPEN │
│ │
└─────────────────────────────────────────────────────────────────┘
State Descriptions:
- CLOSED (green): Normal operation. All requests pass through to the agent. Failures are counted.
- OPEN (red): Circuit is tripped. Requests are immediately failed without contacting the agent. This prevents hammering a broken service.
- HALF-OPEN (yellow): After a timeout, the circuit allows a limited number of probe requests. If they succeed, the circuit closes. If they fail, it reopens.
Configuration from chaos-config.kdl:
circuit-breaker {
failure-threshold 5 // Opens after 5 consecutive failures
success-threshold 2 // Closes after 2 successes in half-open
timeout-seconds 10 // Time before transitioning to half-open
half-open-max-requests 2 // Probe requests allowed in half-open
}
Recovery Timeline
Recovery Times
| Failure Type | Detection Time | Recovery Time |
|---|---|---|
| Agent crash (fail-open) | Immediate | N/A (bypassed) |
| Agent crash (fail-closed) | Immediate | On agent restart |
| Backend crash | ~15s (3 health checks) | ~10s after restart |
| Circuit breaker trip | Immediate | 10s + 2 successes |
Security Validation
WAF Testing with wafworth
598 test cases across 18 OWASP-aligned attack categories, tested against three WAF engine configurations on Zentinel. Results from wafworth run on 2026-03-04.
Overall Comparison
| Metric | zentinel‑waf | zentinel‑zentinelsec | zentinel‑modsec |
|---|---|---|---|
| Detection Rate | 43.1% | 38.7% | 32.1% |
| FP Rate | 9.0% | 2.5% | 2.5% |
| Precision | 94.9% | 98.4% | 98.1% |
| F1 Score | 0.59 | 0.56 | 0.48 |
| Balanced Accuracy | 67.0% | 68.1% | 64.8% |
| p50 Latency | 2.49ms | 2.59ms | 2.54ms |
| p99 Latency | 3.04ms | 3.60ms | 3.49ms |
Per-Category Detection Rates
| Category | zentinel‑waf | zentinel‑zentinelsec | zentinel‑modsec |
|---|---|---|---|
| Cross-Site Scripting | 73% | 70% | 70% |
| Path Traversal | 80% | 50% | 53% |
| SQL Injection | 38% | 67% | 25% |
| SSRF | 80% | 60% | 60% |
| Scanner Detection | 80% | 7% | 33% |
| Known CVEs | 54% | 19% | 16% |
| WAF Bypass | 56% | 52% | 48% |
| SSTI | 60% | 53% | 47% |
| Evasion Techniques | 24% | 52% | 48% |
| Command Injection | 3% | 3% | 3% |
| XXE | 0% | 0% | 0% |
| File Upload | 0% | 0% | 0% |
wafworth run --encodings plain,url,hex to test encoding bypass resilience. See the wafworth repo for full methodology.Integration Test Environment
Full integration tests run in Docker Compose with:
| Service | Port | Purpose |
|---|---|---|
| Zentinel Proxy | 8080 | Main reverse proxy |
| Metrics Endpoint | 9090 | Prometheus metrics |
| Backend (httpbin) | 8081 | Test backend |
| Echo Agent | UDS | Header manipulation |
| Rate Limit Agent | 9092 | Token bucket limiting |
| WAF Agent | 9094 | OWASP CRS protection |
| Prometheus | 9091 | Metrics collection |
| Grafana | 3000 | Dashboards |
| Jaeger | 16686 | Distributed tracing |
Running the Tests
Unit Tests
cargo test --workspace
Integration Tests
cd tests
./integration_test.sh # Full suite
./integration_test.sh --quick # Smoke tests only
Chaos Tests
cd tests/chaos
make quick # 4 core scenarios (~5 min)
make test # All 10 scenarios (~20 min)
make test-circuit-breaker # Single scenario
Soak Tests
cd tests/soak
./run-soak-test.sh --duration 1 # 1 hour quick test
./run-soak-test.sh --duration 24 # Standard 24h test
./run-soak-test.sh --duration 72 # Extended 72h test
# Analyze results
python3 analyze-results.py results/<timestamp>/
Prometheus Metrics
| Metric | Description |
|---|---|
zentinel_requests_total | Total requests by route/status |
zentinel_request_duration_seconds | Latency histogram |
zentinel_upstream_healthy_backends | Backend health status |
zentinel_upstream_retries_total | Retry attempts |
zentinel_agent_circuit_breaker_state | CB state (0=closed, 1=open, 2=half-open) |
zentinel_agent_failures_total | Agent communication failures |
zentinel_agent_timeouts_total | Agent timeout events |
zentinel_agent_bypasses_total | Fail-open bypass count |
Milestone Achievements
| Milestone | Status | Key Metrics |
|---|---|---|
| M2: Cacheable | Complete | 23K RPS load tested |
| M4: Scalable | Complete | Redis/Memcached distributed rate limiting |
| M5: Observable | Complete | OpenTelemetry, Grafana dashboards |
| M6: Optimized | Complete | Core rate limiting < 100μs, geo filtering < 100μs |
How to Reproduce
All benchmark scripts live in the main repository. To reproduce any result on your own hardware:
Proxy Comparison
git clone https://github.com/zentinelproxy/zentinel
cd zentinel
# Install proxies you want to compare (Zentinel, nginx, HAProxy, Caddy, Envoy)
# Then run the benchmark suite:
cd tests/bench
./proxy-bench.sh # All proxies, default settings
./proxy-bench.sh --proxy zentinel # Single proxy
./proxy-bench.sh --duration 120 # Custom duration (seconds)
./proxy-bench.sh --connections 200 # Custom concurrency
The script uses oha as the load generator. Each proxy is started as a native binary (no Docker), warmed up for 5 seconds, then benchmarked for 60 seconds at the configured concurrency. CPU and memory are sampled every second via ps. Proxies are tested sequentially with 10-second cooldowns between runs.
Configuration: All proxies use equivalent minimal configs — single upstream, no TLS, no caching, keepalive enabled. Configs are in tests/bench/configs/.
WAF Benchmarks
cd tests/bench
./waf-bench.sh # zentinel-modsec vs libmodsecurity
Soak & Chaos Tests
cd tests/soak
./run-soak-test.sh --duration 1 # 1-hour quick test
cd tests/chaos
make quick # 4 core scenarios
make test # All 10 scenarios
Results are written to results/<timestamp>/ as JSON. The raw data behind the charts on this page is available at benchmark-results.json.
Known Gaps
We’re actively working on:
| Gap | Status |
|---|---|
| Criterion microbenchmarks | Complete |
| High-concurrency (10k+ conn) tests | Planned |
| Property-based fuzzing | Planned |
| HTTP request smuggling tests | Planned |
| Comparison benchmarks vs nginx/HAProxy/Caddy | Complete |
Last updated: March 2026 | Zentinel v0.4.2