# Put a CDN in front

Everything here runs in one site, in Los Angeles. For an HTTP service with
readers anywhere else, a CDN is the cheapest thing you can do about that: it
answers from a point of presence near the reader, and only the requests it
cannot answer travel to Los Angeles. Cloudflare below.

## What it fixes, and what it does not

**Fixes:**

- **Distance, for anything cacheable.** Static assets, images and any response
  you let it cache are served from the reader's continent.
- **Egress cost.** Egress is metered per GB and inbound is free, so a request
  the edge answers never reaches the meter.
- **The origin address.** A proxied record publishes the CDN's addresses, not
  your VM's, which is what makes the firewall step below worth doing.
- **Recreates.** With the public name pointed at the CDN, replacing the VM is
  an origin-address change behind it rather than a DNS cutover clients have to
  observe.

**Does not fix:**

- **Dynamic requests.** An uncacheable response is still computed in Los
  Angeles, so a logged-in reader in Frankfurt still pays that round trip. The
  connection setup is faster; the answer is not.
- **Availability.** This is not a second region. If the VM is down, cached
  objects may still serve for a while and everything else is down with it.
- **Anything that is not HTTP.** SSH, SMTP and raw TCP are not proxied on the
  ordinary plans.

## The steps

**1. Move DNS, and point the public name at the VM.** An `A` record for your
public hostname holding the VM's IPv4 (`GET /v1/vms/{id}` carries it),
proxied — the orange cloud. Proxied is what puts the CDN in the path; a record
that merely resolves does nothing.

**2. Keep a way in that is not proxied.** SSH does not go through the proxy,
so settle now how you will reach port 22 — a second hostname left unproxied,
or the raw address recorded somewhere. Step 4 locks out a machine that has no
second path in, and [there is no console](../vm.md#access-is-ssh-and-only-ssh).

An unproxied record republishes the origin address. If that matters, keep SSH
on the raw address and restrict port 22 to where you connect from.

**3. Make origin TLS real.** Put a certificate on the VM — the CDN's own
origin certificate is free and long-lived — and set the TLS mode to full
verification. The permissive mode carries the origin leg in plaintext across
the public Internet.

**4. Lock the origin to the CDN.** Nothing of ours filters ahead of you, so
until you do this the VM still answers anyone who finds its address. Allow
inbound 80 and 443 from the CDN's published ranges only, keep 22 reachable
from where you connect, default-deny the rest.

Apply it behind a scheduled revert, and fetch the ranges at apply time rather
than pasting a copy that ages: [Harden a VM](harden-your-vm.md).

**5. Decide what is cacheable.** Static assets cache by default; HTML and API
responses do not until you say so. Set cache rules for the paths that can take
it, send `Cache-Control` from your own application for the rest, and check the
`cf-cache-status` response header — `HIT` is the only value that means any of
this is working.

**6. Fix your logs.** Every request now arrives from a CDN address, so
in-guest logs, rate limits and anything that blocks by IP see the proxy
instead of the reader. The real client address comes in a request
header (`CF-Connecting-IP`); configure your web server's real-IP module to
trust it, from the CDN ranges only.

## What goes wrong

- **The permissive TLS mode.** The padlock appears while the origin leg is
  plaintext. Verify the origin certificate instead.
- **`cf-cache-status: DYNAMIC` on everything.** No cache rules are matching,
  so the edge is a proxy that has added a hop and removed nothing.
- **A stale origin address.** [Recreating a VM](recreate-vm.md) gives you a
  new address; update the origin record or the site goes down at the moment
  the old machine is deleted.
- **Large uploads rejected.** There is a request-body ceiling that varies by
  plan. Check yours before routing an upload endpoint through the proxy, and
  send those to an unproxied hostname if it does not fit.
- **Mail does not go through this.** Outbound `:25` is blocked at the network
  either way: [Email relay](../email.md).
