This guide gets Spooky from zero to first successful traffic with the fewest moving parts possible.

Total time: about 5 minutes.

Prerequisites

  • Rust 1.85+ (edition 2024) — rustup update stable
  • curl with HTTP/3 support — the curl that ships with macOS does not include HTTP/3. Install one that does: bash brew install curl # then use $(brew --prefix curl)/bin/curl in the commands below, or put it first on PATH
  • Python 3 for the simplest local backend: python3 --version
  • UDP port 9889 free — QUIC runs over UDP. Check with lsof -iUDP:9889.

What You Will Run

This quickstart uses:

  • a self-signed certificate for local TLS
  • a small local HTTP backend on 127.0.0.1:8080
  • one catch-all upstream in Spooky
  • one HTTP/3 request to confirm first traffic

Step 1: Build

git clone https://github.com/Supernova-Labs-Org/spooky.git
cd spooky
cargo build --release

The binary lands at target/release/spooky.

Step 2: Generate a Certificate

QUIC requires TLS 1.3. For local testing, a self-signed certificate works fine:

mkdir -p certs
openssl req -x509 -newkey rsa:4096 -nodes \
  -keyout certs/key.pem \
  -out certs/cert.pem \
  -days 365 \
  -subj "/CN=localhost"

Production: see TLS Setup.

Step 3: Start a Test Backend

Spooky supports:

  • HTTP/2 upstream transport for https:// backends
  • HTTP/1.1 upstream transport for http:// backends

For the fastest local test, use a simple HTTP/1.1 backend:

mkdir -p /tmp/spooky-demo
printf 'hello from backend\n' > /tmp/spooky-demo/index.html
cd /tmp/spooky-demo
python3 -m http.server 8080

Leave this running in its own terminal.

Step 4: Write the Config

Create config.yaml in the repository root:

version: 1                        # config schema version — must be 1

listen:
  protocol: http3                 # accept QUIC/HTTP/3 on this socket
  port: 9889                      # UDP port clients connect to
  address: "0.0.0.0"             # bind all interfaces; use 127.0.0.1 for loopback-only
  tls:
    cert: "certs/cert.pem"        # path to PEM-encoded certificate chain
    key: "certs/key.pem"          # path to PEM-encoded private key

upstream:
  default:                        # pool name — referenced internally; "default" catches all unmatched routes
    load_balancing:
      type: round-robin           # distribute requests evenly across backends in order
    route:
      path_prefix: "/"            # match every request path
    backends:
      - id: backend-1             # arbitrary label shown in logs
        address: "http://127.0.0.1:8080" # cleartext HTTP/1.1 backend for the local demo
        weight: 100               # relative share of traffic (only meaningful with multiple backends)

log:
  level: info                     # debug | info | warn | error

Step 5: Start Spooky

./target/release/spooky --config config.yaml

You should see:

INFO spooky: loading config path="config.yaml"
INFO spooky: listening on 0.0.0.0:9889 protocol=http3
INFO spooky: upstream ready upstream=default backends=1

Step 6: Verify HTTP/3

6a. Force HTTP/3 (confirms QUIC is working)

curl --http3-only -k https://localhost:9889/

--http3-only refuses to fall back to TCP. If this succeeds, QUIC is live.

Expected body:

hello from backend

6b. Check the control API health endpoint

In another terminal:

curl -sk --http1.1 https://127.0.0.1:9902/health

Expected response:

{"status":"ok", ...}

6c. Verify the Alt-Svc upgrade path (mimics browser behavior)

Browsers don't start with HTTP/3 — they discover it via the Alt-Svc response header on a regular HTTPS request, then switch on the next connection. Test that Spooky sends this header correctly:

curl -k -I https://localhost:9889/

Look for this line in the response headers:

alt-svc: h3=":9889"; ma=86400

h3=":9889" tells the client that HTTP/3 is available on port 9889. ma=86400 is the max-age in seconds (24 hours) — how long the client should remember and prefer HTTP/3 for this origin.

If you see this header, Spooky is correctly advertising HTTP/3 to clients that don't yet support it or haven't upgraded yet.

Common Issues

Error: Address already in use — something else is bound to UDP 9889. Find it with lsof -iUDP:9889 and stop it, or change port in config.yaml.

Failed to connect to backend — the local backend is not running, or is on a different port. Confirm it is up with curl http://127.0.0.1:8080/.

Failed to load TLS certificate — the paths in config.yaml don't match where you generated the files. Both certs/cert.pem and certs/key.pem must exist relative to the working directory you launch Spooky from.

curl falls back to HTTP/2 silently — you're using the system curl, which lacks HTTP/3 support. Use brew install curl and invoke it with the full path, or check curl --version for HTTP/3 in the features list.

Next Steps