Fix blank page: add Cloudflare-fronted HTTP-only Caddyfile #4

Merged
ahmza merged 3 commits from fix-caddy-cloudflare into main 2026-07-25 03:56:08 +00:00
Owner

Problem

The live site was returning a blank page. The Caddy access log showed:

"msg":"NOP", ... "status":0, "size":0, "bytes_read":0
"server is listening only on the HTTP port, so no automatic HTTPS will be applied"

The live Caddy site block had no responder (no reverse_proxy), so requests fell through to a NOP and returned nothing. This also explained the earlier performance findings: the origin was serving everything uncompressed and with no security headers, because Caddy wasn't running the encode/header/reverse_proxy directives.

Root cause

Actual production topology is Cloudflare → Caddy (HTTP :80) → SWS, but there was no committed, correct Caddyfile for it — the repo only documented the DNS-01 (Caddy-terminates-TLS) variant.

Fix

Add a known-good, committed Caddyfile at the repo root for the Cloudflare-fronted, HTTP-only topology:

  • reverse_proxy blog:80 — the missing responder that caused the blank page
  • encode zstd gzip — fixes the no-compression problem (HTML ~35 KB → ~6 KB gzipped, CSS ~102 KB → ~16 KB, etc.)
  • Security headers — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy; drops the Server header
  • http:// scheme so Caddy serves plain HTTP and does not try to provision TLS (Cloudflare handles HTTPS)
  • trusted_proxies static <Cloudflare ranges> (15 IPv4 + 7 IPv6 from cloudflare.com/ips-v4 and /ips-v6) + client_ip_headers Cf-Connecting-Ip X-Forwarded-For — real visitor IPs are honoured only for genuine Cloudflare requests, preventing header spoofing

Also updates docs/caddy-reverse-proxy.md with a "Deployment topologies" overview and a dedicated Cloudflare-fronted section, labelling the existing Compose/K8s examples as the DNS-01 alternative.

Verification

  • caddy validate + caddy fmt clean against the caddy-cf image; all 22 Cloudflare ranges parse into the adapted JSON.
  • End-to-end runtime test (Caddy → SWS on a shared docker network): Caddy starts cleanly, serves HTTP 200 with real content (<title>ahmza's ramblings</title>), Content-Encoding: gzip, and X-Frame-Options: SAMEORIGIN.

Deploy note

This fixes prod only once deployed to the caddy_cf host — the repo commit alone doesn't update the running proxy:

docker exec caddy_cf caddy reload --config /etc/caddy/Caddyfile

The reverse_proxy blog:80 resolves because Caddy and the blog container share a Docker network with the blog service named blog.

## Problem The live site was returning a **blank page**. The Caddy access log showed: ``` "msg":"NOP", ... "status":0, "size":0, "bytes_read":0 "server is listening only on the HTTP port, so no automatic HTTPS will be applied" ``` The live Caddy site block had **no responder** (no `reverse_proxy`), so requests fell through to a `NOP` and returned nothing. This also explained the earlier performance findings: the origin was serving **everything uncompressed** and with **no security headers**, because Caddy wasn't running the `encode`/`header`/`reverse_proxy` directives. ## Root cause Actual production topology is **Cloudflare → Caddy (HTTP :80) → SWS**, but there was no committed, correct Caddyfile for it — the repo only documented the DNS-01 (Caddy-terminates-TLS) variant. ## Fix Add a known-good, committed `Caddyfile` at the repo root for the Cloudflare-fronted, HTTP-only topology: - **`reverse_proxy blog:80`** — the missing responder that caused the blank page - **`encode zstd gzip`** — fixes the no-compression problem (HTML ~35 KB → ~6 KB gzipped, CSS ~102 KB → ~16 KB, etc.) - **Security headers** — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy; drops the `Server` header - **`http://` scheme** so Caddy serves plain HTTP and does not try to provision TLS (Cloudflare handles HTTPS) - **`trusted_proxies static <Cloudflare ranges>`** (15 IPv4 + 7 IPv6 from cloudflare.com/ips-v4 and /ips-v6) + `client_ip_headers Cf-Connecting-Ip X-Forwarded-For` — real visitor IPs are honoured only for genuine Cloudflare requests, preventing header spoofing Also updates `docs/caddy-reverse-proxy.md` with a "Deployment topologies" overview and a dedicated Cloudflare-fronted section, labelling the existing Compose/K8s examples as the DNS-01 alternative. ## Verification - `caddy validate` + `caddy fmt` clean against the `caddy-cf` image; all 22 Cloudflare ranges parse into the adapted JSON. - End-to-end runtime test (Caddy → SWS on a shared docker network): Caddy starts cleanly, serves **HTTP 200** with real content (`<title>ahmza's ramblings</title>`), **`Content-Encoding: gzip`**, and **`X-Frame-Options: SAMEORIGIN`**. ## Deploy note This fixes prod only once deployed to the `caddy_cf` host — the repo commit alone doesn't update the running proxy: ```bash docker exec caddy_cf caddy reload --config /etc/caddy/Caddyfile ``` The `reverse_proxy blog:80` resolves because Caddy and the blog container share a Docker network with the blog service named `blog`.
ahmza added 3 commits 2026-07-25 03:29:54 +00:00
The live Caddy was returning a blank page (NOP, status 0) because its
site block had no responder \u2014 no reverse_proxy to the blog. This adds a
known-good HTTP-only Caddyfile matching the actual topology
(Cloudflare terminates TLS -> Caddy :80 -> SWS blog:80):

- reverse_proxy blog:80  (the missing responder that caused the blank page)
- encode zstd gzip        (fixes: origin was serving everything uncompressed)
- security headers        (fixes: HSTS/X-Frame-Options/etc were absent)
- trusted_proxies + client_ip_headers for correct Cloudflare visitor IPs
- http:// scheme so Caddy does not try to provision TLS (Cloudflare does)

Validated + fmt-clean against the caddy-cf image; tested end-to-end
locally (Caddy -> SWS) returning HTTP 200 with gzip and headers.
Replace 'private_ranges' with Cloudflare's official published edge ranges
(15 IPv4 + 7 IPv6 from cloudflare.com/ips-v4 and /ips-v6). This ensures
Cf-Connecting-Ip / X-Forwarded-For are only honoured for requests that
genuinely originate from Cloudflare, preventing client-IP header spoofing
from any other source.

Validated + fmt-clean and tested at runtime against the caddy-cf image:
Caddy starts cleanly with all 22 ranges, serves HTTP 200 with gzip and
security headers.
The live production setup is Cloudflare -> Caddy (HTTP :80) -> SWS, which
differed from the DNS-01 topology the doc previously assumed. Add a
'Deployment topologies' overview and a dedicated Cloudflare-fronted
section pointing at the committed root Caddyfile, covering reverse_proxy,
encode, security headers, and Cloudflare trusted_proxies. Label the
existing Compose/K8s examples as the DNS-01 alternative.
ahmza merged commit 09780d9598 into main 2026-07-25 03:56:08 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ahmza/blog-ahmza-com#4