Plugins

Load Balancer

Upstream routing with round-robin, weighted, least-connections, fast-response, and sticky session algorithms.

Load Balancer Plugin

The load balancer plugin routes requests to upstream backend servers using configurable algorithms. It supports multiple load balancer pools, weighted targets, health checks, and sticky sessions.

Configuration

{
  "plugins": {
    "load_balancer": {
      "enabled": true,
      "load_balancers": [
        {
          "id": "default",
          "name": "My Backend",
          "enabled": true,
          "conditions": [],
          "algorithm": {
            "type": "round_robin"
          },
          "targets": [
            {
              "id": "backend-1",
              "host": "10.0.0.1",
              "port": 8080,
              "protocol": "http",
              "weight": 100,
              "enabled": true,
              "tls": {
                "verify": false,
                "sni": null
              }
            },
            {
              "id": "backend-2",
              "host": "10.0.0.2",
              "port": 8080,
              "protocol": "http",
              "weight": 100,
              "enabled": true
            }
          ],
          "host_header": {
            "mode": "visitor"
          },
          "health_check": {
            "mode": "off"
          }
        }
      ]
    }
  }
}

Load balancer structure

FieldTypeRequiredDescription
idstring✅Unique load balancer identifier
namestring✅Human-readable name
enabledbool—Enable/disable (default: true)
conditionsarray—When to use this LB (empty = always)
algorithmobject✅Load balancing algorithm
targetsarray✅Upstream backend servers (at least 1)
host_headerobject—Host header forwarding mode
health_checkobject—Health check configuration

Algorithms

Round Robin

Distributes requests evenly across all targets in order.

{
  "algorithm": {
    "type": "round_robin"
  }
}

Weighted Round Robin

Distributes requests based on target weights. A target with weight 200 receives twice as many requests as one with weight 100.

{
  "algorithm": {
    "type": "weighted_round_robin"
  }
}

Least Connections

Routes to the target with the fewest active connections.

{
  "algorithm": {
    "type": "least_connections"
  }
}

Fast Response

Routes to the target with the lowest EWMA (Exponentially Weighted Moving Average) latency.

{
  "algorithm": {
    "type": "fast_response"
  }
}

Sticky Session

Routes the same client to the same target based on a hash of the key.

{
  "algorithm": {
    "type": "sticky_session",
    "key": "req.client_ip"
  }
}

The key field accepts any condition path:

KeyDescription
req.client_ipSticky by client IP (default)
req.header.X-API-KeySticky by API key
req.cookie.Session-IDSticky by session cookie
req.header.X-Tenant-IDSticky by tenant

Targets

FieldTypeDefaultDescription
idstring✅Unique target identifier
hoststring✅Backend hostname or IP
portnumber✅Backend port
protocolstring"http""http" or "https"
weightnumber100Weight for weighted algorithms
enabledbooltrueEnable/disable target
tls.verifyboolfalseVerify upstream TLS certificate
tls.snistring—Custom SNI for TLS handshake

TLS configuration

{
  "id": "secure-backend",
  "host": "backend.internal",
  "port": 443,
  "protocol": "https",
  "weight": 100,
  "enabled": true,
  "tls": {
    "verify": true,
    "sni": "api.example.com"
  }
}

When tls.sni is not set:

  • If target host is an IP → uses the visitor's Host header
  • If target host is a hostname → uses the target hostname

Host header

Controls what Host header is sent to the upstream.

ModeDescription
visitorForward the visitor's original Host header
customUse a custom value
targetUse the target's hostname
{
  "host_header": {
    "mode": "custom",
    "value": "api.internal.example.com"
  }
}

Health checks

Off mode

No health checks. All enabled targets are considered healthy.

{
  "health_check": {
    "mode": "off"
  }
}

HTTPS mode

Periodic HTTP(S) health checks — an HTTP GET to path using the target's protocol.

{
  "health_check": {
    "mode": "https",
    "path": "/health",
    "interval_seconds": 5,
    "timeout_ms": 1000,
    "expected_statuses": [200],
    "healthy_threshold": 2,
    "unhealthy_threshold": 3
  }
}
The valid health_check.mode values are https, accepts_connections, and off. There is no"http" mode — https performs an HTTP(S) GET using the target's own protocol (http or https targets both work).
FieldTypeDefaultDescription
pathstring"/"Health check endpoint
interval_secondsint5Check interval
timeout_msint1000Request timeout
expected_statusesarray[200]Healthy status codes
healthy_thresholdint2Consecutive successes to mark healthy
unhealthy_thresholdint3Consecutive failures to mark unhealthy

Accepts connections mode

Simple TCP connection check.

{
  "health_check": {
    "mode": "accepts_connections",
    "interval_seconds": 5,
    "timeout_ms": 1000
  }
}

How it works

Request arrives
    │
    ▼
1. Evaluate conditions for each load balancer
    │
    ├── No match → continue (no upstream selected)
    │
    └── Match:
        │
        ├── 2. Filter enabled targets
        │
        ├── No enabled targets → 503 "no_healthy_upstream"
        │
        └── 3. Select target using algorithm
            │
            ├── round_robin → atomic counter % len
            ├── weighted_round_robin → weight-based selection
            ├── least_connections → min active connections
            ├── fast_response → min EWMA latency
            └── sticky_session → hash(key + target_id)

            │
            ├── 4. Record selection in context
            ├── 5. Increment connection count
            │
            └── 6. Create HttpPeer → forward to upstream

Examples

Simple round-robin

{
  "id": "default",
  "name": "Backend Pool",
  "enabled": true,
  "conditions": [],
  "algorithm": { "type": "round_robin" },
  "targets": [
    { "id": "b1", "host": "10.0.0.1", "port": 8080, "protocol": "http", "weight": 100, "enabled": true },
    { "id": "b2", "host": "10.0.0.2", "port": 8080, "protocol": "http", "weight": 100, "enabled": true }
  ],
  "host_header": { "mode": "visitor" },
  "health_check": { "mode": "off" }
}

Weighted with health checks

{
  "id": "default",
  "name": "Production Pool",
  "enabled": true,
  "conditions": [],
  "algorithm": { "type": "weighted_round_robin" },
  "targets": [
    { "id": "large", "host": "10.0.0.1", "port": 8080, "protocol": "http", "weight": 200, "enabled": true },
    { "id": "small", "host": "10.0.0.2", "port": 8080, "protocol": "http", "weight": 100, "enabled": true }
  ],
  "host_header": { "mode": "visitor" },
  "health_check": {
    "mode": "https",
    "path": "/health",
    "interval_seconds": 10,
    "timeout_ms": 2000,
    "expected_statuses": [200, 204],
    "healthy_threshold": 2,
    "unhealthy_threshold": 3
  }
}

Multiple pools by path

{
  "load_balancers": [
    {
      "id": "api-pool",
      "name": "API Servers",
      "enabled": true,
      "conditions": [
        { "left": "req.path", "operator": "starts_with", "value": "/api/" }
      ],
      "algorithm": { "type": "least_connections" },
      "targets": [
        { "id": "api-1", "host": "10.0.1.1", "port": 8080, "protocol": "http", "weight": 100, "enabled": true },
        { "id": "api-2", "host": "10.0.1.2", "port": 8080, "protocol": "http", "weight": 100, "enabled": true }
      ],
      "host_header": { "mode": "visitor" },
      "health_check": { "mode": "off" }
    },
    {
      "id": "static-pool",
      "name": "Static Servers",
      "enabled": true,
      "conditions": [],
      "algorithm": { "type": "round_robin" },
      "targets": [
        { "id": "static-1", "host": "10.0.2.1", "port": 80, "protocol": "http", "weight": 100, "enabled": true }
      ],
      "host_header": { "mode": "visitor" },
      "health_check": { "mode": "off" }
    }
  ]
}
{
  "id": "session-pool",
  "name": "Session Servers",
  "enabled": true,
  "conditions": [],
  "algorithm": {
    "type": "sticky_session",
    "key": "req.cookie.Session-ID"
  },
  "targets": [
    { "id": "app-1", "host": "10.0.0.1", "port": 8080, "protocol": "http", "weight": 100, "enabled": true },
    { "id": "app-2", "host": "10.0.0.2", "port": 8080, "protocol": "http", "weight": 100, "enabled": true }
  ],
  "host_header": { "mode": "visitor" },
  "health_check": { "mode": "off" }
}

HTTPS upstream with TLS verification

{
  "id": "secure-pool",
  "name": "Secure Backend",
  "enabled": true,
  "conditions": [],
  "algorithm": { "type": "round_robin" },
  "targets": [
    {
      "id": "secure-1",
      "host": "backend.internal",
      "port": 443,
      "protocol": "https",
      "weight": 100,
      "enabled": true,
      "tls": {
        "verify": true,
        "sni": "api.example.com"
      }
    }
  ],
  "host_header": { "mode": "target" },
  "health_check": { "mode": "off" }
}

Upstream headers

The gateway adds these headers to upstream requests:

HeaderValue
X-Forwarded-ProtoOriginal request scheme
X-Forwarded-HostOriginal request host
X-Forwarded-ForClient IP (appended to existing)

Log output

{
  "load_balancer": {
    "lb_id": "default",
    "algorithm": "RoundRobin",
    "target_id": "backend-1",
    "target": "10.0.0.1"
  }
}

Performance

MetricValue
Target selectionO(n) where n = targets
Connection trackingAtomic counters
Latency trackingEWMA (lock-free)
Sticky session hashSHA-256

Notes

  • Multiple load balancers are evaluated in order — first match wins
  • If no load balancer matches, the request has no upstream target (will fail)
  • Connection counts are per-gateway-instance (not distributed)
  • Health checks run in a background task (when enabled)
  • If every target is unhealthy the gateway returns 503 with {"error": "no_healthy_upstream"}
Copyright © 2026