Skip to content
By Latent Byte

Cutting Kutt redirect latency with Cloudflare Workers and KV

How Cloudflare Workers and KV reduced Kutt redirect latency while preserving analytics.

Cloudflareperformancearchitecture

Cutting Kutt redirect latency with Cloudflare Workers and KV

We run Kutt as a self-hosted link shortener. The application and its PostgreSQL and Redis dependencies live on a VPS in Europe. That setup is simple and reliable, but every redirect normally travels to the VPS before Kutt can return the destination.

From a test machine near Cloudflare's LAX edge, that distance was visible:

First hopCloudflare edgeDirect VPS
Average TTFB~48 ms~184 ms
Average total~48 ms~184 ms
TLS handshake~22 ms~127 ms
Connect~3 ms~58 ms

The edge-backed short link returned its redirect about 3.8 times faster in this test. Both links ultimately pointed to Google.

Following the complete redirect chain produced a smaller difference: roughly 268 ms through the edge and 279 ms through the VPS. Most of that journey happened after our shortener, including Google's own redirect to www.google.com. Optimizing the first hop cannot remove latency owned by the destination site.

These numbers are one regional comparison, not a global benchmark. The useful result is narrower: moving the short-link decision close to the visitor removed most of the network and TLS cost between Los Angeles and our European origin.

European visitors may see a smaller gain because the origin already sits in Europe. The edge still helps with TLS termination at a nearby PoP and removes a long cross-Atlantic round trip for users far from the VPS, but it will not beat geography everywhere by the same margin.

How we measured it

Both hostnames pointed at the same slug (/testgoogle) and returned 302 to https://google.com. We used curl from a machine near Cloudflare's LAX edge, ten runs per hostname, first hop only (--max-redirs 0). That isolates the shortener from the destination site's own redirects. End-to-end timing used -L and included Google's hop to www.google.com.

HostnamePathRole in test
to.latentbyte.comCloudflare Worker + KVEdge canary
to-origin.latentbyte.comDirect DNS to VPSSame stock Kutt origin, no Worker

KV warm vs cold

The ~48 ms result assumes a KV hit. On a cache miss, the Worker performs one synchronous origin lookup before it can return the 302. That first visitor still waits on origin RTT. Repeat clicks read the destination from KV at the edge and get the fast path. When benchmarking, warm the slug first or report cold and warm separately.

The constraint: preserve Kutt analytics

Caching a 302 at the edge is easy. Preserving Kutt's visit analytics is the interesting part.

A normal Kutt request does two jobs:

  1. Look up the slug and return its destination.
  2. Queue a visit containing the browser, operating system or device, country, referrer, and click increment.

If Cloudflare returns a cached redirect without contacting Kutt, the user gets a fast response but Kutt never records the visit.

We split those jobs in a Cloudflare Worker:

  1. Read the destination from Cloudflare KV.
  2. Return the 302 immediately.
  3. Use waitUntil() to send a background GET to the stock Kutt origin.
  4. Forward the visitor's User-Agent, Referer, IP headers, and Cloudflare country header so Kutt records the same fields it normally derives.

The analytics write is best effort. If the origin is unavailable, the visitor can still use a cached short link, but that click may be absent from Kutt. We accepted that trade-off in exchange for redirect availability and lower latency.

Filling KV without counting twice

A cache miss needs one synchronous origin lookup to discover the destination. A second origin request records the actual visit. Sending both as an ordinary visitor would double-count the first click.

Kutt already ignores known bots. The Worker uses a Googlebot user agent for the lookup request, which makes stock Kutt skip its visit. It then sends the real visitor headers in the background analytics request. This produces one recorded visit rather than two, without modifying Kutt.

Only absolute HTTP or HTTPS destinations enter KV. Relative Kutt control redirects such as /404 and /banned, password pages, and other non-redirect responses stay on the origin path.

What still hits the origin

The Worker is not a full reverse proxy for every path. These requests pass through to stock Kutt unchanged:

  • UI and admin routes (/, /login, /settings, /admin, and similar preserved paths)
  • API and static assets
  • Password-protected links, which return HTML rather than a Location header
  • Link info pages (slug+)
  • Any non-GET method

Only a single path segment matching Kutt's slug pattern on GET is eligible for KV. That keeps the edge layer narrow: cache the redirect decision, proxy everything else.

Domain-scoped links required origin routing

Kutt scopes links by hostname. A slug stored for to.latentbyte.com does not resolve when the origin receives Host: to-origin.latentbyte.com.

Cloudflare's resolveOverride gave us the required separation:

  • The request URL and Host remain to.latentbyte.com for Kutt's lookup.
  • DNS resolution uses to-origin.latentbyte.com.
  • That DNS-only hostname points to the same Dokploy-hosted Kutt origin.

The public hostname uses a proxied DNS record and a Worker Route. We initially tried a Worker Custom Domain, but subrequests to the same public hostname recursed through the Worker. A Worker Route over the proxied origin record was the correct mechanism for this topology.

We also found a Cloudflare SSL mode trap. The zone was set to Flexible, so Cloudflare contacted the origin over HTTP. Traefik redirected HTTP back to HTTPS, producing a self-redirect loop. Switching the zone to Full SSL allowed the Worker subrequest to reach the HTTPS origin correctly.

One failed test had already cached that self-redirect. We deleted the poisoned KV entry and added a guard that refuses to cache a redirect whose destination is the original request URL.

Smart cache refresh without another origin request

A fixed TTL creates a stale-link window after someone edits or deletes a link in Kutt. We use a 15-minute KV TTL as a final bound, but the background analytics request gives us a better refresh signal on every click.

The Worker now reconciles KV from the analytics response:

  • A changed external destination updates KV for the next click.
  • A deleted, banned, password-protected, or not-found response removes the KV entry.
  • A network error or origin 5xx keeps the last known destination.
  • A link with no traffic still expires through the normal 15-minute TTL.

This adds no extra origin request. The current click can still use the previous destination because its response is already on the way, but the following click sees the refreshed value.

Keeping the last known target on transient failures is deliberate. Deleting it during an outage would turn a temporary analytics problem into a redirect outage.

KV is not the CDN response cache

During testing, one report interpreted cf-cache-status: EXPIRED as evidence that edge caching was not working. That header describes Cloudflare's HTTP response cache, not Workers KV.

Our Worker-generated 302 responses do not use the CDN response cache and currently return no cf-cache-status header. The target is stored in KV instead. We verified the KV entry directly and repeated requests returned the stored destination from the Worker.

That distinction matters when measuring the system: cf-cache-status is not a KV hit indicator. Worker observability or a direct KV inspection is needed to establish whether the slug mapping is present.

Verifying the cutover

Before moving more hostnames, we checked that edge redirects did not regress Kutt behavior:

  • Visit count increases by one, not two, on the first click after a KV miss
  • Browser, OS, country, and referrer in the dashboard match the client, not the Worker
  • Country reflects the visitor, not a Cloudflare PoP default
  • Password links still render Kutt's form; slug+ still shows link info
  • Deleted or retargeted links stop working at the edge within the KV TTL, or sooner on the next click that reconciles KV

These checks matter because the whole design depends on stock Kutt remaining unmodified. The Worker adapts to Kutt's existing bot filtering and visit queue, not the other way around.

Result

The canary hostname now follows this path:

visitor
  -> nearest Cloudflare edge
  -> Worker reads destination from KV
  -> immediate 302
  -> background Kutt visit and cache reconciliation
  -> PostgreSQL analytics

In our LAX-to-Europe comparison, first-hop redirect latency fell from about 184 ms to 48 ms on warm KV hits. The existing Kutt deployment remained stock, its dashboard retained visit details, and edits now repair the edge mapping lazily on the next click.

The approach does not make destination sites faster. Best-effort analytics can lose visits when the background ping fails. A link with no traffic after an edit can stay stale until the 15-minute KV TTL expires. During origin outages we keep serving the last known destination rather than failing open to a broken redirect. Those trade-offs are deliberate: the user-visible short link should stay fast and available, with Kutt remaining the source of truth for links and analytics.