Router Options
In APISIX, there are three HTTP router options:
-
radixtree_host_uriroutes requests by hosts and URI paths, prioritizing hostnames over URI paths during matching. This is the default setting and the behaviour is the same as NGINX. -
radixtree_uriroutes requests by hosts and URI paths, prioritizing URI paths over hostnames during matching. -
radixtree_uri_with_parametersupports the use of parameters in path matching.
These router options can be configured in conf/config.yaml under apisix.router.http.
radixtree_host_uri
This is the default router setting, which prioritizes hostnames over URI paths when route matching. If you would like to explicitly configure the option, add the following block to your configuration file:
apisix:
router:
http: radixtree_host_uri
Reload APISIX for changes to take effect.
Configuring two routes with the same URI but different matching hosts is useful for scenarios like multi-tenant SaaS platforms, where each tenant is served through a custom subdomain. For example, both tenants may access the same endpoint, but the requests are routed to different upstream services based on the host.
To do so, you can configure two routes with the same matching URI but different matching hosts and upstream services:
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "httpbin-host-test1",
"uri": "/get",
"host": "test1.com",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "postman-host-test2",
"uri": "/get",
"host": "test2.com",
"upstream": {
"type": "roundrobin",
"nodes": {
"postman-echo.com:80": 1
}
}
}'
Send a request that matches the host of the first route:
curl "http://127.0.0.1:9080/get" -H 'host: test1.com'
You should see a response from httpbin.org:
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "test1.com",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-6746c0bf-653fac896be8818275f1e8da",
"X-Forwarded-Host": "test1.com"
},
"origin": "192.168.65.1, 43.252.208.90",
"url": "http://test1.com/get"
}
Send a request that matches the host of the second route:
curl "http://127.0.0.1:9080/get" -H 'host: test2.com'
You should see a response from postman-echo.com:
{
"args": {},
"headers": {
"host": "test2.com",
"x-request-start": "t=1732834286",
"connection": "close",
"x-forwarded-proto": "http",
"x-forwarded-port": "80",
"x-amzn-trace-id": "Root=1-6746c0ca-2b0b323902e784f512051ef6",
"x-forwarded-host": "test2.com",
"user-agent": "curl/8.6.0",
"accept": "*/*"
},
"url": "http://test2.com/get"
}
To understand the behaviour more, create a third route that matches all requests:
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "mock-api7-all",
"uri": "/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"mock.api7.ai:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}'
Send a request with a random host name:
curl "http://127.0.0.1:9080/get" -H 'host: random.com'
You should see the request is forwarded to the third route:
API7.ai, the creator of Apache APISIX, delivers a cloud-native API Gateway solution for the Enterprise, to help you maximize the value of APIs.
Send another request that matches the host of the first route:
curl "http://127.0.0.1:9080/get" -H 'host: test1.com'
You should see the request is forwarded to the httpbin.org upstream. This demonstrates how the router prioritizes hostnames in routing when it is set to radixtree_host_uri.
radixtree_uri
radixtree_uri prioritizes URI paths over hostnames when route matching. To use radixtree_uri as the HTTP router setting, add the following block to your configuration file:
apisix:
router:
http: radixtree_uri
Reload APISIX for changes to take effect.
To understand the behaviour, create a route that matches test1.com host and all URI paths:
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "httpbin-host-test1",
"uri": "/*",
"host": "test1.com",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
Create another route that matches requests to /get:
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "mock-api7-all",
"uri": "/get",
"upstream": {
"type": "roundrobin",
"nodes": {
"mock.api7.ai:443": 1
},
"scheme": "https",
"pass_host": "node"
}
}'
Send a request to /get with test1.com host:
curl "http://127.0.0.1:9080/get" -H 'host: test1.com'
You should see the request is forwarded to mock.api7.ai
API7.ai, the creator of Apache APISIX, delivers a cloud-native API Gateway solution for the Enterprise, to help you maximize the value of APIs.
This shows the hostname is not being prioritized when router operates in radixtree_uri mode and full path matching takes the priority.
Send another request to /anything with test1.com host:
curl "http://127.0.0.1:9080/anything" -H 'host: test1.com'
You should see the request is forwarded to the httpbin.org upstream.
radixtree_uri_with_parameter
radixtree_uri_with_parameter supports the use of parameters when route matching. To use radixtree_uri_with_parameter as the HTTP router setting, add the following block to your configuration file:
apisix:
router:
http: radixtree_uri_with_parameter
Reload APISIX for changes to take effect.
A common use case is extracting a part of the URI path and using it to construct the request sent to upstream services. This can be used to rewrite the URL path or set a custom header, enabling flexible dynamic routing and request customization based on client input. The matched URI path can be easily extracted with uri_param_<parameter_name>.
For example, you can match a URI path like /user/123/profile, extract the user ID 123, and forward the value to the upstream service in a new header.
To do so, create a route as such:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "httpbin",
"uri": "/anything/user/:user_id/profile",
"plugins":{
"proxy-rewrite": {
"headers": {
"set": {
"X-User-ID": "$uri_param_user_id"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
❶ Match requests to /anything/user/:user_id/profile where user_id is a parameter.
❷ Assign the user_id parameter value to a new header X-User-ID.
To verify, send a request to the route:
curl "http://127.0.0.1:9080/anything/user/123/profile"
You should see the following response:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-68873cf5-7248f64d19d607ea50aa9735",
"X-Forwarded-Host": "127.0.0.1",
"X-User-Id": "123"
},
...
}
The route parameter can also accept URL-encoded string. For instance, if you send a request as such:
curl -i "http://127.0.0.1:9080/anything/user/123%20456/profile"
The user ID would be extracted as 123 456:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-68873d37-7634825b20d05dee3a852cb9",
"X-Forwarded-Host": "127.0.0.1",
"X-User-Id": "123 456"
},
...
}
Match Encoded Slashes in Parameters
NGINX normally decodes an encoded slash (%2F or %2f) before route matching. The decoded slash becomes a path separator, so /files/a%2Fb does not match a parameter route such as /files/:name.
Enable match_uri_encoded_slash to keep encoded slashes as part of path parameters during matching:
apisix:
match_uri_encoded_slash: true
router:
http: radixtree_uri_with_parameter
Reload APISIX for the global setting to take effect. With the option enabled, a request to the existing example route preserves the slash in the captured parameter:
curl "http://127.0.0.1:9080/anything/user/123%2F456/profile"
The proxy-rewrite plugin receives uri_param_user_id as 123%2F456, so the upstream response should include:
{
"headers": {
"X-User-Id": "123%2F456"
}
}
The encoded form exists only during route matching and parameter capture. Plugins continue to read the normalized, decoded URI from ctx.var.uri, while APISIX forwards the original request line to the upstream with %2F preserved.
APISIX normalizes a lowercase %2f from the client to uppercase %2F for matching and parameter capture. Route definitions are compared byte for byte, so write an encoded slash in a route URI as uppercase %2F.
This option is global and can change exact-route compatibility. For example, a request to /files/a%2Fb no longer matches an exact route /files/a/b after the option is enabled.
APISIX keeps %2F encoded only when fully decoding the raw path produces the same normalized URI that NGINX already calculated. Requests that also require dot-segment resolution, slash merging, absolute-form normalization, or another normalization step fall back to the normal decoded URI. If delete_uri_tail_slash or normalize_uri_like_servlet changes the URI, that normalized URI also takes precedence and the encoded-slash match does not apply.
Match Request Body Fields
Routes can use post_arg.* variables to match fields in JSON, URL-encoded form, or multipart request bodies. For JSON and multipart bodies, APISIX limits how much body data it reads during route matching. The apisix.max_post_args_readable_size setting is measured in MiB, defaults to 64, and accepts 0 to disable the limit:
apisix:
max_post_args_readable_size: 64
The limit protects worker memory during route matching. Disabling it allows an arbitrarily large JSON or multipart body to be read before a route is selected.
If a body exceeds the limit, the post_arg.* variable has no value and that route condition does not match. APISIX does not reject the request solely because it exceeded this matching limit; another route can still match it.
For example, configure a smaller limit for verification, reload APISIX, and create a body-matching route:
apisix:
max_post_args_readable_size: 1
curl "http://127.0.0.1:9180/apisix/admin/routes/body-match" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/anything/body-match",
"methods": ["POST"],
"vars": [
["post_arg.model", "==", "gpt-4"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
A small matching body reaches the upstream:
curl -i "http://127.0.0.1:9080/anything/body-match" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4"}'
You should receive an HTTP/1.1 200 OK response. Create a JSON body larger than the configured 1 MiB limit and send it to the same URI:
{
printf '{"model":"gpt-4","pad":"'
head -c 2097152 /dev/zero | tr '\0' a
printf '"}'
} > oversized-body.json
curl -i "http://127.0.0.1:9080/anything/body-match" \
-H "Content-Type: application/json" \
--data-binary @oversized-body.json
With no fallback route, you should receive 404 Route Not Found. The error log records that the body exceeded the readable size; it is the failed route condition, not a request-body rejection, that produces the 404 in this example.
Understand Route Matching
APISIX leverages the luaradixtree library for routing, an adaptive radix tree implemented in Lua for OpenResty. It utilizes the Foreign Function Interface (FFI) to integrate with rax, ensuring efficient and high-performance routing.
There are several ways of route matching.
Full Path
Suppose the route URI is:
/anything/foo
The route will only match requests to /anything/foo.
Wildcard
Suppose the route URI is:
/anything/*
The route will match any request to sub-paths of /anything, such as /anything/foo and /anything/bar. Note that it will not match requests to /anything.
The wildcard does not need to be at the end of the URI. You can also have a route URI as such:
/*/*/test
This will match requests with URIs like /anything/foo/test.
Matching Priority
Full path matching has a higher priority than wildcard matching. Suppose you have two routes with URI /anything/foo and /anything/* respectively. The request to /anything/foo will be matched by the /anything/foo route, instead of the /anything/* route.