Configuration

Complete reference for every field in config.json.

Configuration Reference

Complete reference for config.json. You can also pre-validate generated configs against config.schema.json (JSON Schema) before starting the gateway.

Top-level fields

FieldTypeRequiredDescription
gateway_idstring✅Unique identifier for this gateway instance
gateway_namestring✅Human-readable name
server_namestring✅Server name for logging
config_versionstring✅Version string for tracking config changes
listenobject✅Listen addresses
client_ipobject—Client IP resolution strategy
pluginsobject✅Plugin configurations

listen

{
  "listen": {
    "http": "0.0.0.0:8080",
    "https": "0.0.0.0:8443"
  }
}
FieldTypeDescription
httpstringHTTP listen address (host:port)
httpsstringHTTPS listen address (requires TLS config)

At least one of http or https must be specified.

client_ip

Controls how the real client IP is determined from incoming requests.

{
  "client_ip": {
    "source": "header",
    "header": "X-Forwarded-For",
    "index": 0,
    "fallback": "peer_ip"
  }
}
FieldTypeDefaultDescription
sourceenum"peer_ip"How to resolve client IP
headerstring—Header name (when source is header or header_index)
indexinteger—Comma-separated index (0-based, when source is header_index)
fallbackenum—Fallback when header is missing

source values

ValueDescription
"peer_ip"Use the direct TCP connection IP
"header"Read IP from a single-valued header
"header_index"Read IP from a specific index in a comma-separated header

Example: Cloudflare

{
  "client_ip": {
    "source": "header",
    "header": "CF-Connecting-IP"
  }
}

Example: Nginx reverse proxy

{
  "client_ip": {
    "source": "header_index",
    "header": "X-Forwarded-For",
    "index": 0,
    "fallback": "peer_ip"
  }
}

plugins

{
  "plugins": {
    "ip_intel": { },
    "firewall": { },
    "rate_limiting": { },
    "cache": { },
    "load_balancer": { },
    "logging": { }
  }
}

plugins.ip_intel

IP Intelligence. All data is embedded in the binary — only enabled is needed.

{
  "ip_intel": {
    "enabled": true
  }
}

See IP Intelligence plugin for details.

plugins.firewall

Request firewall with allow/block/redirect rules.

{
  "firewall": {
    "enabled": true,
    "rules": [
      {
        "id": "block-bad-ips",
        "name": "Block bad IPs",
        "enabled": true,
        "conditions": [
          {
            "left": "ip.is_tor",
            "operator": "equals",
            "value": true
          }
        ],
        "action": {
          "type": "block",
          "status": 403
        }
      }
    ]
  }
}

See Firewall plugin for details.

plugins.rate_limiting

Sliding-window rate limiting.

{
  "rate_limiting": {
    "enabled": true,
    "rules": [
      {
        "id": "api-limit",
        "name": "API Rate Limit",
        "enabled": true,
        "conditions": [
          {
            "left": "req.path",
            "operator": "starts_with",
            "value": "/api/"
          }
        ],
        "key": "req.client_ip",
        "limit": 100,
        "window_seconds": 60,
        "action": {
          "type": "block",
          "status": 429
        }
      }
    ]
  }
}

Rule fields

FieldTypeRequiredDescription
idstring✅Unique rule identifier
namestring✅Human-readable name
enabledbooltrueEnable/disable rule
conditionsarray[]When to apply (empty = every request, e.g. a global limit)
keystring | array✅What to count per client (see below)
limitint > 0✅Max requests per window
window_secondsint > 0✅Sliding window length
actionobject—{ "type": "block", "status": 429 } (default)

key forms

FormExampleMeaning
Single path"key": "req.client_ip"Count per resolved client IP
Composite array"key": ["req.client_ip", "req.header.X-API-Key"]Count per combination (parts joined with :)
JWT claim"key": "req.header.authorization.jwt.user_id"Decode Bearer token, count per claim
Legacy template"key": "{req.client_ip}+{http.header.x-api-key}"Placeholders resolved and concatenated

Any condition path can be used as a key part.

See Rate Limiting plugin for details.

plugins.cache

Edge caching with singleflight revalidation. Supports stale-while-revalidate and stale-if-error from origin's Cache-Control headers.

{
  "cache": {
    "enabled": true,
    "max_object_size_bytes": 10485760,
    "rules": [
      {
        "id": "cache-static",
        "name": "Cache static assets",
        "enabled": true,
        "conditions": [
          {
            "left": "req.path",
            "operator": "ends_with",
            "value": ".css"
          }
        ],
        "cache": "eligible",
        "edge_ttl": {
          "mode": "custom",
          "seconds": 3600
        },
        "browser_ttl": {
          "mode": "custom",
          "seconds": 300
        },
        "cache_key": {
          "mode": "default"
        },
        "statuses": [200, 301, 404]
      }
    ]
  }
}
FieldTypeDefaultDescription
enabledbooltrueEnable/disable cache plugin
max_object_size_bytesint10485760Maximum response body size to cache (10 MB)
rulesarray[]Cache rules (first match wins)

Rule fields

FieldTypeRequiredDescription
idstring✅Unique rule identifier
namestring✅Human-readable name
enabledbooltrueEnable/disable rule
conditionsarray[]When to apply (empty = always)
cacheenum"eligible""eligible" or "bypass"
edge_ttlobject✅Edge cache TTL configuration
browser_ttlobject—Browser Cache-Control header
cache_keyobject—Cache key configuration
statusesarray[200, 301, 404]HTTP status codes to cache
stale_while_revalidatenumber—SWR window in seconds. Overrides origin Cache-Control header.
stale_if_errornumber—SIE window in seconds. Overrides origin Cache-Control header.

edge_ttl modes

ModeDescriptionExtra fields
"origin"Read TTL from upstream Cache-Control (s-maxage > max-age)—
"custom"Use fixed TTL (falls back to CDN headers if seconds not set)seconds (optional)

browser_ttl modes

ModeDescriptionExtra fields
"bypass"Don't modify browser Cache-Control—
"origin"Forward upstream Cache-Control—
"custom"Set specific browser TTLseconds (optional)

cache_key modes

ModeDescriptionExtra fields
"default"Key = scheme|host|path—
"custom"Add custom parts to keyparts (array of condition paths)

CDN Cache-Control parsing

When edge_ttl.mode is "origin", the gateway reads these directives from the upstream Cache-Control header:

DirectiveUsed asPriority
s-maxageEdge TTL1st
max-ageEdge TTL (fallback)2nd
stale-while-revalidateSWR windowConfig override > origin header
stale-if-errorSIE windowConfig override > origin header
no-storeSkip cacheOverrides all
no-cacheSkip cacheOverrides all
privateSkip cacheOverrides all
stale-while-revalidate and stale-if-error can be set in the gateway config (recommended) or parsed from the upstream Cache-Control header. Gateway config takes priority over origin headers.

See Cache plugin for details.

plugins.load_balancer

Upstream load balancing.

{
  "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": "http",
          "path": "/health",
          "interval_seconds": 5,
          "timeout_ms": 1000,
          "expected_statuses": [200],
          "healthy_threshold": 2,
          "unhealthy_threshold": 3
        }
      }
    ]
  }
}

See Load Balancer plugin for details.

algorithm.type values

ValueDescription
round_robinRotate through targets
weighted_round_robinRotate proportionally to weight
least_connectionsPick the target with the fewest open connections
fast_responsePrefer targets with the fastest recent responses
sticky_sessionPin a client to a target; algorithm.key names the condition path to pin by (e.g. "req.header.X-Session-Id")

host_header.mode values

ModeDescription
visitorForward the client's original Host header (default)
customUse the fixed value field (required with this mode)
targetUse each target's host

health_check.mode values

ModeDescription
httpsHTTP(S) GET request to path on the target (uses the target's protocol)
accepts_connectionsTCP connect check only
offNo health checking — all targets assumed healthy
There is no"http" health-check mode. Use "https" (which performs an HTTP(S) GET using the target's protocol) or "accepts_connections".

Other fields: path (default "/"), interval_seconds (5), timeout_ms (1000), expected_statuses (array), healthy_threshold (2), unhealthy_threshold (3). If every target is marked unhealthy the gateway returns 503 with {"error": "no_healthy_upstream"}.

plugins.logging

Access logging.

{
  "logging": {
    "enabled": true,
    "mode": "file",
    "file": {
      "path": "/var/log/quayel/access.jsonl",
      "buffer_size": 8192,
      "flush_interval_ms": 1000
    }
  }
}
FieldTypeDefaultDescription
enabledbooltrueEnable/disable logging
modestring"file"Log mode ("file")
file.pathstring—Path to JSONL log file
file.buffer_sizeint8192Write buffer size in bytes
file.flush_interval_msint1000Flush interval in milliseconds

See Logging for log format details.

plugins.auto_ssl

On-demand TLS certificate provisioning using ACME (Let's Encrypt). Certificates are obtained automatically when the first HTTPS request arrives for a configured domain.

{
  "auto_ssl": {
    "enabled": true,
    "email": "admin@example.com",
    "domains": [
      "example.com",
      "www.example.com",
      "api.example.com"
    ],
    "cert_storage_path": "/var/lib/quayel/certs",
    "staging": false,
    "renewal_days_before": 30
  }
}
FieldTypeDefaultDescription
enabledbooltrueEnable/disable Auto-SSL
emailstring—Email for Let's Encrypt account registration (required)
domainsstring[]Domain names to provision certificates for (required)
cert_storage_pathstring"/var/lib/quayel/certs"Directory to store PEM files (write-through cache)
stagingboolfalseUse Let's Encrypt staging environment (for testing)
renewal_days_beforeint30Renew certificates this many days before expiry

How it works

  1. Gateway starts with a self-signed fallback cert — HTTP and HTTPS serve immediately
  2. First TLS handshake for a domain in domains → spawns a background ACME worker
  3. Worker obtains cert from Let's Encrypt via HTTP-01 challenge
  4. Cert loaded into memory + persisted to disk
  5. Next handshake → real cert served

Key behaviors

  • On-demand: certs are only fetched when the first request arrives, not on config change
  • Deduplication: only one ACME worker per domain (prevents duplicate provisioning)
  • Failed cert cooldown: failed certs retry only when a new request arrives AND 24h have passed
  • Daily renewal: background task renews certs 30 days before expiry, retries 1 failed cert per cycle
  • Non-blocking startup: gateway serves traffic immediately, doesn't wait for certs

See Auto-SSL plugin for full documentation.

Complete example

{
  "gateway_id": "gw-prod-01",
  "gateway_name": "production",
  "server_name": "api.example.com",
  "config_version": "2026-01-01-001",
  "listen": {
    "http": "0.0.0.0:8080"
  },
  "client_ip": {
    "source": "header_index",
    "header": "X-Forwarded-For",
    "index": 0,
    "fallback": "peer_ip"
  },
  "plugins": {
    "ip_intel": { "enabled": true },
    "firewall": {
      "enabled": true,
      "rules": [
        {
          "id": "block-tor",
          "name": "Block Tor exits",
          "enabled": true,
          "conditions": [
            { "left": "ip.is_tor", "operator": "equals", "value": true }
          ],
          "action": { "type": "block", "status": 403 }
        },
        {
          "id": "block-high-risk",
          "name": "Block high risk IPs",
          "enabled": true,
          "conditions": [
            { "left": "ip.risk_score", "operator": "greater_than", "value": 30 }
          ],
          "action": { "type": "block", "status": 403 }
        }
      ]
    },
    "rate_limiting": {
      "enabled": true,
      "rules": [
        {
          "id": "api-rate",
          "name": "API rate limit",
          "enabled": true,
          "conditions": [
            { "left": "req.path", "operator": "starts_with", "value": "/api/" }
          ],
          "key": "req.client_ip",
          "limit": 100,
          "window_seconds": 60
        }
      ]
    },
    "cache": { "enabled": false, "rules": [] },
    "load_balancer": {
      "enabled": true,
      "load_balancers": [
        {
          "id": "default",
          "name": "Backend Pool",
          "enabled": true,
          "conditions": [],
          "algorithm": { "type": "round_robin" },
          "targets": [
            {
              "id": "backend-1",
              "host": "10.0.0.1",
              "port": 8080,
              "protocol": "http",
              "weight": 100,
              "enabled": true
            }
          ],
          "host_header": { "mode": "visitor" },
          "health_check": { "mode": "off" }
        }
      ]
    },
    "logging": {
      "enabled": true,
      "mode": "file",
      "file": {
        "path": "/var/log/quayel/access.jsonl",
        "buffer_size": 8192,
        "flush_interval_ms": 1000
      }
    },
    "auto_ssl": {
      "enabled": true,
      "email": "admin@example.com",
      "domains": ["api.example.com"],
      "cert_storage_path": "/var/lib/quayel/certs",
      "staging": false,
      "renewal_days_before": 30
    }
  }
}

Validation

The gateway validates config on startup and rejects:

  • Missing required fields (gateway_id, gateway_name, server_name, config_version)
  • No listen address (http and https both missing)
  • client_ip.source is header or header_index but header is not set
  • client_ip.source is header_index but index is not set
  • Firewall rules with empty conditions
  • Rate limit rules with limit: 0 or window_seconds: 0
  • Load balancers with no targets
  • Auto-SSL: missing email or empty domains
You can pre-validate generated configs against config.schema.json (JSON Schema) before starting the gateway. The gateway's own parser remains authoritative. AI agents should also read the AI Agent Guide.
Copyright © 2026