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 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.12371 13 2 2DF8E1D4E0855DF60F2A8B51C914F2F2866ED1E3344F74F9682DAC4D 42D18802Keytag 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_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 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, andssl.com(plus theirissuewildcounterparts). These appear indigoutput 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
iodefclause (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.10 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.10 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. [F] - Domains that never send mail should publish null SPF (
v=spf1 -all), wildcard null DKIM, and a DMARCp=rejectpolicy. [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.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 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-ageof at least six months and includeincludeSubDomainsonly 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 remainingmax-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.
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-afterresponse header and do not hammer the endpoint.
Client behavior#
- Read
Ratelimit,Ratelimit-Policy, andretry-afterheaders 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_pageparameter. 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)#
| Toggle | What it does |
|---|---|
| Email Address Obfuscation | Rewrites mailto: links in HTML to foil scraper bots |
| Hotlink Protection | Blocks image embedding from other origins |
| Replace Insecure JS Libraries | Substitutes known-vulnerable JS CDN references |
| User Agent Blocking | Blocks by user-agent string |
| Leaked Credentials Detection | Flags 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#
- 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