📑 Contents
Architecture vs Kong / Traefik / Envoy Routes, Services, Upstreams Consumers & Auth Plugin System Plugin Catalog Quick Start (Docker) Standalone Mode Cluster with etcd Kubernetes Ingress Admin API Observability Performance Decision Guide ReferencesApache APISIX
Cloud-native API gateway built on OpenResty (Nginx + LuaJIT) with a dynamic plugin system and etcd-backed config store.
What is Apache APISIX?
Apache APISIX is a high-performance, cloud-native API gateway and ingress controller built on OpenResty (Nginx + LuaJIT). It handles north-south traffic (client → backend) and supports east-west service mesh use cases via APISIX's mesh mode.
Unlike Kong (which uses PostgreSQL or Cassandra for config), APISIX uses etcd as its config store, enabling sub-millisecond hot-reload of routes, plugins, and upstreams — no gateway restart needed.
Architecture
APISIX separates the data plane (request handling) from the control plane (config management). In a production cluster, multiple APISIX data plane nodes pull config from a shared etcd cluster.
Component Roles
| Component | Role | Notes |
|---|---|---|
| APISIX (data plane) | Processes every API request — routing, plugin execution, load balancing, SSL | Stateless; multiple nodes for HA |
| etcd | Source of truth for all config: routes, upstreams, consumers, plugins | Run 3-node cluster in production |
| Admin API | REST API on port 9180 for CRUD operations on routes/upstreams/etc. | Protected by Admin key; can be disabled |
| APISIX Dashboard | Web UI on port 9000 wrapping the Admin API | Separate Docker image; optional |
| Plugin Runner | Sidecar process for running plugins in Java / Python / Go | Communicates via Unix socket |
Plugin Execution Phases
Every request goes through these Nginx phases. Plugins declare which phases they hook into:
-- Phase order for a proxied request:
init -- worker startup
rewrite -- URL rewriting, header manipulation before routing
access -- auth, rate limiting, IP checks (before upstream call)
before_proxy -- final chance to modify upstream request
header_filter -- modify response headers from upstream
body_filter -- modify response body
log -- async logging, metrics, tracing (after response sent)
APISIX vs Kong vs Traefik vs Envoy
| Feature | APISIX 3.x | Kong 3.x | Traefik 3.x | Envoy / Istio |
|---|---|---|---|---|
| Engine | OpenResty (Nginx + LuaJIT) | OpenResty (Nginx + LuaJIT) | Go | C++ (Envoy) |
| Config store | etcd (dynamic) | PostgreSQL / Cassandra | In-memory + provider APIs | xDS (Istio control plane) |
| Dynamic config (no restart) | ✓ Sub-ms via etcd watch | ~ DB polling | ✓ Provider watch | ✓ xDS |
| Plugin language | Lua, WASM, Java/Python/Go runners | Lua, Go (Kong Gateway) | Go (middleware) | C++, WASM, Ext. authz |
| Built-in plugins | 80+ | 60+ | 30+ middlewares | Filters (limited built-in) |
| Kubernetes native | ✓ Ingress Controller + CRDs | ✓ KIC | ✓ Native K8s provider | ✓ First-class |
| Service mesh | ~ Sidecar mode (experimental) | ✗ North-south only | ✗ North-south only | ✓ Core use case |
| gRPC / WebSocket | ✓ | ✓ | ✓ | ✓ |
| Dashboard (OSS) | ✓ (separate container) | ✗ (Enterprise only) | ✓ Built-in | ~ Kiali (Istio) |
| Performance (raw) | Very high (LuaJIT) | Very high (LuaJIT) | High (Go) | Very high (C++) |
| License | Apache 2.0 | Apache 2.0 (OSS) / BSL (EE) | MIT | Apache 2.0 |
| Learning curve | Medium (Lua plugins) | Medium (similar to APISIX) | Low (K8s-native feel) | High (Envoy config is complex) |
Core Concepts: Routes, Services, Upstreams
APISIX's data model has a clear hierarchy. Understanding these six objects covers 90% of day-to-day config.
Object hierarchy
Consumer (API caller identity)
└── Credentials (key, JWT, OAuth token...)
Global Rule (plugin applied to ALL routes)
Plugin Config (reusable plugin bundle)
└── referenced by multiple Routes
Route ──── matches: host + path + method
├── plugins: [array of per-route plugin configs]
├── service_id → Service (optional shared layer)
└── upstream_id → Upstream (OR inline upstream)
Service (shared Route config / plugin set)
└── upstream_id → Upstream
Upstream (load balancer config)
└── nodes: { "host:port": weight, ... }
health-check, retries, timeout, scheme
Route
A Route is the entry point. It matches incoming requests and defines what happens to them.
# POST /apisix/admin/routes/1
{
"uri": "/api/v1/*",
"methods": ["GET", "POST"],
"host": "api.example.com",
"plugins": {
"jwt-auth": {},
"rate-limiting": { "count": 100, "time_window": 60 }
},
"upstream_id": "backend-v1"
}
Upstream
An Upstream defines the backend servers and load-balancing strategy. Supports health checks and circuit breaking.
# POST /apisix/admin/upstreams/backend-v1
{
"name": "backend-v1",
"type": "roundrobin", // roundrobin | least_conn | ewma | chash
"nodes": {
"svc-a:8080": 1,
"svc-b:8080": 2 // weight 2 = 2x traffic
},
"scheme": "http",
"timeout": { "connect": 6, "send": 6, "read": 6 },
"retries": 1,
"checks": {
"active": {
"http_path": "/health",
"healthy": { "interval": 5, "successes": 2 },
"unhealthy": { "interval": 5, "http_failures": 3 }
}
}
}
Service
A Service is an optional shared layer that bundles a common upstream + plugin set. Multiple routes can reference the same service, avoiding duplication.
# POST /apisix/admin/services/user-svc
{
"name": "user-service",
"upstream_id": "backend-v1",
"plugins": {
"prometheus": {},
"cors": { "allow_origins": "*" }
}
}
Load Balancing Algorithms
| Type | Best for |
|---|---|
| roundrobin | Default; equal-weight distribution |
| least_conn | Variable request duration; send to least-busy node |
| ewma | Exponentially Weighted Moving Average latency — picks fastest node |
| chash | Consistent hashing by key (IP, header, query param) — sticky sessions |
| ip_hash | Client IP sticky routing (legacy; prefer chash) |
Consumers & Authentication
A Consumer represents an API caller identity. Credentials are attached to Consumers, and plugins can restrict access per Consumer or Consumer Group.
Consumer example — Key Auth
# 1. Create consumer
# POST /apisix/admin/consumers
{
"username": "alice",
"plugins": {
"key-auth": { "key": "my-secret-api-key" }
}
}
# 2. Enable key-auth on the route
# In the route's plugins block:
{
"key-auth": {} // no config needed — just activates the plugin
}
# 3. Client calls API with key in header (default) or query param
curl -H "apikey: my-secret-api-key" https://api.example.com/api/v1/users
Consumer Groups
Consumer Groups let you apply a shared plugin config (e.g., a rate limit tier) to many Consumers at once.
# POST /apisix/admin/consumer_groups/premium-tier
{
"plugins": {
"rate-limiting": { "count": 10000, "time_window": 60 }
}
}
# Assign consumer to group
{
"username": "alice",
"group_id": "premium-tier",
"plugins": { "key-auth": { "key": "..." } }
}
Supported Auth Methods
| Plugin | Protocol | Notes |
|---|---|---|
| key-auth | API Key | Header or query param |
| jwt-auth | JWT | Validates signature + claims; HS256/RS256 |
| basic-auth | HTTP Basic | For simple use cases / internal tools |
| oauth2 | OAuth 2.0 | Token introspection endpoint |
| openid-connect | OIDC | Integrates with Keycloak, Auth0, Okta… |
| ldap-auth | LDAP | Enterprise directory lookup |
| hmac-auth | HMAC signature | Request signing (AWS-style) |
| wolf-rbac | RBAC | Role-based access via Wolf server |
| casbin | Policy-based | Fine-grained authz with Casbin rules |
Plugin System
Plugins are the core extensibility mechanism. They can be enabled per-Route, per-Service, or globally (Global Rules). APISIX evaluates plugins in priority order within each execution phase.
Plugin scope hierarchy
Global Rule (applied to ALL requests, e.g. IP allow-list)
▼
Service (applied to all routes using this service)
▼
Route (most specific; overrides service-level config)
▼
Consumer (credential-level config, e.g. per-consumer rate limit)
Writing a custom Lua plugin
-- /usr/local/apisix/apisix/plugins/my-plugin.lua
local plugin_name = "my-plugin"
local schema = {
type = "object",
properties = {
message = { type = "string", default = "hello" }
}
}
local _M = {
version = 0.1,
priority = 1000, -- higher = runs earlier in the same phase
name = plugin_name,
schema = schema,
}
function _M.access(conf, ctx)
-- runs in the access phase (before proxying)
core.response.set_header("X-My-Plugin", conf.message)
end
return _M
External Plugin Runners
For teams who don't want to write Lua, APISIX supports external plugin runners — sidecar processes that communicate via Unix socket (RPC). Available runners:
- Java —
apisix-java-plugin-runner - Go —
apisix-go-plugin-runner - Python —
apisix-python-plugin-runner - WASM — WebAssembly plugins (Go/Rust compiled to WASM)
Plugin Config (reusable bundles)
# Create a reusable plugin config
# POST /apisix/admin/plugin_configs/standard-auth
{
"plugins": {
"jwt-auth": {},
"rate-limiting": { "count": 200, "time_window": 60 },
"prometheus": {}
}
}
# Reference in a route (instead of repeating plugin config)
{
"uri": "/api/v2/*",
"plugin_config_id": "standard-auth",
"upstream_id": "backend-v2"
}
Plugin Catalog (Key Plugins)
Authentication
Traffic Management
Observability
Transformation & Header Manipulation
Security
Quick Start with Docker Compose
The fastest way to run APISIX locally with etcd and the Dashboard.
# docker-compose.yml
version: "3"
services:
etcd:
image: bitnami/etcd:3.5
environment:
ALLOW_NONE_AUTHENTICATION: "yes"
ports:
- "2379:2379"
apisix:
image: apache/apisix:3.9.0-debian
volumes:
- ./config/apisix.yaml:/usr/local/apisix/conf/config.yaml
ports:
- "9080:9080" # HTTP proxy
- "9443:9443" # HTTPS proxy
- "9180:9180" # Admin API
- "9091:9091" # Prometheus metrics
depends_on:
- etcd
dashboard:
image: apache/apisix-dashboard:3.0.0-centos
volumes:
- ./config/dashboard.yaml:/usr/local/apisix-dashboard/conf/conf.yaml
ports:
- "9000:9000"
depends_on:
- apisix
# config/apisix.yaml — minimal config
apisix:
node_listen: 9080
enable_ipv6: false
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
host:
- "http://etcd:2379"
prefix: /apisix
timeout: 30
plugin_attr:
prometheus:
export_addr:
ip: "0.0.0.0"
port: 9091
# Create your first route via Admin API
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" \
-X PUT -d '{
"uri": "/get",
"name": "test-route",
"upstream": {
"type": "roundrobin",
"nodes": { "httpbin.org:80": 1 }
}
}'
# Test it
curl http://127.0.0.1:9080/get
edd1c9f034335f136f87ad84b625c8f1. Change it immediately in production by setting apisix.admin_key in your config.
Standalone Mode (No etcd)
Standalone mode reads config from a static apisix.yaml file on disk. No etcd needed — ideal for simple deployments, local dev, and CI pipelines where dynamic updates aren't required.
# config.yaml — enable standalone mode
deployment:
role: data_plane
role_data_plane:
config_provider: yaml # reads conf/apisix.yaml
# conf/apisix.yaml — declarative route definitions
routes:
- uri: /api/hello
upstream:
type: roundrobin
nodes:
"backend:8080": 1
plugins:
rate-limiting:
count: 100
time_window: 60
key_type: remote_addr
rejected_code: 429
- uri: /api/users/*
upstream:
type: roundrobin
nodes:
"user-service:8080": 1
plugins:
jwt-auth: {}
proxy-rewrite:
regex_uri: ["^/api/users/(.*)", "/$1"]
# Config hot-reload: touch the file or send SIGHUP
# APISIX polls apisix.yaml every 1s in standalone mode
HA Cluster with etcd
Production APISIX deployments run multiple data plane nodes behind a load balancer, all reading from a 3-node etcd cluster for high availability.
etcd production tips
- Run a 3-node (or 5-node) etcd cluster. etcd uses Raft — it needs a majority quorum to operate.
- Separate etcd from APISIX data plane nodes — don't run etcd on the same host as the gateway.
- Enable etcd TLS (
etcd-client-cert.pem) if the Admin API is exposed beyond localhost. - Back up etcd regularly:
etcdctl snapshot save. - Tune
etcd.timeoutin APISIX config if network latency to etcd is high.
APISIX config for HA cluster
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
host:
- "https://etcd-0:2379"
- "https://etcd-1:2379"
- "https://etcd-2:2379"
tls:
cert: /etc/ssl/etcd-client.pem
key: /etc/ssl/etcd-client-key.pem
verify: true
prefix: /apisix
timeout: 30
nginx_config:
worker_processes: auto
worker_connections: 10620
Kubernetes: APISIX Ingress Controller
APISIX Ingress Controller (AIC) is a K8s operator that watches Ingress resources and custom CRDs (ApisixRoute, ApisixUpstream, ApisixConsumer) and syncs them to a running APISIX instance.
Install via Helm
# Add chart repo
helm repo add apisix https://charts.apiseven.com
helm repo update
# Install APISIX + Ingress Controller in one chart
helm install apisix apisix/apisix \
--namespace ingress-apisix \
--create-namespace \
--set gateway.type=LoadBalancer \
--set ingress-controller.enabled=true \
--set ingress-controller.config.apisix.serviceNamespace=ingress-apisix
Standard Kubernetes Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-api
annotations:
kubernetes.io/ingress.class: apisix
k8s.apisix.apache.org/rewrite-target: /
spec:
rules:
- host: api.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: my-backend-svc
port:
number: 8080
ApisixRoute CRD (more powerful than Ingress)
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: user-api-route
spec:
http:
- name: users
match:
hosts:
- api.example.com
paths:
- /api/v1/users*
methods:
- GET
- POST
backends:
- serviceName: user-service
servicePort: 8080
weight: 100
plugins:
- name: jwt-auth
enable: true
- name: rate-limiting
enable: true
config:
count: 200
time_window: 60
key_type: consumer
ApisixConsumer CRD
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
name: alice
spec:
authParameter:
jwtAuth:
value:
key: alice-jwt-key
secret: my-signing-secret
ApisixRoute CRDs when you need plugins, traffic splitting, or advanced match rules. The AIC watches both — you can mix and match.
Canary deployment with traffic-split
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: canary-route
spec:
http:
- name: canary
match:
paths: [/api/v1/*]
backends:
- serviceName: backend-stable
servicePort: 8080
weight: 90
- serviceName: backend-canary
servicePort: 8080
weight: 10 # 10% traffic to new version
Admin API Reference
The Admin API runs on port 9180 (separate from the proxy port 9080). All operations require the X-API-KEY header.
| Resource | Endpoint | Methods |
|---|---|---|
| Routes | /apisix/admin/routes/{id} | GET PUT POST PATCH DELETE |
| Services | /apisix/admin/services/{id} | GET PUT POST PATCH DELETE |
| Upstreams | /apisix/admin/upstreams/{id} | GET PUT POST PATCH DELETE |
| Consumers | /apisix/admin/consumers/{username} | GET PUT DELETE |
| Consumer Groups | /apisix/admin/consumer_groups/{id} | GET PUT DELETE |
| Plugin Configs | /apisix/admin/plugin_configs/{id} | GET PUT POST PATCH DELETE |
| Global Rules | /apisix/admin/global_rules/{id} | GET PUT POST PATCH DELETE |
| SSL Certs | /apisix/admin/ssls/{id} | GET PUT POST DELETE |
| Plugins (list) | /apisix/admin/plugins/list | GET |
| Health | /apisix/admin/status | GET |
Common Admin API patterns
# List all routes
curl http://127.0.0.1:9180/apisix/admin/routes \
-H "X-API-KEY: $APISIX_ADMIN_KEY"
# Patch a single field without full replace
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $APISIX_ADMIN_KEY" \
-X PATCH \
-d '{"status": 0}' # 0 = disable route, 1 = enable
# Upload TLS certificate
curl http://127.0.0.1:9180/apisix/admin/ssls/1 \
-H "X-API-KEY: $APISIX_ADMIN_KEY" \
-X PUT \
-d "{
\"snis\": [\"api.example.com\"],
\"cert\": \"$(cat cert.pem)\",
\"key\": \"$(cat key.pem)\"
}"
# Check node health
curl http://127.0.0.1:9180/apisix/admin/upstreams/backend-v1/health \
-H "X-API-KEY: $APISIX_ADMIN_KEY"
Observability
Prometheus metrics
Enable the prometheus plugin globally and scrape :9091/apisix/prometheus/metrics.
# Global rule — applies Prometheus to all routes
# POST /apisix/admin/global_rules/prometheus
{
"plugins": {
"prometheus": {
"prefer_name": true // use route name in labels instead of ID
}
}
}
Key metrics exposed:
apisix_http_requests_total— request count by route, status, methodapisix_http_latency_bucket— latency histogram (request/upstream)apisix_bandwidth— bytes in/out per routeapisix_nginx_http_current_connections— active connectionsapisix_upstream_status— health check results per upstream node
Distributed Tracing with OpenTelemetry
# apisix.yaml — global OTel config
plugin_attr:
opentelemetry:
resource:
service.name: "apisix"
collector:
address: "otel-collector:4317"
request_timeout: 3
set_ngx_var: false
batch_span_processor:
max_export_batch_size: 512
inactive_timeout: 2
# Enable per-route
{
"opentelemetry": {
"sampler": { "name": "parentbased_traceidratio", "options": { "fraction": 0.1 } }
}
}
Access log shipping to Kafka
{
"kafka-logger": {
"broker_list": { "kafka-broker:9092": 1 },
"kafka_topic": "apisix-access-logs",
"timeout": 3,
"include_req_body": false
}
}
Performance
APISIX is one of the highest-throughput API gateways available. Its performance comes from OpenResty's non-blocking I/O and LuaJIT's JIT compilation.
| Gateway | ~Throughput (RPS) | ~P99 Latency | Notes |
|---|---|---|---|
| APISIX 3.x (no plugins) | >200,000 RPS | <2ms | Single node, simple routing |
| APISIX 3.x (jwt-auth + rate-limit) | ~80,000–120,000 RPS | 2–5ms | Depends on plugin count |
| Kong 3.x | Similar range | Similar | Same OpenResty base |
| Traefik 3.x | ~50,000–100,000 RPS | 3–8ms | Go GC pauses at high load |
| Nginx (raw) | >500,000 RPS | <1ms | Baseline — no gateway features |
Tuning tips
- Set
worker_processes: autoto use all CPU cores. - Increase
worker_connectionsto 10620+ for high concurrency. - Use Redis-backed rate limiting (
policy: redis) only when you need cluster-wide counts — local policy is faster for single-node or when per-node limits are acceptable. - Prefer Lua plugins over external runners for latency-sensitive routes.
- Use
proxy-cacheto absorb repeated identical requests to slow upstreams. - Disable unused plugins globally — even unexecuted plugins add microseconds to plugin chain resolution.
# nginx_config tuning in apisix config.yaml
nginx_config:
worker_processes: auto
worker_rlimit_nofile: 65535
event:
worker_connections: 16384
http:
keepalive_timeout: 60s
client_max_body_size: 0
gzip: "on"
real_ip_header: "X-Forwarded-For"
Decision Guide
- You need dynamic config hot-reload without restarts — canary deployments, feature flags at the gateway layer
- You want a fully open-source API gateway with a free web dashboard
- Your team is comfortable with Lua or you need plugin logic in Java/Go/Python via runners
- You're running Kubernetes and want Ingress + CRD-based routing with advanced plugins
- You need gRPC transcoding, MQTT proxying, or Dubbo support
- You need high throughput + low latency — APISIX's LuaJIT base is hard to beat
- You want zero config for K8s Ingress — Traefik's Docker/K8s auto-discovery is simpler to set up
- Your team is Go-first and wants to write middleware in Go without a sidecar
- You don't need rich plugin ecosystem — just routing, TLS, and basic rate limiting
- You need a service mesh (east-west mTLS between services, not just north-south)
- You're already using Istio and want gateway + mesh in one platform
- You need xDS-based dynamic config compatible with the broader Envoy ecosystem
- Canary deploy →
traffic-splitplugin or ApisixRoute weighted backends - Rate limit by user →
rate-limitingwithkey_type: consumer+ Redis - JWT validation →
jwt-authplugin + Consumer with secret - Strip /api prefix →
proxy-rewritewithregex_uri - Circuit breaker →
api-breakerplugin on upstream route - gRPC as REST →
grpc-transcodeplugin with proto file - Block bad IPs →
ip-restrictionas Global Rule - Trace all requests →
opentelemetryas Global Rule with sampling
References
Official documentation and community resources. All links verified as of 2026-06-27.