jokoway
Jokoway is a high-performance API Gateway built on Pingora (Rust) with dead-simple YAML configs. Inspired by Traefikβs expressive routing rules and Kongβs DB-less declarative configuration model.
π Key Features
- π High Performance: Built on Cloudflare's Pingora framework, providing extreme speed, reliability, and security in Rust.
- π¦ Expressive Routing Rules: Traefik-style matching (Host, Path, Method, Headers, Queries) with full Regex support and priority control.
- π DB-less Declarative Configuration: Manage your entire infrastructure via a simple, version-controllable YAML file.
- π Request & Response Transformation: Modify headers, paths, query parameters, and methods on the fly for both requests and responses.
- π Let's Encrypt Support: Built-in Let's Encrypt support with automated issuance and renewal (HTTP-01 and TLS-ALPN-01).
- βοΈ Advanced Load Balancing: Support for backend clusters with weighted round-robin, active health checks (HTTP/TCP), and connection pooling.
- ποΈ HTTP Compression: built-in Gzip, Brotli, and Zstandard compression.
- π WebSocket Transformations: Allow you to modify websocket message via Websocket Middleware.
- π Management API: Allow you to manage upstreams and services via HTTP API.
- π Highly Extensible: Extend core functionality with a clean Rust-based middleware and extension system.
π§ Installation
Jokoway can be installed as a binary via Cargo, run as a container with Docker, or built from the source.
π¦ Using Cargo (Recommended for Rust Users)
Install the latest version of Jokoway directly from crates.io:
π³ Using Docker
Pull the official image from GitHub Container Registry:
π οΈ Building From Source
Prerequisites:
- Rust & Cargo (Stable)
cmakeandperl(Required by Pingora dependencies)
# Clone the repository
# Build in release mode
# The binary will be available at:
# ./target/release/jokoway
π¨ Usage
π Quick Start
- Create a minimal configuration (
config.yml):
jokoway:
http_listen: "0.0.0.0:8080"
upstreams:
- name: my_backend
servers:
- host: "127.0.0.1:3000"
services:
- name: my_service
host: my_backend
protocols:
routes:
- rule: PathPrefix(`/`)
- Run Jokoway:
# Enable logging to see what's happening
- Verify:
π Running with Different Methods
Using the installed binary
Using Docker
Mount your local configuration directory to the container:
Test Configuration
Validate your configuration without starting the server:
π Configuration Guide
Jokoway uses a declarative YAML configuration file, making it easy to version-control your infrastructure. The configuration is divided into two main domains:
jokoway: The operational gateway settings (routing, upstreams, TLS).pingora: The underlying server engine settings.
[!TIP] You can find a full example configuration here showcasing all available options.
π Core Gateway Settings (jokoway)
This section defines the basic behavior and network interfaces for Jokoway.
jokoway:
http_listen: "0.0.0.0:8080" # (Required) Address/port for HTTP traffic
https_listen: "0.0.0.0:8443" # (Optional) Address/port for HTTPS traffic (Requires SSL config)
http_server_options:
keepalive_request_limit: 100
h2c: false
allow_connect_method_proxying: false
π οΈ HTTP Server Options
Fine-tune the behavior of Jokoway's internal HTTP server.
| Field | Default | Description |
|---|---|---|
keepalive_request_limit |
null (Unlimited) |
Maximum number of requests allowed per keep-alive connection. |
h2c |
false |
Enable HTTP/2 over cleartext (without TLS). Useful for gRPC or internal proxying. |
allow_connect_method_proxying |
true |
Allow proxying CONNECT requests when handling HTTP traffic. |
π DNS Resolution
Configure how Jokoway resolves upstream hostnames. If this entire section is omitted, Jokoway falls back to the system resolver (/etc/resolv.conf).
jokoway:
dns:
system_conf: true
use_hosts_file: true
nameservers:
- "1.1.1.1"
- "8.8.8.8"
timeout: 5
attempts: 3
strategy: "ipv4_then_ipv6"
cache_size: 1024
| Field | Default | Description |
|---|---|---|
system_conf |
true |
Load nameservers from the system configuration (/etc/resolv.conf). When true, any entries in nameservers are added to the system ones. When false, only user-provided nameservers are used. |
use_hosts_file |
true |
Read entries from the system hosts file (/etc/hosts), allowing local hostname overrides. |
nameservers |
β | List of custom DNS server IP addresses (e.g., "1.1.1.1", "8.8.8.8"). |
timeout |
5 |
Maximum seconds to wait for a DNS response. |
attempts |
2 |
Number of retry attempts before giving up. |
strategy |
"ipv4_then_ipv6" |
IP resolution strategy. Accepted values: "ipv4_then_ipv6", "ipv6_then_ipv4", "ipv4_only", "ipv6_only". |
cache_size |
1024 |
Number of DNS entries to keep in the in-memory cache. |
[!TIP] In Docker containers, the system resolver (
/etc/resolv.conf) automatically points to Docker's embedded DNS server (127.0.0.11), which can resolve service names (e.g.,"httpbin") to container IPs. So ifsystem_confistrue(the default), Docker service discovery works out of the box β no extranameserversconfiguration needed.
ποΈ Response Compression
Automatically compress HTTP responses to reduce bandwidth usage and improve page load times. Jokoway negotiates the best algorithm with the client via the Accept-Encoding header.
jokoway:
compression:
min_size: 1024
gzip:
level: 6
brotli:
quality: 5
lgwin: 22
buffer_size: 4096
zstd:
level: 3
content_types:
- "text/html"
- "text/css"
- "application/json"
- "application/javascript"
- "image/svg+xml"
General Fields
| Field | Default | Description |
|---|---|---|
min_size |
1024 |
Minimum response body size (in bytes) before compression kicks in. Responses smaller than this are sent uncompressed. |
content_types |
(see below) | List of MIME types eligible for compression. If omitted, Jokoway uses a built-in list of industry-standard compressible types. |
Algorithm Configuration
Each algorithm is optional. Omitting an algorithm section disables it entirely. When multiple algorithms are enabled, Jokoway automatically selects the best one based on the client's Accept-Encoding header, using the priority order: Brotli β Zstandard β Gzip.
Gzip β widest client compatibility, always available.
| Field | Default | Range | Description |
|---|---|---|---|
level |
6 |
1β9 | Compression level. Higher = better ratio but slower. |
Brotli β best compression ratio.
| Field | Default | Range | Description |
|---|---|---|---|
quality |
5 |
1β11 | Compression quality. Higher = better ratio but slower. |
lgwin |
22 |
10β24 | Sliding window size (as a power of 2). Larger = better ratio, more memory. |
buffer_size |
4096 |
β | Internal buffer size in bytes. |
Zstandard (zstd) β fastest compression speed.
| Field | Default | Range | Description |
|---|---|---|---|
level |
3 |
1β22 | Compression level. Higher = better ratio but slower. |
[!IMPORTANT] Brotli and Zstandard require their respective Cargo features to be enabled at compile time (
brotliandzstd). Gzip is always available.
Default Content Types
If you don't specify a content_types list, Jokoway uses an industry-standard set (based on Cloudflare, NGINX, and Apache defaults) that includes: text/html, text/css, text/javascript, application/javascript, application/json, application/xml, text/xml, text/plain, text/markdown, image/svg+xml, and a text/* wildcard.
Already-compressed formats (e.g., image/jpeg, image/png, video/mp4, application/zip, application/gzip) are always skipped regardless of configuration.
π Security & TLS (HTTPS)
Jokoway supports two approaches to TLS: manual certificates you provide yourself, and automatic provisioning via the ACME protocol (Let's Encrypt).
[!WARNING] Both
tlsandacmerequirehttps_listento be configured in thejokowaysection.
Manual Certificates (tls)
Provide your own certificate and private key directly. Ideal for internal services, self-signed setups, or when you manage certificates externally.
jokoway:
tls:
server_cert: "/path/to/cert.pem"
server_key: "/path/to/key.pem"
cacert: "/path/to/ca.pem"
sans:
cipher_suites:
- "TLS_AES_128_GCM_SHA256"
- "TLS_AES_256_GCM_SHA384"
- "TLS_CHACHA20_POLY1305_SHA256"
| Field | Required | Description |
|---|---|---|
server_cert |
β | Path to the server certificate file (PEM format). |
server_key |
β | Path to the server private key file (PEM format). |
cacert |
β | Path to a CA certificate file. Setting this enables mutual TLS (mTLS) β clients must present a valid certificate signed by this CA to connect. |
sans |
β | List of Subject Alternative Names (e.g., ["example.com", "localhost"]). Only used when server_cert / server_key are not provided β in that case, Jokoway generates a self-signed certificate with these SANs as a fallback. |
cipher_suites |
β | List of allowed cipher suites (e.g., "TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384", "TLS_CHACHA20_POLY1305_SHA256"). |
[!TIP] If you omit
server_certandserver_keybut providesans, Jokoway will automatically generate a self-signed certificate. This is useful for local development and testing.
ACME (Automatic Certificate Management Environment)
Automatically issue and renew valid SSL certificates from a CA like Let's Encrypt β zero manual intervention required after initial configuration.
jokoway:
acme:
ca_server: "https://acme-v02.api.letsencrypt.org/directory"
email: "admin@example.com"
storage: "/etc/jokoway/acme.json"
challenge: "http-01"
renewal_interval: 86400
| Field | Required | Default | Description |
|---|---|---|---|
ca_server |
β | β | ACME directory URL. Use https://acme-v02.api.letsencrypt.org/directory for production, or https://acme-staging-v02.api.letsencrypt.org/directory for testing. |
email |
β | β | Email address for ACME account registration and certificate expiry notifications. |
storage |
β | β | Path to a JSON file where certificates and account data are persisted across restarts. |
challenge |
β | "http-01" |
Challenge type for domain validation: "http-01" (requires port 80) or "tls-alpn-01" (uses port 443). |
renewal_interval |
β | 86400 |
How often (in seconds) Jokoway checks whether certificates need renewal. Default is 86400 (1 day). |
[!TIP] Always test with the staging CA server first to avoid hitting Let's Encrypt rate limits.
How does ACME work with Jokoway?
Jokoway automatically requests certificates for domains that appear in your routing rules. To obtain a certificate, simply use the Host(`example.com`) rule in any service route (see Routing Rules Reference). Jokoway will handle issuance, validation, and renewal automatically.
ποΈ Management API
Provides an internal HTTP API for dynamically managing upstreams and services at runtime, without restarting Jokoway. It also serves interactive OpenAPI (Swagger) documentation.
[!IMPORTANT] The Management API requires the
apiCargo feature to be enabled at compile time.
jokoway:
api:
listen: "127.0.0.1:9090"
basic_auth:
username: "admin"
password: "secure_password"
rate_limit:
requests_per_second: 10
burst: 5
openapi:
title: "Jokoway Management API"
description: "Gateway router API"
root_path: "/docs"
[!CAUTION] The Management API gives full control over routing and upstreams. Always bind to a private address (e.g.,
127.0.0.1) and protect it withbasic_authand/orapi_keys. Never expose it to the public internet.
API Fields
| Field | Required | Description |
|---|---|---|
listen |
β | Address and port to listen on (e.g., "127.0.0.1:9090"). Omit the entire api section to disable the API server. |
basic_auth |
β | Enable HTTP Basic Authentication. Accepts a single credential object or a list. See Basic Auth below. |
api_keys |
β | Enable API key authentication. Accepts a single string or a list. See API Keys below. |
rate_limit |
β | Protect the API from excessive requests. See Rate Limit below. |
openapi |
β | Customize the built-in OpenAPI/Swagger documentation. See OpenAPI below. |
Basic Auth
basic_auth can be configured as a single credential object or a list of credentials. If multiple credentials are provided, any matching username/password pair is accepted.
Single credential:
jokoway:
api:
basic_auth:
username: "admin"
password: "secure_password"
Multiple credentials:
jokoway:
api:
basic_auth:
- username: "admin"
password: "secure_password"
- username: "ops"
password: "another_password"
| Field | Required | Description |
|---|---|---|
username |
β | Username for HTTP Basic Authentication. |
password |
β | Password for HTTP Basic Authentication. |
API Keys
api_keys can be configured as a single string or a list of strings. If multiple keys are provided, any matching key is accepted. When both basic_auth and api_keys are configured, either method authorizes the request.
jokoway:
api:
api_keys:
- "key-1"
- "key-2"
Send the key using either header:
Rate Limit
| Field | Required | Description |
|---|---|---|
requests_per_second |
β | Maximum sustained requests per second allowed. |
burst |
β | Maximum number of requests allowed in a burst before rate limiting kicks in. |
OpenAPI
Interactive API documentation is served at the configured root_path.
| Field | Default | Description |
|---|---|---|
title |
"Jokoway API" |
Title displayed in the OpenAPI documentation page. |
description |
"Jokoway Management API" |
Description shown in the API docs. |
root_path |
"/docs" |
URL path where the Swagger UI is served (e.g., visit http://127.0.0.1:9090/docs). |
ποΈ Upstreams & Services
The heart of Jokoway's routing engine. Upstreams define your backend servers, and Services define how incoming requests find their way to those servers.
1. Upstreams (Backend Clusters)
An upstream is a named pool of backend servers. Jokoway load-balances traffic across servers within an upstream using weighted round-robin.
jokoway:
upstreams:
- name: api_cluster
servers:
- host: "10.0.0.1:3000"
weight: 2
tls: false
- host: "10.0.0.2:3000"
weight: 1
tls: true
peer_options: # Override peer options for this server only
read_timeout: 5
health_check:
type: "http"
interval: 10
timeout: 3
unhealthy_threshold: 3
healthy_threshold: 2
path: "/health"
method: "GET"
expected_status:
headers:
User-Check: "Pingora-Health"
update_frequency: 60
peer_options: # Default peer options for all servers
connection_timeout: 10
read_timeout: 30
idle_timeout: 60
write_timeout: 30
verify_cert: true
verify_hostname: true
alternative_cn: "alt.example.com"
alpn: "h1h2"
tcp_keepalive:
idle: 60
interval: 5
count: 5
tcp_recv_buf: 4096
dscp: 46
tcp_fast_open: true
h2_ping_interval: 30
max_h2_streams: 100
second_keyshare: true
curves: "X25519"
cacert: "/path/to/ca.pem"
client_cert: "/path/to/client.pem"
client_key: "/path/to/client-key.pem"
sni: "backend.example.com"
extra_proxy_headers:
X-Forwarded-By: "jokoway"
Upstream Fields
| Field | Required | Description |
|---|---|---|
name |
β | Unique identifier for this upstream cluster. Services reference this name via the host field. |
servers |
β | List of backend servers in this cluster. See Server Fields below. |
health_check |
β | Active health monitoring configuration. See Health Check Fields below. |
update_frequency |
β | How often (in seconds) to refresh the upstream configuration dynamically. |
peer_options |
β | Default connection settings applied to all servers in this upstream. Individual servers can override these. See Peer Options Fields below. |
Server Fields
Each entry in the servers list represents a single backend instance.
| Field | Required | Default | Description |
|---|---|---|---|
host |
β | β | Address of the backend server in host:port format (e.g., "10.0.0.1:3000"). |
weight |
β | 1 |
Relative weight for load balancing. A server with weight: 2 receives twice the traffic of a server with weight: 1. |
tls |
β | false |
If true, Jokoway connects to this backend server over TLS (HTTPS). |
peer_options |
β | β | Override the upstream-level peer_options for this specific server only. |
Health Check Fields
Active health checks periodically probe each backend server to determine if it is healthy and should receive traffic. Jokoway supports three check types: http, https, and tcp.
| Field | Required | Default | Description |
|---|---|---|---|
type |
β | β | Health check protocol: "http", "https", or "tcp". |
interval |
β | 10 |
Seconds between each health check probe. |
timeout |
β | 3 |
Maximum seconds to wait for a health check response before considering it failed. |
unhealthy_threshold |
β | 3 |
Number of consecutive failures before marking a server as unhealthy. |
healthy_threshold |
β | 2 |
Number of consecutive successes before marking a server as healthy again. |
path |
β | β | (HTTP/HTTPS only) The URL path to probe (e.g., "/health"). |
method |
β | β | (HTTP/HTTPS only) HTTP method for the probe (e.g., "GET", "HEAD"). |
expected_status |
β | β | (HTTP/HTTPS only) List of HTTP status codes that indicate a healthy response (e.g., [200, 204]). |
headers |
β | β | (HTTP/HTTPS only) Custom headers to include in the health check request. |
Peer Options Fields
Peer options control how Jokoway connects to upstream backend servers. They can be set at the upstream level (applies to all servers) or at the individual server level (overrides the upstream default).
Timeouts
| Field | Default | Description |
|---|---|---|
connection_timeout |
β | Maximum time (in seconds) to wait for establishing a TCP connection. |
read_timeout |
β | Timeout (in seconds) for reading a response from the upstream. Resets after each read. |
idle_timeout |
β | Timeout (in seconds) before closing an idle keep-alive connection. |
write_timeout |
β | Timeout (in seconds) for writing a request to the upstream. |
TLS / Security
| Field | Default | Description |
|---|---|---|
verify_cert |
false |
If true, verify the upstream server's TLS certificate. |
verify_hostname |
false |
If true, verify that the upstream's TLS certificate hostname matches the SNI. |
alternative_cn |
β | Accept a certificate if its Common Name (CN) matches this value instead of the SNI. |
alpn |
"h1" |
ALPN protocol negotiation: "h1" (HTTP/1.1), "h2" (HTTP/2), or "h1h2" (both). |
curves |
β | TLS curves to advertise for the connection (e.g., "X25519"). |
second_keyshare |
β | If true, use a second key share during TLS handshake (relevant for post-quantum curves). |
cacert |
β | Path to a CA certificate file for verifying the upstream's TLS certificate. |
client_cert |
β | Path to a client certificate file for mutual TLS (mTLS) with the upstream. |
client_key |
β | Path to a client private key file for mTLS. |
sni |
β | Server Name Indication (SNI) hostname to send during the TLS handshake. Auto-detected from the server hostname if not set. |
TCP
| Field | Default | Description |
|---|---|---|
tcp_keepalive |
β | TCP keepalive settings. See TCP Keepalive below. |
tcp_recv_buf |
β | TCP receive buffer size in bytes. |
dscp |
β | DSCP value for Quality of Service (QoS) marking (0β63). |
tcp_fast_open |
β | If true, enable TCP Fast Open for reduced latency on new connections. |
TCP Keepalive
| Field | Default | Description |
|---|---|---|
idle |
60 |
Seconds of idle time before sending the first keepalive probe. |
interval |
5 |
Seconds between consecutive keepalive probes. |
count |
5 |
Number of failed probes before dropping the connection. |
HTTP
| Field | Default | Description |
|---|---|---|
h2_ping_interval |
β | Interval (in seconds) for HTTP/2 PING frames to keep connections alive. |
max_h2_streams |
1 |
Maximum number of concurrent HTTP/2 streams allowed per connection. |
allow_h1_response_invalid_content_length |
false |
If true, accept HTTP/1 responses with invalid Content-Length headers (treats them as close-delimited). Useful for legacy servers. |
extra_proxy_headers |
β | Map of extra headers to send to the upstream (e.g., X-Forwarded-By: "jokoway"). |
[!TIP] You can use YAML anchors to define
peer_optionsonce and reuse them across multiple upstreams. See the full example configuration for details.
2. Services (Frontend Routes)
A service binds an upstream cluster to a set of routing rules. When an incoming request matches a route's rule, it is forwarded to the service's upstream.
jokoway:
services:
- name: public_api
host: api_cluster # Must match an upstream name
protocols:
routes:
- name: api-route
rule: >-
Host(`api.example.com`) && PathPrefix(`/v1`)
priority: 100
request_transformer: "StripPrefix(`/v1`)"
response_transformer: "ReplaceHeader(`Server`, `Jokoway`)"
Service Fields
| Field | Required | Description |
|---|---|---|
name |
β | Unique identifier for this service. |
host |
β | The name of the upstream cluster to route traffic to. Must match an upstream's name field. |
protocols |
β | List of protocols this service accepts: "http", "https", "ws" (WebSocket), "wss" (WebSocket over TLS), "grpc", "grpcs" (gRPC over TLS). |
routes |
β | List of routing rules. See Route Fields below. |
Route Fields
| Field | Required | Default | Description |
|---|---|---|---|
name |
β | β | Unique identifier for this route. |
rule |
β | β | A match expression using the routing rule syntax (see Routing Rules Reference). |
priority |
β | 0 |
Higher values are evaluated first. Use this to ensure specific routes take precedence over broader catch-all rules. |
request_transformer |
β | β | Transform the request before forwarding to the upstream. See Request Transformers. |
response_transformer |
β | β | Transform the response before sending to the client. See Response Transformers. |
βοΈ Pingora Engine Settings
These settings configure the underlying Pingora server runtime. This is the pingora top-level key (separate from jokoway).
pingora:
# Core
threads: 4
work_stealing: true
version: 1
# Daemon / Process
daemon: false
pid_file: "/var/run/jokoway.pid"
error_log: "/var/log/jokoway.log"
upgrade_sock: "/tmp/jokoway_upgrade.sock"
user: null
group: null
# Shutdown
grace_period_seconds: 60
graceful_shutdown_timeout_seconds: 30
# Networking
ca_file: null
listener_tasks_per_fd: 1
upstream_keepalive_pool_size: 128
client_bind_to_ipv4:
client_bind_to_ipv6:
upstream_connect_offload_threadpools: null
upstream_connect_offload_thread_per_pool: null
[!NOTE] For most deployments, you only need to set
threadsandwork_stealing. The defaults are production-ready. See the Pingora ServerConf documentation for advanced tuning.
π¦ Routing Rules Reference
Jokoway uses expressive matching rules. You can combine them effortlessly using logical operators to build highly specific routing paradigms:
&&(AND)||(OR)!(NOT)
Example: Host(`api.test.com`) && (PathPrefix(`/users`) || PathPrefix(`/orders`))
| Rule Syntax | Description | Example |
|---|---|---|
Host(string) |
Exact domain match. This is used by the acme extension to request SSL certificates. |
Host(`example.com`) |
HostRegexp(regex) |
Regex domain match. | HostRegexp(`^.*\.example\.com$`) |
Path(string) |
Exact path match. | Path(`/api/v1/health`) |
PathPrefix(string) |
Path starts-with match. | PathPrefix(`/api`) |
PathRegexp(regex) |
Regex path match. | PathRegexp(`^/user/[0-9]+$`) |
Method(string) |
HTTP Method match. | Method(`POST`) |
HeaderRegexp(k, v) |
Header name and Regex value match. | HeaderRegexp(`User-Agent`, `^Mozilla.*`) |
QueryRegexp(k, v) |
Query parameter name and Regex value match. | QueryRegexp(`id`, `^[0-9]+$`) |
π Request / Response Transformers
Transformers mutate HTTP traffic on the fly. You can configure multiple transformers on a single route by separating them with a semicolon (;).
Example: StripPrefix(`/api`); AddPrefix(`/v2`); ReplaceHeader(`Host`, `backend.local`)
Request Transformers (Before routing to Upstream)
Used to adapt the client request to map exactly to what the internal backend expects.
| Function | Description | Example |
|---|---|---|
ReplaceHeader(k, v) |
Replaces / sets a header value. | ReplaceHeader(`Host`, `backend.local`) |
AppendHeader(k, v) |
Appends to an existing header. | AppendHeader(`X-Forwarded-For`, `client-ip`) |
DeleteHeader(k) |
Removes a header entirely. | DeleteHeader(`Authorization`) |
ReplaceQuery(k, v) |
Sets a query parameter. | ReplaceQuery(`foo`, `bar`) |
AppendQuery(k, v) |
Appends a new query parameter. | AppendQuery(`debug`, `true`) |
DeleteQuery(k) |
Removes a query parameter. | DeleteQuery(`token`) |
StripPrefix(path) |
Strips prefix from the requested path. | StripPrefix(`/api`) |
AddPrefix(path) |
Prepends a prefix to the requested path. | AddPrefix(`/v1`) |
RewritePath(reg, rp) |
Rewrites the path using Regex capture groups. | RewritePath(`^/old/(.*)`, `/new/$1`) |
SetMethod(method) |
Changes the HTTP Method. | SetMethod(`PUT`) |
Response Transformers (Before sending to Client)
Used to hide server-side details, enforce security headers, or append cache statuses before the client sees the response.
| Function | Description | Example |
|---|---|---|
ReplaceHeader(k, v) |
Sets a header on the response. | ReplaceHeader(`Server`, `Jokoway`) |
AppendHeader(k, v) |
Appends to a response header. | AppendHeader(`X-Cache-Status`, `MISS`) |
DeleteHeader(k) |
Strips an internal header. | DeleteHeader(`X-Powered-By`) |