Pomerium → Cloudflare Access

Pomerium ↔ Cloudflare Access: integration to migration path.

An identity-aware proxy is load-bearing — break it and every protected app rejects every user at once. So we never flip a switch. Pomerium goes live behind Cloudflare Access first, sits in-path validating the edge's assertion, and only then takes over the policy decision app by app against a parallel proxy. No forced re-login, no cutover day, and every app rolls back in minutes.

Both products are identity-aware reverse proxies, so this is a per-request authorisation swap, not a network re-tunnel — the honest exceptions are the Cloudflare-only edge features (anycast PoPs, Browser Isolation, WARP posture, Gateway DNS) we name below before you commit.

The idea

Decide behind the edge first. Retire the edge last.

The topology that makes this zero-downtime is the sandwich: Cloudflare Access keeps terminating TLS at its anycast edge and injecting the Cf-Access-Jwt-Assertion, while Pomerium deploys at your origin, re-validates that JWT against Cloudflare's JWKS, and runs its own per-request policy before forwarding upstream. Both halves authenticate against the same IdP — one user population, two OIDC clients emitting the same group claim. Pomerium starts observe-only, then per-app we translate each Cloudflare rule into a cel-go policy, shadow it for a week, and flip it to enforce while the edge still enforces too. Only once Pomerium owns every decision do we demote and remove the Cloudflare app — each app independent, each reversible. You never bet every app on one cutover.

The phases

Seven steps. Each one reversible.

0

Baseline & inventory

We document every Cloudflare Access app: hostname, upstream, current include/exclude/require policy, IdP claim shape, WARP-posture dependence, Browser Isolation enforcement, Service Token consumers, mTLS-to-origin status, and whether each app is public or internal. Read-only, via the CF API.

Users see: No user impact.

Rollback: N/A

1

Pomerium goes live behind the edge

Pomerium stands up in HA — three-plus replicas, databroker on Postgres or Redis — as an OIDC client at the same IdP Cloudflare Access already uses. A canary route serves a no-traffic test app. No production app trusts Pomerium yet.

Users see: None. Canary login proves the path before production depends on it.

Rollback: Decommission Pomerium — no app routes through it.

2

Sandwich Pomerium behind CF Access

Selected apps' upstream is cut to traverse Pomerium first. Pomerium validates the Cf-Access-Jwt-Assertion against Cloudflare's JWKS and runs a permissive observe-only policy. Cloudflare still terminates TLS at the edge and still enforces — Pomerium is in-path but not yet deciding.

Users see: None — users still sign in once at the Cloudflare edge.

Rollback: Revert the origin load-balancer target to the upstream. Under 15 minutes.

3

Translate CF policy to Pomerium

Per app, in low/medium/high blast-radius waves, each CF include/exclude/require rule is translated to a cel-go policy.allow/deny. We run it in shadow mode for 7 days, diff CF denies against Pomerium would-denies until divergence is zero, then flip Pomerium to enforce while CF still enforces as defence in depth.

Users see: None when translation is correct — the inner decision is now Pomerium's.

Rollback: Revert the Pomerium policy to permissive. Under 15 minutes.

4

Demote CF Access to identity-only

The Cloudflare Access policy for migrated apps is reduced to a single rule — any valid IdP login. Cloudflare still terminates TLS and injects the assertion, but no app-specific authorisation lives at the edge anymore. Pomerium owns the decision.

Users see: None — Cloudflare becomes an authentication checkpoint, not an authoriser.

Rollback: Re-deploy the saved CF ruleset, kept disabled in Terraform. Under 15 minutes.

5

Cut internal apps off the edge

Each internal-only app is confirmed reachable at the Pomerium proxy directly. We move DNS from Cloudflare's orange-cloud to grey-cloud pointing at the Pomerium load balancer, delete the CF application, and bake 14 days. Pomerium is now the sole identity-aware proxy for those apps.

Users see: The login form changes from Cloudflare-branded to Pomerium-branded — communicated per app or in bulk.

Rollback: Re-point DNS to orange-cloud and re-create the CF app from Terraform. Under 30 minutes including DNS TTL.

6

Final retirement (partial)

Cloudflare Access applications are removed for migrated apps — internal definitely, public depending on the SaaS-only decisions below. Cloudflare may still front public hostnames as CDN, WAF and DDoS without an Access app, and WARP, Browser Isolation and Gateway DNS can stay deployed independently. The Zero Trust SKU is downgraded to whatever covers what remains.

Users see: None for users on migrated apps.

Rollback: Re-create deleted apps from Terraform within the 30-day window.

Feature parity

Where Pomerium matches Cloudflare Access — and where it can't.

CapabilityPomeriumCloudflare AccessParity
Transport / proxy model Identity-aware reverse proxy (per-request authn+authz on HTTP/gRPC) Identity-aware reverse proxy at anycast edge At parity
Per-app ZTNA Pomerium route + cel-go policy.allow/policy.deny CF Access application + include/exclude/require policy At parity
Identity federation OIDC client (idp_provider), groups claim OIDC/SAML IdP, Cf-Access-Jwt-Assertion At parity
Device posture Consumes IdP/MDM claim or mTLS device cert; no first-party agent WARP-reported OS+patch+EDR+disk-enc, require device posture: compliant SaaS only
ACL policy model cel-go (method/path/header/time predicates, claim arithmetic, OPA) include/exclude/require over identity + posture + country At parity
Global edge / PoP Self-hosted single-binary (in-cluster/VPC/VM), GeoDNS multi-region 300+ PoP anycast edge, sub-50ms TLS termination SaaS only
Anycast DDoS absorption None Anycast volumetric absorption at edge SaaS only
DNS / SWG None native WARP + Gateway DNS + DNS filter bundle SaaS only
Browser isolation None CF Browser Isolation (RBI) SaaS only
Signing / key custody X-Pomerium-Jwt-Assertion signed under your KMS Cf-Access-Jwt-Assertion, JWKS on cloudflareaccess.com OSS only
mTLS to upstream Per-route tls_client_cert_file Authenticated Origin Pulls (account-coarse) Partial
Session state / audit Databroker (in-memory/Redis/Postgres), structured authorize logs Edge session state, CF Logs (Logpush) At parity
M2M / service auth Per-route API key or IdP client-credentials JWT CF Service Tokens (HMAC client-id/secret) At parity
Cost model Self-hosted compute + ops, no per-user licence Per-user CF Zero Trust SKU Partial
Compliance Boundary inside your SSP (KMS, patch, IR) Inherited CF SOC 2 / FedRAMP ATO SaaS only

What we're honest about

The caveats most vendors leave out.

No global anycast edge

Cloudflare terminates TLS at 300-plus PoPs within sub-50ms of most users; Pomerium is self-hosted and has no anycast fabric. Public users far from your origin will see higher P95 latency. We either keep Cloudflare as a dumb orange-cloud edge (CDN + WAF, no Access app) in front of Pomerium, deploy Pomerium multi-region behind GeoDNS, or quantify and accept the hit — we decide this per app, not by hand-waving.

Device posture is WARP's asymmetric advantage

Cloudflare Access enforces device posture from the WARP agent — OS, patch level, EDR, disk encryption — against a vendor-curated signal catalogue. Pomerium has no first-party endpoint agent. We replace it with an IdP-emitted compliance claim, an mTLS device cert as a posture gate, or an OPA sidecar that calls your MDM — and if posture is load-bearing for an app, we do not retire CF Access for it until that is in place. This is the highest-risk gap in the migration.

Browser Isolation, Gateway DNS and Page Shield don't move

Cloudflare Browser Isolation, the WARP + Gateway DNS filtering bundle, and Page Shield's client-side supply-chain monitoring are edge properties with no Pomerium equivalent. Each one is an explicit retain-on-CF, replace-with-a-named-workstream (Menlo, Talon or Island for isolation; NextDNS or Pi-hole for DNS), or accept-the-gap decision — recorded and signed off by the control owner, never quietly dropped.

You own uptime, JWKS custody and the compliance boundary

With Pomerium authoritative there is no managed-service backstop — if Pomerium is down every protected app is down. The flip side is signing custody: the X-Pomerium-Jwt-Assertion is signed under your KMS, so we must pin Pomerium's JWKS at the upstream and retire the CF JWKS, or a stray CF token bypasses policy. We run it HA across replicas and a multi-AZ databroker, with a break-glass route, a tested DR restore, and the new self-hosted controls mapped into your SSP.

Why this beats a flag day

Reversible per app. Soaked before cancellation.

Every per-app step rolls back in under 15 minutes — Phases 2 through 4 reverse with a load-balancer flip or a Pomerium policy revert, and even the DNS cut in Phase 5 backs out in under 30 minutes once the TTL drains. And the Cloudflare Access applications are never deleted on faith: each migrated app soaks for at least 14 days with its CF ruleset kept disabled in Terraform, and we require a minimum 30-day clean window — Pomerium enforcing, zero CF enforcement events — before any Zero Trust SKU is downgraded. If anything regresses you re-create the app from Terraform inside the window instead of rebuilding policy from scratch.

See whether your apps migrate cleanly.

A 30-minute call with a senior identity engineer. We inventory your Cloudflare Access apps by claim shape, flag the WARP-posture, Browser Isolation and anycast-edge dependencies that need a replacement plan, and tell you honestly where Pomerium's policy engine wins and where Cloudflare's edge still earns its seat — before you commit.

Map my migration →