Cloudflare hardening reference
Curated guidance from Cloudflare's official documentation, tagged [F] = available on the Free plan, [P] = paid-only. Six areas: API tokens, DNS/DNSSEC/CAA, SSL/TLS, account hardening, API hygiene, and free-tier security features.
This page is generic best-practice guidance. It does not contain zone-specific settings, token values, or infrastructure references.
1. API tokens#
Cloudflare supports three credential types: Global API Key (legacy), Service Key, and API tokens. Service Key auth is being removed 2026-09-30 (deprecation notice, 2026-03-19). Default to API tokens for all new integrations.
Recommendations:
- Prefer API tokens over the legacy Global API Key. [F]
- Match the token type to the use case: user tokens for human-tied access; Account API tokens for service accounts not tied to a person. [F]
- Least-privilege scoping: grant only the specific zone(s) or account resources required, and select
ReadvsEditper permission group. [F] - Set a TTL so the credential expires automatically; a one-year TTL is a reasonable default. [F]
- Restrict by client IP when the caller's egress IP is stable and predictable. [F]
- Name tokens descriptively so audit and revocation are unambiguous. [F]
- Treat the token secret as write-once: it is only displayed at creation. Store it in a secrets manager; never in plaintext. [F]
- Detect leaks: new credentials carry a scannable prefix,
cfut_(user token),cfat_(account token), orcfk_(Global API Key), each followed by 40 characters and a checksum (token formats). [F] - Rotate by rolling the token via the Roll Token endpoint: the previous secret is invalidated while permissions are preserved. [F]
- Quotas: 50 user API tokens, 500 account API tokens per account (API limits). [F]
Source: Create API tokens
2. DNS, DNSSEC, and CAA#
DNSSEC#
- Enable DNSSEC under DNS → Settings and submit the DS record at your registrar. DNSSEC signing and validation are defined in RFC 4033, RFC 4034, and RFC 4035. Cloudflare calls Algorithm 13 (ECDSA Curve P-256 with SHA-256, RFC 6605) its "preferred cipher choice" and documents what to do when a registrar or TLD does not support it (Cloudflare DNSSEC). [F]
- Before migrating a signed zone to Cloudflare, remove the DS record at the registrar and wait for its TTL to expire (commonly 24 to 48 hours) before changing nameservers; switching while the old DS is still cached makes validating resolvers
SERVFAIL. If the old provider can host an external DNSKEY, multi-signer DNSSEC migrates with no insecure window instead. [F]
Once DNSSEC is enabled and the DS record is live at the parent TLD, a validating resolver returns the AD (Authenticated Data) bit (RFC 4035 section 3.2.3). You can verify this from the command line:
dig @1.1.1.1 +dnssec isitdns.net DNSKEY;; flags: qr rd ra ad; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
isitdns.net. 294 IN DNSKEY 256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIj... (ZSK)
isitdns.net. 294 IN DNSKEY 257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo... (KSK)
isitdns.net. 294 IN RRSIG DNSKEY 13 2 3600 ...The TTL column is the resolver's cache countdown and the RRSIG validity dates change as Cloudflare re-signs, so those values vary run to run. The ad flag in the flags line confirms the chain of trust from the root through .net to the zone is intact, as reported by the resolver. Cloudflare states "1.1.1.1 is a DNSSEC-validating resolver. On every query, it sends the DO (DNSSEC OK) flag to signal that it can accept signed responses" (1.1.1.1 FAQ). The AD bit is set by the resolver, not the authoritative server, so it is only trustworthy if your path to the resolver is trusted (e.g., DoT (RFC 7858) or DoH (RFC 8484) to a known validating resolver). An on-path attacker on an unencrypted channel can strip or forge the AD bit, and so can a local router that intercepts UDP/53 and answers on the resolver's behalf. See dot-doh for how to query over an encrypted channel. To check the DS record the parent TLD is serving:
dig @1.1.1.1 +short isitdns.net DS2371 13 2 2DF8E1D4E0855DF60F2A8B51C914F2F2866ED1E3344F74F9682DAC4D 42D18802Keytag 2371 (as of 2026-07-28; it changes when Cloudflare rolls the KSK), algorithm 13 (ECDSAP256SHA256), digest type 2 (SHA-256). The space in the digest is just how dig wraps long hex fields; it is a single 64-character SHA-256 string. If this record is absent, the chain is broken and validating resolvers will SERVFAIL queries for the zone.
nslookup can show DNSKEY and DS records (as
rdata_48andrdata_43) but cannot display the AD bit or RRSIG signatures. Usedigor the curl DoH method to confirm validation:curl -s -H 'accept: application/dns-json' 'https://cloudflare-dns.com/dns-query?name=isitdns.net&type=A'returns"AD": truewhen the zone validates.
See dnssec for the full trust chain model and dnssec-troubleshooting for debugging validation failures.
Proxied records and TTLs#
- Proxy A, AAAA, and CNAME records that serve web traffic. Keep verification, email, and auxiliary records DNS-only. [F]
- Proxied records carry a TTL of Auto, five minutes by default; only unproxied records take a custom TTL (DNS FAQ, enforce DNS-only). [F]
- CNAME flattening at the apex is what lets you put a CNAME at the zone apex at all; flattening every CNAME record in the zone is a paid-account option ("Paid accounts can choose to flatten all CNAME records on their domain", CNAME flattening). [F apex / P all records]
CAA records#
- Cloudflare auto-injects CAA records for the CAs backing Universal SSL, but only once the zone has at least one CAA record of its own (a zone with no CAA records serves none, which permits any CA per RFC 8659 section 3). Cloudflare's currently documented set is
letsencrypt.org,pki.goog,sectigo.com, andssl.com;comodoca.comanddigicert.compersist from an earlier set on older zones, this one included (re-verified over DoH 2026-08-16). Injected records appear indigoutput but not in the dashboard. Cloudflare states "this list is not exhaustive, and other CAs might be added or removed for operational reasons", so check your zone's live CAA response to see what is actually in effect. [F] - CAA also applies on CNAME targets: the CA's lookup chases aliases and climbs the name tree (RFC 8659 section 3), so verify CAA at every link of a CNAME chain. [F]
- Add an
iodefclause (0 iodef "mailto:security@example.com", RFC 8659 section 4.4) so misissuance reports have a return address. [F]
You can see the auto-injected records on any Cloudflare-hosted zone:
dig @1.1.1.1 +short isitdns.net CAA0 issue "comodoca.com"
0 issue "digicert.com; cansignhttpexchanges=yes"
0 issue "letsencrypt.org"
0 issue "pki.goog; cansignhttpexchanges=yes"
0 issue "sectigo.com"
0 issue "ssl.com"
0 issuewild "comodoca.com"
0 issuewild "digicert.com; cansignhttpexchanges=yes"
0 issuewild "letsencrypt.org"
0 issuewild "pki.goog; cansignhttpexchanges=yes"
0 issuewild "sectigo.com"
0 issuewild "ssl.com"Not everything in that response is a zone record. As of 2026-07-28 the dashboard for this zone holds eight CAA records at the apex: issue and issuewild pairs for letsencrypt.org, pki.goog, sectigo.com, and ssl.com (checked against the DNS records API). The comodoca.com and digicert.com pairs and the cansignhttpexchanges=yes parameters exist in no zone record: Cloudflare adds those at serve time. The live 12-record response is the union of the two. A minimal hand-configured CAA record alongside these looks like:
dig @1.1.1.1 +short caa.example.isitdns.net CAA0 issue "letsencrypt.org"nslookup -type=CAA caa.example.isitdns.net 1.1.1.1caa.example.isitdns.net rdata_257 = 0 issue "letsencrypt.org"nslookup displays CAA records as
rdata_257rather than parsing theissue/issuewild/iodeftags. The data is correct but the presentation is raw. Usedigwhen you need to read the tag labels clearly.
See caa-policy for CAA record structure and validation, and caa for the record type reference.
Email authentication (SPF / DKIM / DMARC)#
- Email auth records must be DNS-only (grey-cloud TXT/CNAME). Proxying them breaks mail delivery. [F]
- Start DMARC at
p=none(monitor mode), thenp=quarantine, thenp=reject. Add anrua=mailto:address so you actually receive aggregate reports while in monitor mode. DMARC is now specified by RFC 9989, which obsoletes the original RFC 7489; aggregate reporting moved to RFC 9990 and failure reporting to RFC 9991. [F] - Domains that never send mail should publish null SPF (
v=spf1 -all, RFC 7208), a wildcard null DKIM record at*._domainkey, and a DMARCp=rejectpolicy. Cloudflare's full setup guide shows exactly this record set, all DNS-only. [F]
To verify your SPF record is on the wire and DNS-only:
dig @1.1.1.1 +short example.com TXT"v=spf1 -all"
"_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9"nslookup -type=TXT example.com 1.1.1.1example.com text = "v=spf1 -all"
example.com text = "_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9"The TXT response includes the v=spf1 ... string. DMARC lives at _dmarc.example.com (TXT) and DKIM at <selector>._domainkey.example.com (TXT). Note that you cannot accidentally proxy these: Cloudflare's proxy applies only to A, AAAA, and CNAME records, so TXT records are always published DNS-only exactly as entered. (A TTL of 300 in a response is not a proxy indicator either way; resolvers commonly return decremented cache TTLs, and non-Cloudflare zones use 300 too.) See email-records for SPF, DKIM, and DMARC record details.
Sources: DNSSEC, Proxy status, CAA records
3. SSL / TLS#
Mode selection#
| Mode | Origin leg | Recommended? |
|---|---|---|
| Off | Plain HTTP | Never |
| Flexible | Plain HTTP | No: origin traffic is unencrypted |
| Full | HTTPS, cert not validated | Acceptable interim step |
| Full (strict) | HTTPS, cert validated | Yes: use this |
Cloudflare "strongly recommends using Full or Full (strict) modes to prevent malicious connections to your origin", and only Full (strict) validates the origin certificate, against a public CA or a Cloudflare Origin CA. Enterprise zones get a fifth mode, Strict (SSL-Only Origin Pull), which forces HTTPS to the origin regardless of the visitor's scheme. Automatic SSL/TLS is the default and adjusts the mode for you: "Automatic SSL/TLS will not change your setting to a less secure encryption mode" (encryption modes). The older SSL/TLS Recommender, which emailed a recommendation rather than changing anything, is being deprecated in its favour. [F]
Minimum TLS version#
- Configure the zone-wide minimum TLS version in SSL/TLS → Edge Certificates → Minimum TLS Version. Zone-level is available on Free, Pro, Business, and Enterprise (Minimum TLS Version). [F zone-wide / P per-hostname]
- Raise this to TLS 1.2 at minimum. All major browsers dropped TLS 1.0 and 1.1 support in 2020, so no current browser needs them.
- Per-hostname minimum TLS version is included with Advanced Certificate Manager (paid). [P]
- Cloudflare states "it is not possible to configure minimum TLS version for Cloudflare Pages hostnames." [F]
To set the minimum via the API:
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/min_tls_version" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{"value":"1.2"}'Replace $ZONE_ID with your zone ID from the Cloudflare dashboard and $CF_TOKEN with an API token scoped to Zone Settings: Edit for that zone. See the API token guidance in section 1.
HSTS#
- Enable HSTS under SSL/TLS → Edge Certificates → HSTS. The Max Age Header field accepts "Disable, or a range from 1 to 12 months"; include
includeSubDomainsonly if all subdomains also serve HTTPS (HSTS). [F] - Leave Preload off until you are certain: "a minimum Max Age Header of 12 months is required for inclusion in HSTS preload lists", and a preloaded site that later drops HTTPS becomes unreachable for the remaining
max-age. - The dashboard flow makes you acknowledge a warning dialog before saving. Via the API, HSTS is the
security_headerzone setting, set with the same PATCH pattern asmin_tls_versionabove. - Once HSTS is on, do not flip proxied records to DNS-only, pause Cloudflare, or point nameservers away: Cloudflare lists all three as things that break visitor access.
Other toggles#
- Always Use HTTPS: redirects all HTTP requests to HTTPS at the edge. [F]
- Automatic HTTPS Rewrites: rewrites insecure resource URLs in HTML responses to eliminate mixed-content errors. [F]
- TLS 1.3: enable it; there is no reason to leave it off. [F]
Sources: SSL modes, Minimum TLS
4. Account hardening#
Two-factor authentication#
- Enable 2FA for every account holder. [F]
- Prefer security keys (WebAuthn / hardware tokens): phishing-resistant, strongest available. TOTP apps (Authenticator, Authy) are also acceptable. Email-only 2FA is the weakest option. [F]
- Enable at least two 2FA factors and store backup codes off-device in an encrypted store. [F]
- Super Administrators can enforce 2FA for all account members in the account settings. [F]
Audit logs and zone hygiene#
- Audit logs v2 are generally available to all Cloudflare customers and capture user and system actions automatically across roughly 95% of Cloudflare products. "Audit logs are retained for 18 months before being deleted." The dashboard view queries only the most recent 90 days: "to access the full 18 months of data, use the API or Logpush." Review the log periodically, quarterly at minimum, rather than waiting for an incident (Audit logs). [F]
- Review the "Secure a compromised account" runbook before you need it. [F]
- Use Zone Holds to prevent a zone from being added to another Cloudflare account without authorization. One-click and worth enabling, but the availability table reads No / No / No / Yes for Free / Pro / Business / Enterprise, and holds are on by default for Enterprise zones. [P]
Sources: 2FA, Audit logs
5. API hygiene: pagination, rate limits, and retries#
Rate limits#
- Global limit: "1,200 requests per five minute period per user", cumulative "regardless of whether the request is made via the dashboard, API key, or API token". [F]
- Per-IP limit: 200 requests per second. [F]
- GraphQL: varies by query cost, capped at 320 per 5 minutes. [F]
- On a 429 response: "all API calls for the next five minutes will be blocked." Honor the
retry-afterheader and do not hammer the endpoint.
Client behavior#
- Read the three rate-limit headers and pace requests accordingly:
Ratelimit(limit name, remaining quotar, window resett),Ratelimit-Policy(policy name, total quotaq, window), andretry-after(seconds until capacity, returned only when you have exceeded the limit). [F] - "Cloudflare's SDKs will also automatically work with the headers and back off in response to rate limits." Match that behavior in hand-rolled scripts. [F]
- Paginate list endpoints rather than pulling everything in one call. Over-large result sets time out. [F]
Source: API limits
6. Free-tier security: WAF, Rules, and Bot Fight Mode#
WAF and managed rulesets#
- Free Managed Ruleset is the only managed WAF ruleset on the Free plan. The availability table gives Free a Yes for it and a No for the Cloudflare Managed Ruleset, the OWASP Core Ruleset, and Sensitive Data Detection. Enable it: the cost is zero, the baseline protection is not. [F]
- Custom Rules allow hand-crafted allow/block/challenge logic. Useful for specific paths, user-agent patterns, or country restrictions. [F]
- Rate Limiting Rules: one rule on Free, two on Pro, five on Business, 100 on Enterprise (rate limiting rules). Spend the Free rule on the most abuse-prone endpoint (a login form, an API endpoint, a contact form). [F limited / P more rules]
- IP Access Rules are available on every plan, 50,000 rules each, but the block by country option is Enterprise-only (IP Access Rules). [F]
- Zone Lockdown is not available on Free. The availability table reads No / Yes / Yes / Yes with 0 / 3 / 10 / 200 rules. Use a custom rule instead on Free. [P]
- Bot Fight Mode is a Free-plan on/off toggle that challenges traffic matching known-bot patterns. You cannot customize it or skip it with a custom rule (Bot Fight Mode). [F]
Hygiene toggles#
| Toggle | What it does | Availability |
|---|---|---|
| Email Address Obfuscation | Rewrites visible email addresses in HTML and injects a deferred decode script | On automatically at signup |
| Hotlink Protection | Blocks image embedding from other origins | [F] |
| User Agent Blocking | Blocks by user-agent string, 10 rules on Free (Cloudflare recommends custom rules instead) | [F] |
| Leaked Credentials Detection | On by default on Free, exposing the Password Leaked and User and Password Leaked fields; custom detection locations are Enterprise-only | [F] |
Rules migration note#
New work should use the Ruleset Engine, not Page Rules. Cloudflare "recommends using modern Rules features for new implementations" and names the replacements:
- Configuration Rules
- Compression Rules
- Origin Rules
- Redirects
- Transform Rules
- Cache Rules
It also states it "will handle the migration of your existing Page Rules automatically ... You will receive advance notification before any changes are made." Verify the results yourself when that lands.
Analytics#
- Security Analytics at the zone level is included on every plan; account-level analytics need Business or Enterprise. Free retains 7 days with a 24-hour query window (Security Analytics). [F]
- Security Events on Free gives you the Sampled logs view only, with 24 hours of retention and no export. The other dashboard sections need Pro or above. Sampling itself is not a plan limit: Cloudflare samples high-volume traffic on every plan, so narrow the time window to reduce it (Security Events). [F limited / P full dashboard]
Sources: WAF, Page Rules migration guide
See also#
- caa-policy: CAA record structure and validation
- caa: CAA record type reference
- dnssec: DNSSEC concepts and signing
- dnssec-troubleshooting: debugging DNSSEC validation failures
- dot-doh: how encrypted DNS (DoT/DoH) interacts with Cloudflare's proxy
- email-records: SPF, DKIM, and DMARC record structure
- nslookup-and-dig: dig and nslookup tool reference