fly-replay feature gives you fine-grained control over request routing in your Fly.io applications. It allows you to dynamically route requests between regions, specific Machines, or even different apps within your organization.
How fly-replay Works
When your app responds to a request with afly-replay response, Fly Proxy will automatically replay the original request according to your specified routing rules. This enables advanced patterns like:
- Routing write operations to primary database regions
- Load balancing between specific Machines
- Cross-app request routing within your organization
- Implementing sticky sessions
Replay Header Format
Your app can add afly-replay header to its response. The fly-replay header accepts the following fields:
Example Usage
Route to a specific region:By default, cross-app routing works within the same organization and network. To route across networks, see Cross-Network Routing. To route to apps in other organizations, see Cross-Organization Routing.
Note: A comma-separated list of regions must be quoted.
Geographic groups and aliases
When replaying to a region, you can use geographic aliases likeus, eu, or sa to target a broader area.
Replay Timeout and Fallback
You can set atimeout and fallback on a replay to handle cases where the replay target is unreachable.
timeout sets how long the proxy tries to reach the replay target. The actual duration may slightly exceed this value. Accepts duration strings like 10s, 500ms. Without fallback, a timeout makes the replay error faster instead of waiting for the default error timeout.
fallback tells the proxy to route the request back to the Machine that issued the replay if the replay fails due to timeout, exhausted retries, or no available candidate:
force_self: Route back to the exact Machine that issued the replay. Returns a proxy error if that Machine is no longer available.prefer_self: Try the original Machine first, but fall back to any Machine in the original app if it is unavailable.
fly-replay-failed request header containing details about the failed replay attempt. Since this is still the original request, your app can respond with a useful error instead of the client receiving a generic proxy error.
Note: Fallback requests cannot themselves issue
fly-replay responses.Replay JSON Format
Your app can set the response content-type toapplication/vnd.fly.replay+json and include replay instructions in the response body.
For a complex replay the JSON body is easier to compose than the header format, and also supports more functionality.
JSON Structure
Theapplication/vnd.fly.replay+json replay body accepts the following fields:
Example Usage
Route to another app, and modify the request:Replay Caching
Replay caching allows Fly Proxy to remember and reuse replay decisions, reducing both load on your application and the latency of replayed requests. There are two types of replay caching:- Path-based: Cache replays for specific URL paths
- Session-based: Cache replays for specific cookie or header values
Note: Replay caching is an optimization, not a guarantee. Your app should not depend on this mechanism to function.
The app issuing
fly-replay still serves as the ultimate source of truth, and we may decide to consult that app at any moment even if a replay cache has previously been set.
To ensure reliable operation, the app issuing fly-replay should still have multiple instances deployed in multiple regions.Path-based Replay Caching
Replay caching for paths is configured per-request during a replay. To cache a replay for a path:- Issue a
fly-replayheader as usual - Set
fly-replay-cache: /some/path/*- This needs to be a path-matching pattern, and implicitly ends with a wildcard
/*. This is where the cached replay is applied. - The cached replay is unique to the request domain. The domain may also be set explicitly:
example.com/some/path/* - The domain part should not include ports; use
example.com, notexample.com:80. - The pattern must also match the current request.
- This needs to be a path-matching pattern, and implicitly ends with a wildcard
- Set
fly-replay-cache-ttl-secs: number_of_seconds
cache object as part of the replay:
Session-based Replay Caching
If your app’s sessions are identified by a cookie or authorization header, and should consistently route to the same target, Fly Proxy can cache that replay. Unlike path-based caching (which requires your app to returnfly-replay-cache headers), session-based caching is configured in your fly.toml. When your app issues a fly-replay response for a request with a matching cookie or header, Fly Proxy will cache that replay decision and automatically apply it to subsequent requests with the same session identifier.
Configuration
Configure session-based replay caching in yourfly.toml. This can be set under http_service.http_options or services.ports.http_options:
- Replays for requests to
/(and its subpaths) are cached for 5 minutes based on thesession_idcookie value - Replays for requests to
/api(and its subpaths) are cached for 10 minutes based on theAuthorizationheader value - When multiple rules match, the longest matching path takes precedence
path_prefix:
path_prefix doesn’t specify a hostname. Each hostname maintains its own cache.
For more details on configuration options, see the fly.toml configuration reference.
Invalidating the Replay Cache
If the replay target does eventually change, the replay target may proactively invalidate the cache by:- Issuing a
fly-replayback to the origin - Setting
fly-replay-cache: invalidate
Bypassing Replay Cache
Sometimes, you might want a client to bypass a cached replay without actually invalidating the cache. For example, you might have an API endpoint that mostly needs to be replayed to a “leader” instance. But depending on application logic, it could sometimes be handled by any instance. The recommended approach is to clearly separate endpoints that require replay from those that don’t. However, when that is not possible, the replaying app can be configured to allow bypass. For path-based replay caching, additionally set the header:"allow_bypass": true.
For session-based replay caching, set allow_bypass to true in your fly.toml configuration for your replay_cache rule.
Then, when your client makes a request, it can set the header:
Note: This behavior is opt-in because enabling it by default could make your app vulnerable to malicious clients creating unexpected load on the replaying machine.
Was my request served by the cache?
If your app needs to know whether a request was served from the replay cache, it can check the fly-replay-cache-status header. This header is sent to the replay target app or machine.- Absent: the request was not replayed and was delivered directly to the app or machine.
miss: the request didn’t use replay cache; it first hit another machine and got replayed here.hit: the request did use the replay cache! It never reached the source app or machine and was redirected straight to the target.bypass: even though the cache might have been set, it was explicitly skipped by setting thefly-replay-cache-control: skipheader.
Implementation Details
Requirements and Limitations
- Your app must use the http handler
- Requests larger than 1MB cannot be replayed
- Field combinations must be logically valid (e.g., don’t specify both app and instance if instance isn’t in that app)
- Using direct-to-storage uploads where possible
- Using the fly-prefer-region header instead
fly-replay-cache, the following limitations apply:
- The
statefield cannot be set in thefly-replayintended to be cached - Transformations for the headers or path cannot be defined.
- The TTL needs to be a minimum of 10 seconds
- Only one step of lookup is performed in the cache; as such, if the target app issues another
fly-replay-cache, the caching behavior in this case is undefined - The
timeoutandfallbackfields cannot be set in thefly-replayintended to be cached - The
fly-replay-srcheader (described below) will not be set for requests replayed through the cache
The fly-replay-src Header
When a request is replayed, Fly Proxy adds afly-replay-src header containing metadata about the original request:
This header is useful for tracking request paths and implementing consistency patterns. See the official Ruby client for an example of using these fields to prevent read-your-write inconsistency.
This header is not set when the request is replayed through a cached
fly-replay entry (fly-replay-cache).
The fly-preferred-instance-unavailable Header
If you replay withprefer_instance set, Fly Proxy will attempt to route to this Machine. This may not happen for a number of reasons, for example the Machine may not be found, or found but at its configured hard_limit.
In these cases, the request will be delivered to a different Machine that matches the remaining fields in your replay. Along with the other Fly.io-specific headers, a fly-preferred-instance-unavailable header will be set containing the ID of the instance that could not be reached.
The fly-replay-failed Header
When a replay fallback triggers, Fly Proxy delivers the request back to the original Machine with afly-replay-failed request header. This header contains semicolon-separated metadata about the failed replay attempt:
Example header value:
Web Socket Considerations
It is worth noting that an application returningfly-replay headers should not negotiate a web socket upgrade itself. Some frameworks automatically handle this process. Instead, the application or instance receiving the requests should handle the upgrade.
Alternative Routing Headers
For cases wherefly-replay isn’t suitable, Fly.io provides two alternative request headers:
The fly-prefer-region Header
The fly-force-region Header
fly-prefer-region, the Fly Proxy will not fall back to requesting the nearest region if it cannot reach any machine in the listed region(s).
Like fly-prefer-region, you may also specify multiple regions in order of preference:
The fly-prefer-instance-id Header
The fly-force-instance-id Header
Note: Get Machine IDs using
fly status or fly Machines list.Common Use Cases
Multi-Region Databases
When using global read replicas, usefly-replay to ensure write operations go to the primary region:
Cross-App Routing
Usefly-replay to implement routing layers or FaaS-style architectures:
Cross-Network Routing
By default,fly-replay with app= only routes to apps within the same private network. If you have multiple networks within your organization and need to replay requests across them, you can enable cross-network replays for your organization.
Note: Cross-network replay restrictions only apply to same-org replays. Cross-organization replays (via the allowlist) are not affected by this setting.
Enabling Cross-Network Replays
Only organization admins can manage this setting. You can enable cross-network replays via the dashboard or CLI. Dashboard: Go to your organization’s settings page and find the Routing section. Use the Cross-Network Replay toggle to enable or disable cross-network replays. CLI: Check the current status:Cross-Organization Routing
By default,fly-replay with app= only works for apps within the same organization. To allow replay requests from apps in other organizations, you must configure an allowlist on the target organization.