Progressive Feature Rollouts in VCL: Safely Enabling Features for 5% of Traffic
When working with Fastly and VCL, one of the most powerful patterns you can implement is progressive delivery at the edge.
Instead of deploying a feature and exposing it to all users at once, you can gradually roll it out to a percentage of traffic, observe behavior, and increase confidence before going fully live.
This post focuses purely on the VCL mechanics behind that approach.
A Minimal 5% Rollout Pattern in VCL
Here’s a clean example of enabling a new flow for a subset of requests:
if (
req.http.X-Env == "prod"
&& randombool(5, 100)
&& req.url.path ~ "^/api/v2/resource/([^/]*)$"
) {
set req.http.X-Resource-ID = re.group.1;
set req.http.X-Feature-Flag = "v2_enabled";
error 680;
}if (req.http.X-Service == "EDGE_RENDER") {
call feature_recv;
}Breaking Down the Pattern
This is a three-layer gate that determines whether a request should enter a new execution path.
1. Environment Guard
req.http.X-Env == "prod"The first condition ensures the logic only applies in a specific environment.
This is useful when:
- You share VCL across staging and production
- You want explicit control over where rollouts happen
2. Percentage-Based Traffic Sampling
randombool(5, 100)This is the core rollout mechanism.
- Returns
true~5% of the time - Evaluated per request
- Stateless and fast
This effectively creates a 5% traffic slice at the edge.
3. Route Targeting with Regex
req.url.path ~ "^/api/v2/resource/([^/]*)$"Instead of applying globally, we scope the rollout to a specific route.
The regex also captures a dynamic segment:
re.group.1→ resource identifier
This allows you to extract values directly from the URL.
4. Passing State via Headers
set req.http.X-Resource-ID = re.group.1;
set req.http.X-Feature-Flag = "v2_enabled";Headers act as a lightweight communication channel between layers.
They allow downstream logic to:
- Know which feature path was selected
- Access extracted parameters
- Log or debug rollout behavior
5. Switching Execution Paths with Custom Errors
error 680;This is a key VCL pattern.
Get John Sourgiadakis’s stories in your inbox
Join Medium for free to get updates from this writer.
Instead of mutating the request inline, you:
- Raise a synthetic error
- Handle it in
vcl_error
Example:
if (obj.status == 680) {
set obj.status = 307;
set obj.http.Location = "/api/v2/alternate";
return (deliver);
}This keeps your logic:
- Modular
- Easier to reason about
- Cleaner to extend
6. Delegating to Subroutines
if (req.http.X-Service == "EDGE_RENDER") {
call feature_recv;
}Once flagged, the request can be processed by a dedicated subroutine.
This enables:
- Separation of concerns
- Reusable logic blocks
- Cleaner VCL organization
Making the Rollout Incremental
The beauty of this pattern is how easy it is to scale:
randombool(10, 100) # 10%
randombool(25, 100) # 25%
randombool(50, 100) # 50%
randombool(100, 100) # 100%You move gradually from:
- limited exposure
- to partial rollout
- to full adoption
Improving Consistency (Advanced)
One limitation of randombool() is that it operates per request, not per user.
If you need consistency, you can base the decision on a stable identifier:
if (digest.hash_md5(req.http.User-ID) % 100 < 5) {This ensures:
- The same user always gets the same experience
- True feature flag behavior
Why This Works Well in VCL
This pattern is effective because:
- Decisions are made at the edge, before origin load
- No backend deployment is required to adjust rollout
- It composes well with existing routing logic
- It is extremely fast and stateless
Final Thoughts
VCL gives you low-level control over request flow, and with just a few primitives — conditionals, regex, headers, and randombool() — you can build a robust progressive delivery system.
Start small, observe behavior, and increase gradually.
That’s the essence of safe rollouts at the edge.





