is it dns? / wiki ← back to the live monitor

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 Read vs Edit per 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 tokens carry a cfut_ prefix that credential scanners recognize. [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. [F]

Source: Create API tokens


2. DNS, DNSSEC, and CAA#

DNSSEC#

  • Enable DNSSEC under DNS → Settings and submit the DS record at your registrar. Cloudflare's default cipher is Algorithm 13 (ECDSA P-256 + SHA-256). [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. You can verify this from the command line:

dig +dnssec DNSKEY isitdns.net @1.1.1.1
;; 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. 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 or DoH to a known validating resolver). An on-path attacker on an unencrypted channel can strip or forge the AD bit. See dot-doh for how to query over an encrypted channel. To check the DS record the parent TLD is serving:

dig +short DS isitdns.net @1.1.1.1
2371 13 2 2DF8E1D4E0855DF60F2A8B51C914F2F2866ED1E3344F74F9682DAC4D 42D18802

Keytag 2371, 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_48 and rdata_43) but cannot display the AD bit or RRSIG signatures. Use dig or 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": true when 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 default to Auto TTL = 300 s and cannot be changed; only DNS-only records support custom TTLs. [F]
  • CNAME flattening at the apex happens automatically on all plans whenever the zone apex is a CNAME; flattening all CNAME records in the zone is a paid option. [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). As of mid-2026 the injected set on this zone is: comodoca.com, digicert.com, letsencrypt.org, pki.goog, sectigo.com, and ssl.com (plus their issuewild counterparts). These appear in dig output but not in the dashboard. The set may change; check your zone's live CAA response to see what is actually in effect. [F]
  • CAA also applies on CNAME targets: verify CAA at every link of a CNAME chain. [F]
  • Add an iodef clause (0 iodef "mailto:security@example.com") so misissuance reports have a return address. [F]

You can see the auto-injected records on any Cloudflare-hosted zone:

dig +short CAA isitdns.net @1.1.1.1
0 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"

Most of those records are not in the dashboard for this zone: Cloudflare injects them at serve time. The exception is the trigger record. One hand-added CAA (sectigo.com, both issue and issuewild) is configured in the dashboard, and its presence is what makes Cloudflare inject the rest of the set. So the live 12-record response is one hand-added pair plus the CF-injected CAs. A minimal hand-configured CAA record alongside these looks like:

dig +short CAA caa.example.isitdns.net @1.1.1.1
0 issue "letsencrypt.org"
nslookup -type=CAA caa.example.isitdns.net 1.1.1.1
caa.example.isitdns.net	rdata_257 = 0 issue "letsencrypt.org"

nslookup displays CAA records as rdata_257 rather than parsing the issue/issuewild/iodef tags. The data is correct but the presentation is raw. Use dig when 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), then p=quarantine, then p=reject. Add an rua=mailto: address so you actually receive aggregate reports while in monitor mode. [F]
  • Domains that never send mail should publish null SPF (v=spf1 -all), wildcard null DKIM, and a DMARC p=reject policy. [F]

To verify your SPF record is on the wire and DNS-only:

dig +short TXT example.com @1.1.1.1
"v=spf1 -all"
"_k2n1y4vw3qtb4skdx9e7dxt97qrmmq9"
nslookup -type=TXT example.com 1.1.1.1
example.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#

ModeOrigin legRecommended?
OffPlain HTTPNever
FlexiblePlain HTTPNo: origin traffic is unencrypted
FullHTTPS, cert not validatedAcceptable interim step
Full (strict)HTTPS, cert validatedYes: use this

Cloudflare strongly recommends Full or Full (strict), and only Full (strict) validates the origin certificate, against a public CA or a Cloudflare Origin CA. The Automatic SSL/TLS feature scans your origin and only ever moves the zone to a stricter mode, never a weaker one. [F]

Minimum TLS version#

  • Configure the zone-wide minimum TLS version in SSL/TLS → Edge Certificates → 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 requires Advanced Certificate Manager (paid). [P]
  • Cloudflare Pages hostnames cannot have their minimum TLS version configured. [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. Start with max-age of at least six months and include includeSubDomains only if all subdomains also serve HTTPS. [F]
  • Leave Preload off until you are certain: browser preload lists require a 12-month max-age, 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_header zone setting, set with the same PATCH pattern as min_tls_version above.

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 are always on, all plans, free of charge: 18 months of retention, with CSV download from the dashboard and a list API on every plan. The v2 dashboard view only queries the most recent 90 days; use the API for the full 18 months, or Logpush (Enterprise) to keep logs longer externally. Review the log periodically, quarterly at minimum, rather than waiting for an incident. [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 it is Enterprise-only (on by default for Enterprise zones); Free, Pro, and Business plans do not have it. [P]

Sources: 2FA, Audit logs


5. API hygiene: pagination, rate limits, and retries#

Rate limits#

  • Global limit: 1,200 requests per 5-minute period per user, cumulative across dashboard, Global API Key, and API token traffic. [F]
  • Per-IP limit: 200 requests per second. [F]
  • On a 429 response: the account is blocked from all API calls for 5 minutes. Honor the retry-after response header and do not hammer the endpoint.

Client behavior#

  • Read Ratelimit, Ratelimit-Policy, and retry-after headers and pace requests accordingly. [F]
  • The official Cloudflare SDKs default to 2 retries with exponential backoff for HTTP 408, 409, 429, 5xx, and connection errors. Match this behavior in custom scripts. [F]
  • Paginate list endpoints with the per_page parameter. Over-large result sets time out. Use SDK auto-pagination iterators where available. [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 available on the Free plan. 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. Spend it on the most abuse-prone endpoint (a login form, an API endpoint, a contact form). [F limited / P more rules]
  • IP Access Rules and Zone Lockdown are available on the Free plan. [F]

Hygiene toggles (all Free)#

ToggleWhat it does
Email Address ObfuscationRewrites mailto: links in HTML to foil scraper bots
Hotlink ProtectionBlocks image embedding from other origins
Replace Insecure JS LibrariesSubstitutes known-vulnerable JS CDN references
User Agent BlockingBlocks by user-agent string
Leaked Credentials DetectionFlags one form field for credential-stuffing detection

Rules migration note#

Page Rules are deprecated. Any existing Page Rules should be migrated to the Ruleset Engine equivalents: Configuration Rules, Origin Rules, Redirects, Transform Rules, Compression Rules, and Cache Rules. Cloudflare plans to migrate remaining Page Rules automatically with advance notice, but verify the results yourself. New work should use the Ruleset Engine exclusively.

Analytics#

  • Security Analytics at the zone level is available on Free. [F]
  • Security Events logs are sampled on Free; full unsampled logs require a paid plan. [F sampled / P full]

Sources: WAF, Page Rules migration guide


See also#