---
title: "15 — Security and Performance at the Edge"
uid: security-and-performance
tags: ["cache-api", "security", "bot-management", "cloudflare", "html-rewriting", "tls", "roadmap:cloudflare", "security-headers", "isolates", "performance"]
excerpt: "Two concerns I kept conflating: security comes from the isolate architecture, headers, and edge-terminated TLS; performance comes from the cache and in-flight response rewriting."
date: 2026-08-13T03:28:16+0000
source: https://www.aveshina.my.id/en/blog/security-and-performance
---

"Cloudflare makes things fast and safe, somehow" was my security-and-performance model, and it couldn't explain any of the levers. The separation that fixed it: **security comes from the isolate architecture, the headers I set, and the TLS terminated at the edge; performance comes from the cache and from rewriting responses in flight.** [1][2] Two different concerns, both applied at the edge because that's where the request lands.

The framing that landed is the layering. Cloudflare sits in front of my origin (or _is_ the origin, in a pure Workers app), which means every request passes through its security and performance machinery before reaching anything I wrote. The default settings already do a lot — DDoS mitigation, TLS termination, a CDN cache — and the work is in understanding what each layer does so I can configure it correctly rather than fighting it.

## The Workers security model

The foundation is the runtime architecture itself. Workers run in **V8 isolates** — the same sandboxing technology Chrome uses to keep one website from interfering with another [3][4]. The consequences for security:

- **Strong isolation between tenants.** One Worker cannot read another's memory, even though they share the same physical machine. V8 was designed for exactly this kind of adversarial co-tenancy.
- **No ambient access to the outside world.** A Worker can only reach external resources it's been given a binding to. There's no filesystem to wander, no child_process to spawn, no implicit network access to internal services.
- **Bounded execution.** CPU-time limits prevent a runaway Worker (or a malicious one) from consuming unbounded resources.

This is the "secure by construction" part. Where a traditional server has to be secured against the things it might do, a Worker is secured by the things it _can't_ do [3].

## Web security headers

On top of the runtime, Cloudflare makes it straightforward to set the HTTP response headers that harden the browser side [5]. The ones worth knowing:

- **Content-Security-Policy (CSP).** The big one — declares which sources the browser is allowed to load scripts, styles, images, and other resources from. A tight CSP stops most cross-site scripting (XSS) attacks dead, because injected scripts from disallowed origins are refused.
- **Strict-Transport-Security (HSTS).** Tells the browser "only ever connect to this site over HTTPS." Prevents SSL-stripping attacks where a man-in-the-middle downgrades the connection.
- **X-Frame-Options.** Prevents the page from being embedded in an iframe on another domain — a clickjacking defense.
- **Referrer-Policy.** Controls how much referrer information is sent with outgoing requests.

A Worker can set these on every response, or Cloudflare's dashboard can apply them globally. The mental note I keep: CSP is the highest-leverage one. A correct CSP is the difference between an XSS bug being a nuisance and being a catastrophe.

## Edge SSL/TLS

TLS termination is the point where the encrypted connection from the user's browser is decrypted — and doing it at the edge, rather than at the origin, has both security and performance implications [6]:

- **Closer to the user.** The TLS handshake (which adds a round trip) happens at the nearby edge, not at a distant origin. The connection establishes faster.
- **DDoS absorption.** The edge is built to absorb volumetric attacks; a TLS flood hits Cloudflare's network, not my origin.
- **Origin protection.** With TLS terminated at the edge, my origin can sit behind Cloudflare and accept only encrypted, proxied traffic — never direct exposure.

The certificate options Cloudflare offers — Universal SSL (free, basic), Dedicated SSL (custom cert), Origin CA (for the Cloudflare-to-origin leg) — are all variations on "where does the cert come from and what does it cover" [6]. Universal SSL covers the vast majority of cases; the others are for specific needs (wildcard certs, extended validation, legacy client support).

## Bot management

Not all traffic is human, and not all non-human traffic is benign. Bot management identifies automated traffic and lets me respond to it [7]:

- **Detection.** Machine learning and behavioral analysis classify requests as likely-human or likely-bot, based on signals like request patterns, headers, and TLS fingerprinting.
- **Response.** Per the classification, Cloudflare can allow, challenge (a CAPTCHA or JS check that bots fail), or block.

The threat model bot management addresses is the automated one: credential stuffing (attackers trying stolen password lists), content scraping, form spam, and the application-layer DDoS where bots flood specific endpoints. The detection is probabilistic — it's about raising the cost to the attacker and filtering the noise, not a binary "bot or not."

```figure
<svg viewBox="0 0 740 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Security and performance layers at the edge. A request enters from the left, passes through DDoS protection and bot management, hits TLS termination, is inspected by WAF/security headers, reaches the Worker (running in a V8 isolate), and gets a response back through the cache layer.">
  <defs>
    <marker id="sparrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- request -->
    <rect x="20" y="130" width="60" height="40" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="50" y="154" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">request</text>

    <!-- security layers -->
    <rect x="110" y="50" width="120" height="34" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="170" y="71" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">DDoS protection</text>
    <rect x="110" y="92" width="120" height="34" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="170" y="113" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">bot management</text>
    <rect x="110" y="134" width="120" height="34" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="170" y="155" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">TLS termination</text>
    <rect x="110" y="176" width="120" height="34" rx="6" fill="#fee2e2" stroke="#dc2626"/>
    <text x="170" y="197" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">security headers</text>

    <text x="170" y="36" font-size="10" font-style="italic" font-weight="700" fill="#7f1d1d" text-anchor="middle">security</text>

    <!-- Worker -->
    <rect x="290" y="120" width="140" height="60" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="2"/>
    <text x="360" y="144" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">Worker</text>
    <text x="360" y="162" font-size="9" fill="#052e16" text-anchor="middle">V8 isolate — isolated</text>
    <text x="360" y="174" font-size="9" fill="#052e16" text-anchor="middle">from other tenants</text>

    <!-- performance -->
    <rect x="490" y="100" width="120" height="34" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="550" y="121" font-size="10" font-weight="700" fill="#422006" text-anchor="middle">Cache API</text>
    <rect x="490" y="142" width="120" height="34" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="550" y="163" font-size="10" font-weight="700" fill="#422006" text-anchor="middle">HTMLRewriter</text>
    <rect x="490" y="184" width="120" height="34" rx="6" fill="#fef9c3" stroke="#ca8a04"/>
    <text x="550" y="205" font-size="10" font-weight="700" fill="#422006" text-anchor="middle">response headers</text>

    <text x="550" y="86" font-size="10" font-style="italic" font-weight="700" fill="#422006" text-anchor="middle">performance</text>

    <!-- response -->
    <rect x="660" y="130" width="60" height="40" rx="6" fill="#e0e7ff" stroke="#6366f1"/>
    <text x="690" y="154" font-size="10" font-weight="700" fill="#1e1b4b" text-anchor="middle">response</text>

    <path d="M80,150 L108,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#psarrow)"/>
    <path d="M230,150 L288,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#psarrow)"/>
    <path d="M430,150 L488,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#psarrow)"/>
    <path d="M610,150 L658,150" fill="none" stroke="#64748b" stroke-width="1.5" marker-end="url(#psarrow)"/>

    <text x="370" y="260" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">security is applied before the Worker; performance is applied to the response</text>
    <text x="370" y="278" font-size="10" font-style="italic" fill="#64748b" text-anchor="middle">both happen at the edge — closer to the user than the origin ever could be</text>
  </g>
</svg>
```

(I omitted the marker definition for psarrow in that SVG — it uses the same sparrow arrow.)

## The Cache API

On the performance side, the Cache API gives a Worker programmatic control over what gets cached and how [8]. Unlike the standard CDN cache (which is governed by HTTP headers), the Cache API lets me:

- Cache a Response object directly, with a cache key I choose.
- Read from the cache programmatically and decide whether to use a cached value.
- Vary cache keys on anything — the request URL, a header value, a derived key — not just the URL path.

The leverage is in the keys. A cache keyed on the URL serves the same response to everyone; a cache keyed on URL + country can serve personalized-but-cached responses per region; a cache keyed on URL + auth-context can cache per-user data safely. The Cache API is what turns the cache from "a thing that happens to my responses" into "a tool I program."

## HTML rewriting

The other performance and flexibility lever is HTMLRewriter — a streaming HTML parser that lets a Worker modify HTML as it flows through, without buffering the whole document [9]. The use cases:

- **A/B testing.** Inject a different variant of an element for a subset of users.
- **Personalization.** Tailor part of a cached page based on a cookie or a header, without busting the whole-page cache.
- **Lazy augmentation.** Add analytics scripts, ad tags, or third-party widgets to a page on the way out, so the origin doesn't have to know about them.
- **Sanitization.** Strip something unwanted (a script, a meta tag) from a response on the fly.

The reason this is a performance feature as much as a flexibility one is the streaming. The Worker doesn't wait for the full HTML, doesn't parse it into a DOM, doesn't re-serialize it. It processes elements as they stream past, which means the latency cost is tiny — often small enough to be invisible compared to buffering and rewriting the whole document.

## How I use this

The shape I keep: I treat Cloudflare's default security stack as the baseline (it's on, it works, I mostly don't touch it), I add a tight CSP on top because it's the highest-leverage header, I terminate TLS at the edge and let my origin sit behind it, and on the performance side I program the cache explicitly for the hot paths rather than relying on default header-based caching. HTMLRewriting I reach for when I need to modify a response without regenerating it — A/B tests, lazy script injection, sanitization — and I avoid it when a simpler header or cache-key change would do. The discipline is to reach for the lightest tool that solves the problem, because every layer of rewriting adds complexity that someone will have to debug later.

## References

[1] Cloudflare, "Application security &amp; performance solutions," Cloudflare Solutions, 2024. [Online]. Available: [https://www.cloudflare.com/application-services/solutions/](https://www.cloudflare.com/application-services/solutions/)

[2] Cloudflare, "Cloudflare security architecture," Cloudflare Reference Architecture, 2024. [Online]. Available: [https://developers.cloudflare.com/reference-architecture/architectures/security/](https://developers.cloudflare.com/reference-architecture/architectures/security/)

[3] Cloudflare, "Security model · Cloudflare Workers," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/workers/reference/security-model/](https://developers.cloudflare.com/workers/reference/security-model/)

[4] Cloudflare, "Reference architectures · Cloudflare," Cloudflare Reference Architecture. [Online]. Available: [https://developers.cloudflare.com/reference-architecture/](https://developers.cloudflare.com/reference-architecture/)

[5] web.dev, "Quick reference to security headers," web.dev. [Online]. Available: [https://web.dev/articles/security-headers](https://web.dev/articles/security-headers)

[6] Cloudflare, "Get started with SSL/TLS — Cloudflare Docs," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/ssl/get-started/](https://developers.cloudflare.com/ssl/get-started/)

[7] Cloudflare, "Cloudflare Bot Management &amp; Protection," Cloudflare Application Services, 2024. [Online]. Available: [https://www.cloudflare.com/application-services/products/bot-management/](https://www.cloudflare.com/application-services/products/bot-management/)

[8] Cloudflare, "How the cache works · Cloudflare Workers," Cloudflare Docs, 2024. [Online]. Available: [https://developers.cloudflare.com/workers/reference/how-the-cache-works/](https://developers.cloudflare.com/workers/reference/how-the-cache-works/)

[9] Cloudflare, "HTMLRewriter · Cloudflare Workers," Cloudflare Workers Runtime APIs, 2024. [Online]. Available: [https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/](https://developers.cloudflare.com/workers/runtime-apis/html-rewriter/)

```quiz
Q: What provides isolation between different customers' Workers running on the same physical machine?
- Each Worker runs in its own VM with its own kernel
- V8 isolates — the same sandboxing technology Chrome uses to separate websites
- Network-level firewall rules between tenants
correct: 1
explain: Workers run in V8 isolates, designed for adversarial co-tenancy. One Worker cannot read another's memory, which is the foundation of the security model.

Q: Which security header is the highest-leverage defense against cross-site scripting (XSS)?
- X-Frame-Options
- Content-Security-Policy (CSP) — it declares which sources scripts/styles can load from, blocking injected scripts
- Referrer-Policy
correct: 1
explain: A tight CSP blocks injected scripts from disallowed origins, which stops most XSS attacks regardless of how the injection happened. The other headers address different (narrower) threats.

Q: Why does terminating TLS at the edge improve both security and performance?
- It doesn't — TLS should always be terminated at the origin for security
- The handshake happens near the user (faster), Cloudflare absorbs attacks at its network, and the origin is never directly exposed
- It lets the origin skip encryption entirely, saving CPU
correct: 1
explain: Edge TLS termination means the handshake's round trips happen close to the user, volumetric attacks hit Cloudflare's network rather than the origin, and the origin sits protected behind the edge.

Q: What does the Cache API let a Worker do that default CDN caching doesn't?
- Cache responses with custom keys the Worker chooses (per country, per auth context, per derived key)
- Serve responses without contacting the origin ever
- Encrypt cached responses at rest
correct: 0
explain: Default CDN caching is URL-keyed and header-governed. The Cache API turns the cache into a programmable tool — a Worker chooses the cache key, so it can cache personalized or per-segment responses safely.

Q: HTMLRewriter modifies HTML as it streams through a Worker. What's the main reason this is also a performance feature?
- It re-renders the HTML faster than the origin
- It streams — the Worker modifies elements as they pass through, without buffering the whole document into a DOM
- It compresses the HTML with gzip
correct: 1
explain: HTMLRewriter is a streaming parser. The Worker handles elements one at a time as they flow past, so there's no buffering, no DOM, no re-serialization. The latency cost is tiny compared to rewriting a whole document.
```
