This guide covers fundamental concepts and basic usage of Spooky, an HTTP/3 to HTTP/2 edge proxy.
Architecture Overview
Spooky operates as a protocol translation layer between HTTP/3 clients and HTTP/2 backend services:
- QUIC Connection Termination: Accepts incoming HTTP/3 requests over QUIC
- Protocol Translation: Converts HTTP/3 streams to HTTP/2 requests
- Load Balancing: Routes requests to backend servers based on configured algorithms
- Response Conversion: Translates backend HTTP/2 responses back to HTTP/3
- Client Delivery: Returns responses to the client over QUIC
Client (HTTP/3/QUIC) → Spooky Edge → Backend (HTTP/2)
↓
Route Matching
Load Balancing
Health Checking
Configuration Structure
Minimal Configuration
version: 1
listen:
protocol: http3
port: 9889
address: "0.0.0.0"
tls:
cert: "server.crt"
key: "server.key"
upstream:
default_pool:
load_balancing:
type: "random"
route:
path_prefix: "/"
backends:
- id: "backend1"
address: "127.0.0.1:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
log:
level: info
Configuration Sections
listen: Defines the edge server configuration
- protocol: Must be "http3"
- port: UDP port for QUIC connections (default: 9889)
- address: Bind address (default: "0.0.0.0")
- tls.cert: Path to TLS certificate
- tls.key: Path to TLS private key
upstream: Named pools of backend servers with routing rules and load balancing configuration
- Key: Arbitrary pool name for identification
- load_balancing: Load balancing algorithm for this pool (random, round-robin, consistent-hash)
- route: Routing criteria to match requests
- backends: List of backend servers
log: Logging configuration
- level: Log verbosity (trace, debug, info, warn, error)
- file.enabled: Write logs to a file instead of stderr (default: false)
- file.path: Log file path (default: /var/log/spooky/spooky.log)
Upstream Pools and Routing
Spooky supports multiple upstream pools with independent routing rules. Requests are matched using longest-prefix matching across all configured routes.
Path-Based Routing
upstream:
api_pool:
route:
path_prefix: "/api"
backends:
- id: "api1"
address: "10.0.1.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
auth_pool:
route:
path_prefix: "/auth"
backends:
- id: "auth1"
address: "10.0.2.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
default_pool:
route:
path_prefix: "/"
backends:
- id: "web1"
address: "10.0.3.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
Routing Behavior:
- Requests to /api/* → api_pool
- Requests to /auth/* → auth_pool
- All other requests → default_pool
Host-Based Routing
upstream:
api_backend:
route:
host: "api.example.com"
backends:
- id: "api1"
address: "10.0.1.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
www_backend:
route:
host: "www.example.com"
backends:
- id: "web1"
address: "10.0.2.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
Combined Routing
upstream:
api_v2:
route:
host: "api.example.com"
path_prefix: "/v2"
backends:
- id: "api-v2-1"
address: "10.0.1.10:8080"
weight: 100
health_check:
path: "/health"
interval: 5000
Matches requests to api.example.com/v2/*.
Backend Configuration
Backend Parameters
backends:
- id: "backend1" # Unique identifier
address: "127.0.0.1:8080" # Backend address (IP:port)
weight: 100 # Relative weight for load balancing
health_check:
path: "/health" # Health check endpoint
interval: 5000 # Check interval (milliseconds)
timeout_ms: 2000 # Request timeout (milliseconds)
failure_threshold: 3 # Consecutive failures before marking unhealthy
success_threshold: 2 # Consecutive successes before marking healthy
cooldown_ms: 5000 # Cooldown period after marking unhealthy
Health Check Configuration
interval: Time between health checks in milliseconds (default: 5000)
timeout_ms: Maximum time to wait for health check response (default: 1000)
failure_threshold: Number of consecutive failures required to mark backend unhealthy (default: 3)
success_threshold: Number of consecutive successes required to mark backend healthy after being unhealthy (default: 2)
cooldown_ms: Time to wait before attempting recovery after marking unhealthy (default: 5000)
Health Check Implementation
Backend services must implement health check endpoints that return 2xx status codes when healthy:
// Node.js example
app.get('/health', (req, res) => {
// Verify critical dependencies
if (database.isConnected() && cache.isReady()) {
res.status(200).json({ status: 'healthy' });
} else {
res.status(503).json({ status: 'unhealthy' });
}
});
# Python example
@app.route('/health')
def health():
if check_database() and check_cache():
return {'status': 'healthy'}, 200
return {'status': 'unhealthy'}, 503
Command Line Interface
Starting Spooky
# Start with configuration file
spooky --config config.yaml
# Display version
spooky --version
Command Line Options
| Option | Description | Default |
|---|---|---|
--config |
Path to configuration file | Required |
--version |
Display version information | - |
--help |
Display help information | - |
Note: Log level is configured in config.yaml (log.level) or via RUST_LOG environment variable. Configuration validation happens automatically during startup.
Testing and Verification
Testing with curl
Requires curl built with HTTP/3 support (nghttp3 and ngtcp2):
# Basic HTTP/3 request
curl --http3-only -k https://localhost:9889/
# Test with custom host resolution
curl --http3-only -k \
--resolve example.com:9889:127.0.0.1 \
https://example.com:9889/api/users
# Test with headers
curl --http3-only -k \
-H "Authorization: Bearer token" \
https://localhost:9889/protected
# Verbose output for debugging
curl --http3-only -k -v https://localhost:9889/
Load Balancing Verification
# Generate concurrent requests
for i in {1..20}; do
curl --http3-only -k https://localhost:9889/ &
done
wait
# Monitor backend request distribution (systemd)
sudo journalctl -u spooky.service | grep "routing to backend" | \
awk '{print $NF}' | sort | uniq -c
# Monitor backend request distribution (if redirected to file)
grep "routing to backend" /var/log/spooky/spooky.log | \
awk '{print $NF}' | sort | uniq -c
Health Check Monitoring
# Monitor health check activity (systemd)
sudo journalctl -u spooky.service -f | grep -i health
# Monitor health check activity (direct process)
spooky --config config.yaml 2>&1 | grep -i health
# Check backend status (systemd)
sudo journalctl -u spooky.service | grep "backend.*healthy" | tail -20
# Check backend status (if redirected to file)
grep "backend.*healthy" /var/log/spooky/spooky.log | tail -20
Logging
Log Levels
| Level | Description | Use Case |
|---|---|---|
trace |
Extremely verbose, includes protocol details | Protocol debugging |
debug |
Detailed operational information | Development, troubleshooting |
info |
General operational messages | Production (default) |
warn |
Warning conditions | Production |
error |
Error conditions | Production |
Log Configuration
# stderr (default)
log:
level: info
# write to file
log:
level: info
file:
enabled: true
path: /var/log/spooky/spooky.log
Log Analysis
systemd (log.file.enabled: false)
# Follow logs in real-time
sudo journalctl -u spooky.service -f
# Filter by severity
sudo journalctl -u spooky.service | grep ERROR
# Search for specific requests
sudo journalctl -u spooky.service | grep "GET /api/users"
# Monitor backend selection
sudo journalctl -u spooky.service | grep "routing to backend"
# Track health check failures
sudo journalctl -u spooky.service | grep "health check failed"
File (log.file.enabled: true)
# Follow logs in real-time
tail -f /var/log/spooky/spooky.log
# Filter by severity
grep ERROR /var/log/spooky/spooky.log
# Search for specific requests
grep "GET /api/users" /var/log/spooky/spooky.log
# Monitor backend selection
grep "routing to backend" /var/log/spooky/spooky.log
# Track health check failures
grep "health check failed" /var/log/spooky/spooky.log
Common Deployment Patterns
Development Setup
# Generate self-signed certificate
openssl req -x509 -newkey rsa:2048 \
-keyout server.key -out server.crt \
-days 365 -nodes -subj "/CN=localhost"
# Create development configuration
cat > dev-config.yaml <<EOF
version: 1
listen:
protocol: http3
port: 9889
address: "127.0.0.1"
tls:
cert: "server.crt"
key: "server.key"
upstream:
dev_pool:
load_balancing:
type: "random"
route:
path_prefix: "/"
backends:
- id: "dev-backend"
address: "127.0.0.1:3000"
weight: 100
health_check:
path: "/health"
interval: 5000
log:
level: debug
EOF
# Start Spooky
spooky --config dev-config.yaml
Example Multi-Backend Setup
Note: Spooky is experimental. The configuration below shows how a multi-backend setup would look, but is not a production deployment recommendation.
# Obtain certificates (Let's Encrypt)
certbot certonly --standalone -d example.com
# Create production configuration
cat > prod-config.yaml <<EOF
version: 1
listen:
protocol: http3
port: 443
address: "0.0.0.0"
tls:
cert: "/etc/letsencrypt/live/example.com/fullchain.pem"
key: "/etc/letsencrypt/live/example.com/privkey.pem"
upstream:
prod_pool:
load_balancing:
type: "round-robin"
route:
path_prefix: "/"
backends:
- id: "web-01"
address: "10.0.1.10:8080"
weight: 100
health_check:
path: "/health"
interval: 10000
timeout_ms: 3000
failure_threshold: 3
success_threshold: 2
cooldown_ms: 30000
- id: "web-02"
address: "10.0.1.11:8080"
weight: 100
health_check:
path: "/health"
interval: 10000
timeout_ms: 3000
failure_threshold: 3
success_threshold: 2
cooldown_ms: 30000
log:
level: info
EOF
# Deploy as systemd service
sudo systemctl start spooky
sudo systemctl enable spooky
Troubleshooting
Connection Issues
# Verify Spooky is listening on UDP
sudo netstat -uln | grep 9889
# or
sudo ss -uln | grep 9889
# Check firewall configuration
sudo ufw status
sudo iptables -L -n -v | grep 9889
# Test UDP connectivity
nc -u -v -z localhost 9889
# Verify TLS certificate
openssl s_client -connect localhost:9889 -showcerts
Backend Connectivity
# Test backend directly
curl -v http://127.0.0.1:8080/health
# Test through Spooky
curl --http3-only -k -v https://localhost:9889/health
# Check backend reachability from Spooky host
telnet 10.0.1.10 8080
Configuration Validation
# Validate configuration syntax (startup validation happens before serving)
spooky --config config.yaml
# Check for YAML syntax errors
yamllint config.yaml
# Verify routing configuration
grep -A 10 "route:" config.yaml
Performance Debugging
# Monitor system resources
top -p $(pgrep spooky)
htop -p $(pgrep spooky)
# Check UDP buffer statistics
netstat -su | grep Udp
# Monitor QUIC connections
ss -u -a | grep 9889
# Check for packet loss
netstat -s | grep -i lost
Next Steps
- Review Load Balancing for detailed algorithm documentation
- Refer to Configuration Reference for complete parameter documentation
- See Deployment Guide for production deployment best practices