Routing a Test Hostname to a Separate Backend
- Applies to
- Gatewayruntime for API, AI, and MCP gateways
- Hosting
- Managed EdgeNot availableManaged DedicatedSelf-HostedNot available
Test a new backend release against production data by sending a second public hostname through the same production Zuplo gateway. Akamai tags each request with a header based on the hostname, and one Zuplo policy uses that header to pick the production or test backend. Your frontend and API clients change nothing except the hostname they call.
How it works
Akamai turns the hostname into a header, and the gateway turns the header into a backend. Everything between the client and the backend is the production path for both hostnames: the same Akamai property, the same gateway deployment, and the same routes, policies, and rate limits.
The examples on this page use these hostnames. Replace them with your own.
| Example hostname | Purpose |
|---|---|
api.example.com | Public production hostname |
api-test.example.com | New public test hostname, on the same Akamai property |
gateway.example.com | Production Zuplo custom domain, the Akamai origin |
origin-api.example.com | Production backend |
origin-api-test.example.com | Test backend that runs the release candidate |
Prerequisites
- An Akamai property that already forwards
api.example.comto your production Zuplo gateway. See Setting up Akamai CDNs. - A test backend that runs the release you want to validate and can reach production data.
- An edge certificate and DNS access for the new test hostname.
Step 1: Set the routing header in Akamai
Both public hostnames live on the same Akamai property and use the same origin:
the production Zuplo custom domain. Akamai always sets x-zuplo-route, so it
overwrites any value a client sends.
In Property Manager, edit the property and make these changes:
- Default rule. Add a
Modify Outgoing Request Header
behavior. Set the action to Add or Modify, the custom header name to
x-zuplo-route, and the value toprod. - New child rule named
Test routing. Add a Hostname match forapi-test.example.com. Add a Modify Outgoing Request Header behavior with the custom header namex-zuplo-routeand the valuetest. Because the child rule runs after the default rule, it wins for the test hostname. - Cache key. Make sure test and production responses can't share a cache
entry. Set Cache Key Hostname to Incoming Host Header, or turn off
caching in the
Test routingrule. - Hostname. Add
api-test.example.comto the property's hostnames with its edge certificate, and create its CNAME record in your DNS.
Activate the property version. Clients that test the release only change the base URL they call. They never send the header themselves.
Step 2: Add the backend URLs in Zuplo
In the Zuplo Portal, open your
project and select Settings → Environment Variables. Add these variables to
the production environment. If you already have a variable for the production
backend, keep its name and use it in place of BASE_URL_PROD in the following
steps.
Code
You don't need a new Zuplo environment, branch, or custom domain. The production deployment you run today serves both hostnames. For more on variables, see Environment variables.
Step 3: Add the routing policy
This policy follows the pattern in
Canary routing for employees,
adapted to one signal: the x-zuplo-route header that Akamai sets. Any value
other than test goes to production, so a missing or unexpected value never
reaches the test backend. You add the policy once; it doesn't change between
releases.
Create modules/test-routing.ts:
modules/test-routing.ts
Register it in config/policies.json:
config/policies.json
Step 4: Point each route at the chosen backend
On every route that forwards to the backend, make two changes:
- Set the URL Forward handler
baseUrlto${context.custom.backendUrl}instead of the environment variable. - Add
test-routingto the inbound policies. Keep your existing policies. The routing policy only reads a header, so its position in the list doesn't matter.
config/routes.oas.json
If the route connects to your backend with
mTLS, keep the mtlsCertificate
option you use today and configure the test backend to trust the same client
certificate. One setting then covers both backends.
Deploy this change to production once. From then on, every route, policy, and rate limit is identical for both hostnames; only the backend differs.
Test the setup
Send these requests and check the x-backend-stage response header on each:
| Request | Expected x-backend-stage |
|---|---|
curl -i https://api.example.com/v1/products/1 | prod |
curl -i https://api-test.example.com/v1/products/1 | test |
curl -i https://api.example.com/v1/products/1 -H "x-zuplo-route: test" | prod (Akamai overwrote the header) |
If the third request returns test, the default rule from Step 1 isn't setting
the header. Fix that before you share the test hostname.
Release flow
A backend release doesn't require a gateway change:
- Your CI/CD pipeline deploys the new backend version to the test backend.
- Testers and test builds of your frontend call
api-test.example.com. Their requests go through the production Akamai property and the production gateway and its policies, to production data. - When testing passes, promote the same build to the production backend.
Keep using your non-production Zuplo environments to test gateway changes. Changing the gateway and the backend in the same test makes a failure hard to place. Serving both hostnames from one gateway also means the test and production paths can't drift apart, which a separate Zuplo environment for testing would allow.
Guardrails
- Only Akamai sets the header. If
gateway.example.comis reachable without going through Akamai, anyone could sendx-zuplo-route: test. Restrict the gateway to Akamai traffic with a shared secret header or mTLS. See Securing Zuplo from direct access. - The test hostname uses production data. Treat it as production: keep the same authentication and rate limits, and don't publish the hostname.
Related resources
- Akamai architecture
- Controlling Akamai CDN caching
- User-based backend routing
- ZuploContext
addResponseSendingHook