Skip to the content.

Configuration Recipes

Ready-to-use configs for common scenarios. Each recipe shows the YAML version with inline comments. Where a JSON equivalent exists in examples/, a link appears after the code block.

For the full field reference see configuration.md.


Table of Contents


Getting started

Minimal — static files + proxy

The smallest useful configuration.

# examples/minimal.yaml
port: 8080
static: ./dist
proxy:
  /api: "http://localhost:4000"
GET /            → serves ./dist/index.html
GET /styles.css  → serves ./dist/styles.css
GET /api/users   → proxied to http://localhost:4000/api/users

→ JSON: examples/minimal.json


Local dev server

Browser hot reload, open CORS, colorized logs, SPA fallback.

# examples/dev-hot-reload.yaml
port: 3000
logging: dev
cors: true
hotReload:
  extensions: [html, css, js, ts, jsx, tsx, json]
static: ./src
proxy:
  /api: "http://localhost:4000"
fallback:
  file: ./src/index.html
  status: 200

→ JSON: examples/dev-hot-reload.json


SPA + API (production)

Pre-compressed assets, least-conn balancing, API cache, content-aware fallback.

# examples/spa-with-api.yaml
port: 443
tls:
  cert: /etc/tls/cert.pem
  key: /etc/tls/key.pem
  httpRedirectPort: 80     # redirect port 80 → 443 automatically

http2: true
securityHeaders: true
compression: true

cors:
  origins: ["https://app.example.com"]
  credentials: true

logging:
  format: json
  file: /var/log/conduit/access.log
  skipPaths: [/__health__, /__metrics__]

static: ./dist
staticOptions:
  preCompressed: true      # serve .br/.gz sidecars when available
  maxAge: "1y"             # long-lived Cache-Control for hashed assets

proxy:
  /api:
    targets:
      - "http://api1:4000"
      - "http://api2:4000"
    strategy: least-conn
    stripPrefix: true
    retry:
      attempts: 3
      conditions: [connection_error, "5xx"]
    healthCheck:
      path: /health
      intervalSecs: 10
    cache:
      store: memory
      ttlSecs: 60
      skipIfCookie: true   # don't cache authenticated responses

rateLimit:
  windowSecs: 60
  limit: 300
  skipPaths: [/__health__]

healthCheck: true
metrics:
  path: /__metrics__
  token: "$METRICS_TOKEN"

fallback:
  byAccept:
    html: { file: ./dist/index.html, status: 200 }  # SPA
    json: { body: { error: "Not Found", status: 404 }, status: 404 }

→ JSON: examples/spa-with-api.json


HTTPS

Manual certificates

TLS with your own certificate files — from Let’s Encrypt CLI, Certbot, or a CA.

# examples/tls-h2.yaml
port: 443
tls:
  cert: /etc/tls/fullchain.pem
  key: /etc/tls/privkey.pem
  httpRedirectPort: 80      # redirect port 80 → 443 automatically
  versions: ["TLSv1.2", "TLSv1.3"]
http2: true
securityHeaders: true
proxy:
  /: "http://localhost:4000"

→ JSON: examples/tls-h2.json


Auto-TLS via Let’s Encrypt

Requires cargo build --features acme

Conduit obtains and renews certificates automatically. The domain must point to this server and port 80 must be reachable for the HTTP-01 challenge.

# examples/tls-acme.yaml
port: 443
tls:
  acme:
    email: admin@example.com
    storage: /var/cache/conduit/certs
    challenge: http-01
    # Use staging for testing — no rate limits:
    # directory: "https://acme-staging-v02.api.letsencrypt.org/directory"
  httpRedirectPort: 80
http2: true
securityHeaders: true
proxy:
  /: "http://localhost:4000"

→ JSON: examples/tls-acme.json


mTLS — require client certificates

Every client must present a certificate signed by your CA. Useful for service-to-service auth, B2B APIs, and IoT devices.

# examples/mtls.yaml
port: 443
tls:
  cert: /etc/tls/server.crt
  key: /etc/tls/server.key
  clientAuth:
    ca: /etc/tls/client-ca.crt  # CA that signs authorized client certs
    optional: false              # reject connections without a valid cert
proxy:
  /api:
    targets: ["http://backend:4000"]
    stripPrefix: true

→ JSON: examples/mtls.json — includes certificate generation commands.


Authentication

JWT with JWKS (Auth0 / Cognito / Google)

Requires cargo build --features jwt

Validates Authorization: Bearer <token> on every request. Inject validated claims as upstream headers so backends don’t need to re-validate.

# examples/jwt-auth.yaml
port: 8080

jwtAuth:
  jwksUrl: "https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json"
  audience: ["https://api.example.com"]
  issuer: "https://YOUR_DOMAIN.auth0.com"
  skipPaths: [/__health__]

# Forward identity to the backend — it trusts these headers, not raw JWT
requestTransform:
  setHeaders:
    X-User-ID: ""
    X-User-Email: ""
  removeHeaders: [Authorization]

proxy:
  /api: "http://backend:4000"
healthCheck: true

→ JSON: examples/jwt-auth.json


API key with multiple keys

Rotate keys without downtime by keeping both old and new active.

port: 8080

apiKey:
  keys:
    - "$API_KEY_V2"   # new key
    - "$API_KEY_V1"   # old key — remove after all clients migrate
  header: X-API-Key
  skipPaths: [/__health__, /public/**]

proxy:
  /api: "http://backend:4000"

Named consumer tiers

Requires cargo build --features consumers

Each API client gets its own credentials, rate limit, and upstream headers. Useful for developer portals and partner APIs.

# examples/consumers.yaml
port: 8080

consumers:
  idHeader: X-Consumer-ID
  skipPaths: [/__health__]
  consumers:
    - username: free-tier-client
      apiKey: "$FREE_KEY"
      rateLimit: { windowSecs: 60, limit: 60 }
      headers: { X-Tier: free }

    - username: premium-client
      apiKey: "$PREMIUM_KEY"
      rateLimit: { windowSecs: 60, limit: 6000 }
      headers: { X-Tier: premium, X-SLA: "99.9" }

    - username: internal-service
      basicAuth: { password: "$INTERNAL_PASSWORD" }
      headers: { X-Internal: "true" }

proxy:
  /api: "http://backend:4000"

→ JSON: examples/consumers.json


External auth service (Forward Auth)

Requires cargo build --features forward-auth

Delegate every auth decision to an existing service (oauth2-proxy, Ory Oathkeeper, custom SSO middleware). The auth service’s response headers (X-User-ID, X-Role, …) are forwarded to the upstream.

# examples/forward-auth.yaml
port: 8080

forwardAuth:
  url: "http://auth-service:9000/verify"
  requestHeaders: [Authorization, Cookie]    # pass these to the auth service
  responseHeaders: [X-User-ID, X-Role, X-Tenant]  # inject these into upstream
  timeoutMs: 3000
  skipPaths: [/__health__, /login, /public/**]

proxy:
  /api: "http://backend:4000"

→ JSON: examples/forward-auth.json


Load balancing

Weighted round-robin

Send more traffic to powerful instances, or gradually shift traffic during a canary deployment.

# examples/load-balanced.yaml (excerpt)
proxy:
  /api:
    targets:
      - { url: "http://main:4000",   weight: 9 }   # 90%
      - { url: "http://canary:4000", weight: 1 }   # 10% canary
    strategy: weighted-round-robin

Adjust weights at runtime without a reload: conduit upstreams weight --route /api --target http://canary:4000 --weight 2

→ JSON: examples/load-balanced.json


Least connections with health checks

Routes each request to the backend with the fewest active connections. Removes unhealthy backends automatically; ramps traffic back slowly after recovery.

# examples/load-balanced.yaml (excerpt)
proxy:
  /api:
    targets:
      - "http://api1:4000"
      - "http://api2:4000"
      - "http://api3:4000"
    strategy: least-conn
    healthCheck:
      path: /health
      intervalSecs: 10
      unhealthyThreshold: 2
      slowStartSecs: 30   # ramp recovered upstream over 30 s

→ JSON: examples/load-balanced.json


Active/passive failover

Primary cluster handles all traffic; backup receives traffic only when all primaries are unhealthy.

proxy:
  /api:
    targets:
      - "http://primary1:4000"
      - "http://primary2:4000"
    strategy: round-robin
    healthCheck:
      path: /health
      intervalSecs: 10
    backup: "http://dr-site:4000"   # activated only when all primaries are down
    retry:
      attempts: 2
      conditions: [connection_error, "5xx"]

Geo-regional routing (upstream groups)

Outer strategy (ip-hash) pins each client to a region; inner strategy (least-conn) balances within the region.

# examples/upstream-groups.yaml
proxy:
  /api:
    groups:
      - name: us-east
        targets: ["http://us-east-1:4000", "http://us-east-2:4000"]
        strategy: least-conn
      - name: eu-west
        targets: ["http://eu-west-1:4000", "http://eu-west-2:4000"]
        strategy: least-conn
    groupStrategy: ip-hash   # same client IP always hits the same region

→ JSON: examples/upstream-groups.json


Reliability

Circuit breaker + retry budget

503 when all upstreams are saturated. Retry storms limited to 20% of traffic. Outlier detection passively ejects backends that return too many 5xx responses.

# examples/circuit-breaker.yaml
proxy:
  /api:
    targets: ["http://a:4000", "http://b:4000", "http://c:4000"]
    strategy: least-conn
    healthCheck:
      path: /health
      intervalSecs: 10
      maxConnectionsPerUpstream: 100   # circuit breaker: 503 when all hit 100 conn
    backup: "http://replica:4000"      # cold standby activated by circuit breaker
    retry:
      attempts: 3
      conditions: [connection_error, "5xx", timeout]
      backoffMs: 100
      backoffJitter: true              # ±50% spread to avoid thundering herd
      budgetPercent: 20                # at most 20% of active requests are retries
    timeout:
      connectMs: 500
      readMs: 10000
      perTryMs: 3000

outlierDetection:
  consecutive5xx: 5          # eject after 5 consecutive errors
  baseEjectionTimeSecs: 30   # first ejection: 30 s
  maxEjectionTimeSecs: 300   # cap at 5 min with exponential backoff
  maxEjectionPercent: 33     # never eject more than 1/3 of the cluster

maskErrors: true             # replace 5xx bodies with generic JSON

→ JSON: examples/circuit-breaker.json


Response caching with stale-while-revalidate

Requires cargo build --features cache

Zero-latency cache expiry: stale content is served immediately while a background request fetches fresh data.

# examples/stale-while-revalidate.yaml
proxy:
  /api:
    targets: ["http://backend:4000"]
    stripPrefix: true
    cache:
      store: memory
      ttlSecs: 60
      staleWhileRevalidateSecs: 300  # serve stale for up to 5 min while refreshing
      staleIfErrorSecs: 600          # serve stale if upstream is down
      varyHeaders: [Accept-Language] # separate cache entries per language
      skipIfCookie: true             # don't cache authenticated sessions
      skipPaths: [/api/me, /api/cart]

For shared cache across multiple Conduit instances, use store: "redis://host:6379" (--features redis required).

→ JSON: examples/stale-while-revalidate.json


File Upload

Requires cargo build --features upload

Accept multipart file uploads

# examples/file-upload.yaml
port: 8080

upload:
  path: /files              # POST /files/<anything> → handled by upload server
  dir: ./uploads            # destination directory (created if absent)
  fieldName: file           # multipart field name (default: "file")
  maxFileSizeBytes: 10485760   # 10 MB per file
  maxTotalSizeBytes: 20971520  # 20 MB per request
  maxFiles: 5
  allowedMimeTypes:
    - "image/jpeg"
    - "image/png"
    - "application/pdf"

proxy:
  targets: ["http://api:4000"]   # remaining traffic goes to backend

Upload with curl:

curl -X POST http://localhost:8080/files/my-doc \
  -F "file=@document.pdf;type=application/pdf"
# → {"status":"ok","files":[{"name":"<uuid>.pdf","originalName":"document.pdf","size":204800}]}

Files are saved with UUID v4 names (preserving extension) to prevent path traversal and name collisions.

→ JSON: examples/file-upload.json


TCP Proxy

Requires cargo build --features tcp

Forward raw TCP connections without HTTP parsing. Useful for databases (MySQL, PostgreSQL, Redis), SMTP, and other non-HTTP protocols.

# conduit.yaml — TCP passthrough to PostgreSQL
port: 5432
tcp:
  targets:
    - "db-primary:5432"
    - "db-replica:5432"
  strategy: round-robin
  connectTimeoutMs: 3000

Multiple TCP sites from one process:

global:
  admin:
    bind: "127.0.0.1:2019"

sites:
  # MySQL proxy
  - port: 3306
    tcp:
      targets: ["mysql-1:3306", "mysql-2:3306"]
      strategy: round-robin

  # Redis proxy
  - port: 6380
    tcp:
      targets: ["redis-primary:6379"]
      strategy: round-robin

TCP sites are separate from HTTP sites — they cannot share a port. Health checks and TLS termination are not available in TCP mode.


API gateway

Microservices gateway

Route traffic to individual services by path. One place for rate limiting, IP filtering, auth, and metrics.

# examples/api-gateway.yaml
port: 8080
logging: json

ipFilter:
  allow: ["10.0.0.0/8", "172.16.0.0/12"]

rateLimit:
  windowSecs: 60
  limit: 500

proxy:
  /users:   "http://users-svc:4001"
  /orders:  "http://orders-svc:4002"
  /catalog:
    targets: ["http://catalog1:4003", "http://catalog2:4003"]
    strategy: round-robin
    cache:
      store: memory
      ttlSecs: 300
  /payments:
    targets: ["https://payment-svc:8443"]
    upstreamTls: { verify: true }
    rateLimit: { windowSecs: 60, limit: 20, keyBy: "header:X-User-ID" }

healthCheck: true
metrics:
  path: /__metrics__
maskErrors: true

→ JSON: examples/api-gateway.json


JWT gateway with per-route rate limits

Requires cargo build --features jwt

Validate JWT once at the gateway. Inject user identity as headers. Strict per-route limits on expensive endpoints.

port: 443
tls:
  cert: /etc/tls/cert.pem
  key: /etc/tls/key.pem
  httpRedirectPort: 80

jwtAuth:
  jwksUrl: "https://auth.example.com/.well-known/jwks.json"
  audience: ["api.example.com"]
  skipPaths: [/__health__]

requestTransform:
  setHeaders:
    X-User-ID: ""
    X-User-Role: ""
  removeHeaders: [Authorization]

responseTransform:
  removeHeaders: [Server, X-Powered-By]

proxy:
  /v1/users:
    targets: ["http://users:4001", "http://users:4002"]
    strategy: least-conn
    stripPrefix: true
    rateLimit: { windowSecs: 60, limit: 200, keyBy: "header:X-User-ID" }

  /v1/payments:
    targets: ["http://payments:4002"]
    stripPrefix: true
    rateLimit: { windowSecs: 60, limit: 10, keyBy: "header:X-User-ID" }

  /v1/search:
    targets: ["http://search:4003"]
    stripPrefix: true
    cache: { store: memory, ttlSecs: 30 }

healthCheck: true
maskErrors: true
logging:
  format: json
  skipPaths: [/__health__]

Security hardening

Defence-in-depth: TLS hardening, security headers, CORS, IP allowlist, rate limit, API key, error masking, admin token, upstream TLS verification.

# examples/security-hardened.yaml
global:
  admin:
    bind: "127.0.0.1:2019"
    token: "$ADMIN_TOKEN"   # always protect the admin API

sites:
  - port: 443
    host: secure.example.com

    tls:
      cert: /etc/tls/server.crt
      key: /etc/tls/server.key
      httpRedirectPort: 80
      versions: ["TLSv1.2", "TLSv1.3"]   # disable TLS 1.0/1.1

    securityHeaders:
      hstsMaxAgeSecs: 63072000             # 2 years
      hstsIncludeSubDomains: true
      csp: "default-src 'self'"
      xFrameOptions: DENY
      referrerPolicy: "strict-origin-when-cross-origin"
      permissionsPolicy: "geolocation=(), microphone=()"
      allowedHosts: ["secure.example.com"]  # reject forged Host headers

    cors:
      origins: ["https://app.example.com"]
      credentials: true
      allowedHeaders: [Authorization, Content-Type]

    ipFilter:
      allow: ["10.0.0.0/8", "172.16.0.0/12"]

    rateLimit:
      windowSecs: 60
      limit: 200
      burst: 50                # allow short bursts above the sustained rate

    apiKey:
      keys: ["$API_KEY_PRIMARY", "$API_KEY_SECONDARY"]
      skipPaths: [/__health__]

    maskErrors: true           # never send upstream stack traces to clients

    proxy:
      /api:
        targets: ["https://api-internal:8443"]
        stripPrefix: true
        upstreamTls:
          verify: true
          serverName: api-internal.svc.cluster.local

    healthCheck: true
    metrics:
      path: /__metrics__
      token: "$METRICS_TOKEN"

→ JSON: examples/security-hardened.json


Observability

OTLP tracing requires cargo build --features otlp

Prometheus metrics, OTLP tracing (Grafana Tempo / Jaeger), structured JSON logs with upstream timing, and passive outlier detection.

# examples/observability.yaml
global:
  otlp:
    endpoint: "http://tempo:4317"
    serviceName: "my-service"
    sampleRate: 0.1      # 10% sampling in production

  admin:
    bind: "127.0.0.1:2019"

sites:
  - port: 8080
    logging:
      format: json
      file: ./logs/access.log
      skipPaths: [/__health__, /__metrics__]
      # upstream_ms field in JSON log shows how long the upstream took

    metrics:
      path: /__metrics__
      token: "$METRICS_TOKEN"

    healthCheck:
      includeUpstreams: true   # /__health__ returns upstream latency + ejection status

    outlierDetection:
      consecutive5xx: 5
      baseEjectionTimeSecs: 30
      maxEjectionTimeSecs: 300
      maxEjectionPercent: 10

    securityHeaders: true

    proxy:
      /api:
        targets: ["http://api1:4000", "http://api2:4000"]
        strategy: least-conn
        stripPrefix: true
        healthCheck:
          path: /health
          intervalSecs: 10

Prometheus scrape config:

scrape_configs:
  - job_name: conduit
    static_configs: [{ targets: ["conduit-host:8080"] }]
    metrics_path: /__metrics__
    bearer_token: "$METRICS_TOKEN"

Grafana queries:

# Request rate
rate(conduit_requests_total[5m])

# p99 latency
histogram_quantile(0.99, rate(conduit_request_duration_seconds_bucket[5m]))

# Per-upstream error rate
rate(conduit_upstream_errors_total[5m])

# Cache hit ratio
rate(conduit_cache_hits_total[5m])
  / (rate(conduit_cache_hits_total[5m]) + rate(conduit_cache_misses_total[5m]))

→ JSON: examples/observability.json


Multi-site virtual hosting

Uses jwtAuth — requires cargo build --features jwt Uses tls.acme — requires cargo build --features acme

Three virtual hosts from one process — each with its own auth, TLS, and backends.

# examples/multi-site.yaml
global:
  workers: 4
  admin:
    bind: "127.0.0.1:2019"
    token: "$ADMIN_TOKEN"

sites:
  # Public app — JWT auth, auto-TLS
  - port: 443
    host: app.example.com
    tls:
      acme:
        email: admin@example.com
        storage: /var/cache/conduit/certs
        challenge: http-01
    jwtAuth:
      jwksUrl: "https://auth.example.com/.well-known/jwks.json"
      skipPaths: [/__health__]
    proxy:
      /api: "http://app-backend:4000"
    static: ./dist
    fallback: { file: ./dist/index.html, status: 200 }

  # Admin panel — Basic Auth, internal network only
  - port: 443
    host: admin.example.com
    tls:
      cert: /etc/tls/admin.crt
      key: /etc/tls/admin.key
    ipFilter:
      allow: ["10.0.0.0/8"]
    basicAuth:
      users: { admin: "$ADMIN_PASSWORD" }
    proxy:
      /: "http://admin-ui:3000"

  # Internal metrics — plain HTTP, loopback only
  - port: 9090
    host: 127.0.0.1
    metrics:
      path: /metrics
    healthCheck: true

→ JSON: examples/multi-site.json


Ready to deploy?

Config written — now run it in production.

→ Deployment Guide — Docker, systemd, Kubernetes, production checklist, secrets management.