1
0 Comments

How to Find Companies that are paying Cloudflare

If you're building a B2B product as a solo founder or small team, your hardest problem isn't writing code. It's finding the right companies to pitch - ones that actually have budget, actually use real tooling, and actually pay for software instead of duct-taping free tiers together.

Here's a tactic that almost nobody outside of the GTM-data world is using: you can identify companies that pay Cloudflare just by inspecting them from the outside. Free vs. Pro vs. Business vs. Enterprise. No scraping LinkedIn, no buying lists from Apollo, no waiting for inbound.

Why does that matter for an indie hacker? Because a company that's paying $25/mo for Cloudflare Pro - let alone six figures for Enterprise - has already self-identified as:

  1. Technically literate (someone configured a CDN, a WAF, an SSO setup)
  2. Comfortable spending real money on infrastructure
  3. Likely to have a budget for your B2B tool too

That's a pre-qualified lead list, hiding in plain sight in DNS records and HTTP responses. Below is the full playbook, ranked from "easiest to detect" to "almost certainly an Enterprise customer." Code samples are in JavaScript (Node.js) - paste them into Claude or whatever LLM you use and translate to your stack if you prefer Python or Go. They assume standard Node libraries (dns/promises, node:tls, fetch) plus whois-json for the BGP signal at the end.

Why use Cloudflare data for lead gen?

Roughly 20% of the public web sits behind Cloudflare. That's millions of domains. Cloudflare itself publishes the Radar top million by traffic, which is essentially a free starter dataset.

The trick is filtering. Cloudflare deliberately makes paid and free customers look identical in response headers - privacy reasons, mostly. But "deliberately obscured" isn't the same as "undetectable," and the leaks are exactly what you want as a founder doing outbound.

Concrete ways to use the resulting list:

  • Cold outreach for B2B SaaS - paying Cloudflare customers are far more likely to convert than random ICP-shaped domains scraped from a directory.
  • Niche product validation - building something for "companies running internal tooling behind Zero Trust"? You can literally enumerate them.
  • Freelance/agency work - DevOps consultants, security auditors, performance engineers can target companies clearly running serious infra.
  • Competitive intel - figuring out who your competitors' customers are when those customers tend to use Cloudflare.

Now the techniques.

Step 0: Are they on Cloudflare at all?

The cheapest filter. Cloudflare publishes its full IP ranges at cloudflare.com/ips. Resolve a domain's A records, check if any IP falls in those ranges, or check if the nameservers contain "cloudflare." Done.

import dns from "node:dns/promises";

async function onCloudflare(domain) {
  try {
    const aRecords = await dns.resolve4(domain);
    if (aRecords.some(ip => isInCloudflareRange(ip))) return true;
  } catch {}

  try {
    const nsRecords = await dns.resolveNs(domain);
    return nsRecords.some(ns => ns.toLowerCase().includes("cloudflare"));
  } catch {
    return false;
  }
}

Run this against the Radar top million and you have a couple hundred thousand Cloudflare-using domains in an afternoon. The problem: most of them are blogs, parked domains, hobby projects, mom-and-pop sites. Signal-to-noise is bad. The interesting filters come next.

Signal 1: Dashboard SSO TXT records - "they take Cloudflare seriously"

Cloudflare lets you wire your team's dashboard login into a SAML provider (Okta, Azure AD, etc.). Setup requires a TXT record like:

cloudflare_dashboard_sso=1111111
import dns from "node:dns/promises";

async function hasDashboardSso(domain) {
  try {
    const txtRecords = await dns.resolveTxt(domain);
    return txtRecords.some(record =>
      record.join("").includes("cloudflare_dashboard_sso=")
    );
  } catch {
    return false;
  }
}

SSO used to be Enterprise-only - it isn't anymore - so this isn't a hard paid-customer signal. But nobody on a free plan is going to spend an afternoon configuring SAML for a dashboard they barely log into. The friction self-selects. If you see this TXT record, the company is using Cloudflare deliberately, with multiple humans, inside a real identity stack.

Indie hacker use: decent first filter to cut Radar's million domains down to "real organizations." Combine with later signals.

Signal 2: __cf_bm and _cfuvid cookies - bot defense and rate limiting

Cloudflare drops specific cookies depending on which products are enabled.

  • __cf_bm - appears when Bot Management, Bot Fight Mode, or Super Bot Fight Mode is on. The full Bot Management product is Enterprise-only. Super Bot Fight Mode is Pro+.
  • _cfuvid - set when a site uses unique-visitor tracking in WAF rate limiting rules. Available on any plan, but it implies someone actively wrote rate-limiting rules instead of leaving defaults.
function cookieSignals(response) {
  const cookies = response.headers.get("set-cookie") || "";
  return {
    botManagement: cookies.includes("__cf_bm="),
    advancedRateLimiting: cookies.includes("_cfuvid="),
  };
}

Soft signals - neither guarantees a paying customer - but combined with the rest, they help.

Signal 3: Custom error pages - first hard "they're paying" signal

This is the fun one, because you can finally say with high confidence "yes, this company is paying Cloudflare."

Default Cloudflare error pages have known fingerprints (Attention Required! | Cloudflare, _cf_chl_opt, cf-error-details). But Pro plan and above ($25/mo) lets customers replace these with their own branded versions. A lot of paying customers do, because the default pages look ugly next to their brand.

The detection trick: Cloudflare always injects a cf-ray header (a per-request unique ID the customer's origin server can't possibly know). When a custom error page is rendered at Cloudflare's edge, that Ray ID also ends up in the response body. So:

If the Ray ID from the header appears in the body, and the body doesn't match any default Cloudflare template, it's a custom error page from a paying customer.

const CLOUDFLARE_DEFAULT_MARKERS = [
  "Attention Required! | Cloudflare",
  "_cf_chl_opt",
  "cf-error-details",
  "__CF$cv$params",
  "/cdn-cgi/challenge-platform/scripts/jsd/main.js",
];

async function hasCustomErrorPage(url) {
  const response = await fetch(url);
  if (response.status < 400 || response.status >= 600) return false;

  const server = response.headers.get("server") || "";
  if (!server.toLowerCase().includes("cloudflare")) return false;

  const cfRay = response.headers.get("cf-ray");
  if (!cfRay) return false;
  const rayId = cfRay.split("-")[0];

  const body = await response.text();
  if (!body.includes(rayId)) return false;

  return !CLOUDFLARE_DEFAULT_MARKERS.some(marker => body.includes(marker));
}

To trigger an error page without being a jerk, hit a non-existent API endpoint like https://api.example.com/api/v1/zzzz_definitely_not_real. APIs reliably 4xx on garbage paths. Stay away from probing /wp-admin or /.env at scale - that'll get your IP flagged fast.

When this is run across a few thousand api.* subdomains, the hits are exactly what you'd expect: regulated finance, central banks, large consumer brands. High-budget, high-signal targets.

Signal 4: Cloudflare Access - companies running internal tooling

Cloudflare Access is the Zero Trust product. Companies use it to put auth in front of internal tools - GitLab, Grafana, Jenkins, Notion, admin panels, and increasingly MCP servers in 2026.

When you hit an Access-protected URL without a session, Cloudflare redirects to the customer's *.cloudflareaccess.com team subdomain. That redirect is the whole signal.

async function hasCloudflareAccess(url) {
  const response = await fetch(url, { redirect: "manual" });
  if (![301, 302, 303, 307, 308].includes(response.status)) return false;

  const location = response.headers.get("location") || "";
  return location.includes("cloudflareaccess.com");
}

The art is guessing which subdomains to probe. Internal-tooling subdomain naming is shockingly predictable:

gitlab.<domain>
grafana.<domain>
jenkins.<domain>
wiki.<domain>
internal.<domain>
admin.<domain>
mcp.<domain>   // increasingly relevant in 2026

Most won't resolve. The ones that do, hit and check the redirect.

Indie hacker angle: if you're selling DevOps, internal tooling, observability, or anything MCP-adjacent, this is a goldmine. Anyone who's wired up Access has done real engineering and probably has budget for adjacent tooling. Access has a free tier (50 users), so this isn't strictly Enterprise - but the correlation with paid signals is high.

Signal 5: OV or EV SSL certificates - almost certainly Enterprise

This one's underrated.

SSL certs come in three validation tiers:

  • DV (domain-validated): proves you control the domain. Free everywhere, including Cloudflare's Universal SSL.
  • OV (organization-validated): the CA verifies your company is a real registered entity - business documents, phone calls, the works. Costs money. Embeds your legal company name into the cert.
  • EV (extended validation): even stricter. Used to give you the green address bar (browsers killed it). Still required by compliance regimes in finance, healthcare, government.

Here's the kicker: Cloudflare cannot issue OV or EV certificates. Universal SSL only does DV. So if a domain is being served from Cloudflare's IPs and its cert is OV or EV, the company had to upload their own commercial cert via Cloudflare's Custom Certificates feature - which requires the Business plan minimum ($200/month per domain) and is in practice almost exclusively used by Enterprise customers.

import tls from "node:tls";

function fetchTlsCert(host) {
  return new Promise((resolve, reject) => {
    const socket = tls.connect(
      { host, port: 443, servername: host },
      () => {
        const cert = socket.getPeerCertificate(true);
        socket.end();
        resolve(cert);
      }
    );
    socket.on("error", reject);
  });
}

function certTier(cert) {
  const subject = cert.subject || {};
  const policyOids = (cert.infoAccess?.policies || []).join(",");

  const hasEvFields = subject.businessCategory ||
                      subject.serialNumber ||
                      subject.jurisdictionCountryName;

  if (hasEvFields || policyOids.includes("2.23.140.1.1")) return "EV";
  if (subject.O && policyOids.includes("2.23.140.1.2.2")) return "OV";
  return "DV";
}

async function hasPaidCert(domain) {
  const aRecords = await dns.resolve4(domain).catch(() => []);
  if (!aRecords.some(ip => isInCloudflareRange(ip))) return false;

  const cert = await fetchTlsCert(domain);
  return ["OV", "EV"].includes(certTier(cert));
}

Hit rate on actual paying customers is around 80–90%. This is one of the strongest, cleanest signals in the playbook.

Signal 6: Static IPs - Enterprise-only, requires aggregate data

Cloudflare is anycast by default - one IP serves thousands of unrelated domains. If two random companies both resolve to 104.21.3.47, that's normal. The IP belongs to Cloudflare, not them.

Static IPs are an Enterprise feature where Cloudflare allocates dedicated IPs to a single customer. Common when the customer's clients need to whitelist a stable IP for firewall rules - a lot of fintech and B2B SaaS does this. The Cloudflare docs literally say "contact your account team," which is Enterprise-speak.

You can't detect this on a single domain. You build a frequency map across all your Cloudflare-resolved domains:

async function findStaticIpCandidates(allCloudflareDomains) {
  const ipToDomains = new Map();

  for (const domain of allCloudflareDomains) {
    const aRecords = await dns.resolve4(domain).catch(() => []);
    for (const ip of aRecords) {
      if (!isInCloudflareRange(ip)) continue;
      if (!ipToDomains.has(ip)) ipToDomains.set(ip, []);
      ipToDomains.get(ip).push(domain);
    }
  }

  return Object.fromEntries(
    [...ipToDomains.entries()].filter(([, domains]) => domains.length <= 3)
  );
}

Shared anycast IPs appear on tens of thousands of domains. Static IPs appear on one or two (apex + www). Bonus corroboration: Static IPs come in sequential blocks, so a domain with A records like 104.20.39.237 and 104.20.40.237 (sequential third octet) is almost certainly Enterprise.

Signal 7: Secondary DNS - clean, simple, Enterprise-only

This one's the easiest hard-Enterprise signal in the whole list.

Most Cloudflare customers let Cloudflare host their DNS entirely (ns1.cloudflare.com, ns2.cloudflare.com). Standard, available on any plan.

Secondary DNS is different. The customer keeps their own primary DNS, and Cloudflare runs as a secondary authoritative server, syncing zones via AXFR/IXFR. It's a resilience play for organizations whose DNS going down would be a Big Deal - banks, government, infrastructure.

The NS records expose it immediately:

ns1.company.com
ns2.company.com
ns0227.secondary.cloudflare.com
ns0022.secondary.cloudflare.com

The *.secondary.cloudflare.com pattern only appears on Secondary DNS customers.

async function hasCloudflareSecondaryDns(domain) {
  try {
    const nsRecords = await dns.resolveNs(domain);
    return nsRecords.some(ns => ns.includes("secondary.cloudflare.com"));
  } catch {
    return false;
  }
}

Per Cloudflare's DNS feature matrix, Secondary DNS requires the Foundation DNS add-on on top of Enterprise - a deliberate upsell for customers treating DNS as critical infrastructure. A scan of the top million surfaces names like JP Morgan Chase and the IRS. If your product is enterprise-grade infra, this is your list.

Signal 8: Magic Transit (BGP) - Enterprise, no exceptions

This one's the cleanest signal in the entire playbook, but it requires looking at BGP routing data, not the domain itself.

Magic Transit is Enterprise-only. Customers bring their own IP prefixes, and Cloudflare announces them to the internet from AS13335, scrubbing DDoS traffic upstream of the customer's network.

The detection: query every prefix being announced by AS13335, then cross-reference WHOIS to find prefixes owned by non-Cloudflare entities. Those are Magic Transit customers.

import whois from "whois-json";

const NOISE_PATTERNS = ["ip-ripe", "ip manager", "-mnt"];

function isNoise(name) {
  if (!name) return true;
  const lower = name.toLowerCase();
  return NOISE_PATTERNS.some(p => lower.includes(p));
}

async function getAs13335Prefixes() {
  const r = await fetch(
    "https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS13335"
  );
  const data = await r.json();
  return data.data.prefixes;
}

async function findMagicTransitCustomers(prefixes) {
  const customers = [];

  for (const p of prefixes) {
    const ip = p.prefix.split("/")[0];
    const org = await whois(ip).catch(() => null);
    const orgName = org?.orgName || org?.netname;

    if (!orgName || isNoise(orgName)) continue;

    customers.push({
      prefix: p.prefix,
      name: orgName,
      bgpview: `https://bgpview.io/prefix/${p.prefix}`,
    });
  }

  return customers;
}

You can sanity-check the prefix list manually at bgp.he.net/AS13335.

Filter out:

  • ISPs and hosting providers (netnames containing ISP, HOSTING, TELECOM, CARRIER, DATACENTER)
  • Placeholder WHOIS records (IP-RIPE, IP Manager, *-MNT handles)
  • IP-leasing companies like IPXO (tell: mnt-lower: IPXO-MNT)

What's left is unambiguously Enterprise. Sample names from a recent scan: Shopify, AstraZeneca, University of Sydney, Google. Magic Transit requires committed bandwidth, a signed contract, and a BGP session with Cloudflare's network team. You don't get this by accident.

Two things to watch out for

Hosting platform contamination. A lot of platforms route customer traffic through Cloudflare by default. Kinsta runs every WordPress site through Cloudflare Enterprise; the actual tenant didn't choose it and might not know it's there. WP Engine, Pantheon, others do similar things. This is exactly why detecting paid signals matters more than detecting Cloudflare at all - custom error pages, OV certs, Access redirects, Magic Transit all filter out the platform-default noise.

Don't be reckless probing at scale. WAF-triggering patterns (SQLi-style payloads, /wp-admin, /.env) across thousands of domains will get your scanner IP flagged immediately. Stick to benign probes - non-existent paths, normal User-Agents, standard request structure. The signals above mostly need nothing aggressive.

Putting it together: a practical workflow for indie hackers

Here's how I'd actually use this if I were doing outbound for a B2B product tomorrow:

  1. Pull the Radar top million as your starting corpus.
  2. Filter to Cloudflare-using domains with the Step 0 check. You'll have ~200k.
  3. Apply 2–3 paid signals depending on your ICP:
    • Selling DevOps/internal tooling? → Cloudflare Access (Signal 4).
    • Selling to mid-market and up? → Custom error pages (Signal 3) + OV certs (Signal 5).
    • Selling enterprise infra/security? → Secondary DNS (Signal 7) + Magic Transit (Signal 8).
  4. Enrich with company name (from WHOIS / cert subject) and domain. Your remaining list might be 500–5,000 names.
  5. Cross-reference with LinkedIn or Apollo to find the right person, then run outbound.

Most indie hackers buy a list, get a 1% reply rate, and quit on outbound. A list filtered through these signals is dramatically denser - every domain on it is a company with budget and technical maturity. Your reply rate goes up because you're actually pitching the right people.

The broader takeaway isn't really about Cloudflare. It's that the public internet leaks an enormous amount of buying-intent and budget data if you know where to look. DNS records, TLS certs, response headers, BGP announcements - all of it is free, all of it is queryable at scale, and almost no one in the indie hacker world is using it. Whoever automates this for their niche first gets a real edge.

on May 7, 2026
Trending on Indie Hackers
Agencies charge $5,000 for a 60-second product demo video. I make mine for $0. Here's the exact workflow. User Avatar 147 comments I've been building for months and made $0. Here's the honest psychological reason — and it's not what I expected. User Avatar 140 comments This system tells you what’s working in your startup — every week User Avatar 40 comments 11 Weeks Ago I Had 0 Users. Now VIDI Has Reviewed $10M+ in Contracts - and I’m Opening a Small SAFE Round User Avatar 19 comments I built a health platform for my family because nobody has a clue what is going on User Avatar 15 comments Why Direction Matters More Than Motivation in Exam Preparation User Avatar 14 comments