>_C2CZ
Serverless · Self-Hosting · Security · Independent Ops

Cloudflare Error 1020 Access Denied: Fix

Cloudflare Error 1020 "Access denied" means a firewall rule evaluated your request and blocked it — nothing on the origin server is broken, and Cloudflare is working exactly as configured. The error page you land on is Cloudflare's branded block page, not a response from the website's server, and the only identifier that matters on it is the Ray ID at the bottom.

I have debugged this error from both sides of the glass: as a visitor locked out of a site I legitimately needed, and as the operator who finds the matching rule in Security Events. The two fixes could not be more different, which is why most advice online is useless — it assumes you are the site owner. This post covers the exact triage for each role, with the Cloudflare dashboard paths and a GraphQL query you can run today to pull the offending event out of the analytics API.

TL;DR

  • Error 1020 is a deliberate block, not an outage. A Cloudflare firewall rule with a Block action matched the request at the edge and answered with a 403 and the branded "Access denied" page. The origin never saw the request.
  • As a visitor, you cannot fix it. Retrying does nothing (you are still you), and hammering refresh can make adjacent automated rules treat you as a bot. Capture the Ray ID and the time, then contact the site owner through any channel that is not the blocked page.
  • As the site owner, the Ray ID is your lookup key. Open Security Events (Security → Analytics → Events), paste the Ray ID or the visitor's client IP, and the log shows exactly which rule matched and what action it took.
  • Free and Pro plans only retain firewall events for about 24 hours, so trace 1020 reports the same day you get them — or the evidence is gone. Business gets 3 days, Enterprise 30.
  • The common root cause is a stale rule. The block is usually years old and over-broad — a country or ASN filter written when the threat model was different. The durable fix is narrowing the rule, not whitelisting one annoyed customer.

⚠️ Do not confuse 1020 with Cloudflare errors that mean the edge itself failed. Error 1016 (origin DNS error) and Error 1101 (worker threw an exception) are infrastructure faults; 1020 is a policy decision. Different code, different fix, different inbox.

What Error 1020 actually means

Cloudflare's official documentation for Error 1020 is one sentence long: access to the website is denied by a Cloudflare firewall rule, and the common cause is a client blocked by the zone's firewall rules. Under the hood, every proxied request passes through the zone's security layer at the edge before anything is fetched from the origin. That layer evaluates IP Access Rules, WAF custom rules, and managed rulesets. When a rule configured with the Block action matches, the edge stops there — the origin never sees the request — and returns the branded 1020 page with a Ray ID that uniquely identifies the blocked event.

The visitor-facing page is deliberately vague. It says you do not have access to the site, that the owner may have set restrictions, and that you should contact the owner or try again later. That copy is correct as far as it goes, and it is also why this error generates so many confused threads: the person on the other end of the browser usually has no idea they tripped a security policy.

Cloudflare serves several near-identical block pages from the same 1xxx family, and telling them apart up front saves an hour of wrong turns. The codes are documented on Cloudflare's 1xxx errors index:

ErrorWhat was deniedBlock basis
1005Access deniedYour autonomous system (ASN) is banned
1006 / 1007 / 1008 / 1106Access deniedYour IP address is banned
1010Access deniedYour browser signature is banned
1020Access deniedA firewall / WAF rule matched and blocked you
1016Origin DNS errorEdge could not resolve the origin — infrastructure, not a block

If the visitor reports 1006 instead of 1020, the block came from an IP-level rule rather than an expression-based firewall rule. The triage is the same log, but the fix target is a different rule type. Everything below assumes the reported code is 1020.

The visitor's side: what 1020 tells you, and what it does not

When you hit Error 1020 on a site you need, the first question is whether the block is about you or about your network position. Cloudflare assigns reputation to the IP address you are coming from, not to your account. If you are on a shared egress — an office NAT, a hotel, a university, a VPN provider, or any cloud/VPS host — the IP carries the accumulated reputation of everyone who used it before you. A site with a strict WAF rule can block that whole range even though you personally have never touched the site.

From the blocked network, you can reproduce what the server-side log will show. The response is an HTTP 403 from Cloudflare's edge with the branded block page, and the headers carry the cf-ray identifier. Save both the headers and the body, then compare the 16-hex Ray ID in each — the header appends the datacenter code (for example cf-ray: 64c0c2f1ccb5d781-MIA), while the page prints only the bare Ray ID — and a match confirms you captured the exact event the owner will look up:

# Run this from the network that is blocked.
# A firewall-rule block answers 403 with an HTML body that contains
# "Access denied", "Error 1020", and a Ray ID.
curl -sS -D /tmp/cf-headers.txt -o /tmp/cf-body.html \
  --max-time 15 https://the-site.example/some/path

# Extract the bare 16-hex Ray ID from both the cf-ray header...
RAY_HEADER=$(grep -ioE '[a-f0-9]{16}' /tmp/cf-headers.txt | head -1)

# ...and from the block page body.
RAY_BODY=$(grep -ioE '[a-f0-9]{16}' /tmp/cf-body.html | head -1)

echo "header Ray ID: $RAY_HEADER"
echo "page Ray ID:   $RAY_BODY"
[ "$RAY_HEADER" = "$RAY_BODY" ] && echo "match: blocked event captured" \
  || echo "no match: different request or not a 1020 page"

What you can actually do, in order of usefulness:

  • Save the Ray ID and the timestamp. This is the one piece of evidence an operator can act on. Without it, your report forces the owner to grep a 24-hour log window by IP guesswork.
  • Try a different network once. Tethering to a phone or dropping the VPN changes the egress IP. If the site loads, the block was targeting your previous IP range, and you have your answer without bothering anyone.
  • Contact the owner out of band. Email, LinkedIn, a ticket system — anything that is not served through the blocked page. Include the Ray ID, the exact time in UTC, and the URL you were trying to reach. The official Cloudflare resolution for non-owners is literally: provide the owner a screenshot of the 1020 error.

⚠️ Do not hammer the refresh button or run aggressive retries. Every blocked request is a security event on the owner's side. A burst of identical blocked requests is exactly the behavioral signal that makes adjacent bot-fighting rules decide the block was correct.

The owner's side: trace the block in Security Events

If the site is yours, the 1020 error page is a precise audit trail, not a mystery. Cloudflare's resolution steps for owners are short: take the screenshot, search the Security Events log for the Ray ID or client IP, assess the cause, then update the rule or allow the IP.

The dashboard path is Analytics → Events for the zone (the full URL is the zone's Security → Analytics page, Events tab). Filter by the Ray ID from the error page, or by the reporter's client IP if you never got the Ray ID. Each event row shows:

  • Actionblock is what produced the 1020; log rows are rules that matched but only recorded.
  • Source / service — which security feature fired (WAF, firewall rules, and so on).
  • Rule identity — the rule name or ID that matched, so you can jump straight to it.
  • Match details — country, ASN, user agent, path, and the expression fields that tripped.

One operational reality shapes everything else: retention is short on lower plans. Security Events data retention and query windows, from Cloudflare's availability documentation, are:

PlanData retentionMax query window
FreeUp to 24 hours (sampled logs only)24 hours
ProUp to 24 hours24 hours
BusinessUp to 3 days3 days
EnterpriseUp to 30 days31 days

💡 On Free and Pro, a 1020 complaint from Tuesday has no evidence by Thursday. Build the habit of triaging block reports the same day, and if you run a site where false-positive blocks are frequent, the Business tier's three-day window is a defensible operating expense — not a luxury.

Trace the block with the GraphQL Analytics API

The dashboard is fine for one Ray ID. When you get a batch of complaints — a new ISP range, a VPN provider's exit nodes, a country-wide block that caught real customers — you want the same lookup scriptable. Cloudflare's firewallEventsAdaptive dataset powers Security Events, and it is queryable through the GraphQL Analytics API.

The call below is the documented shape from Cloudflare's Querying Firewall Events tutorial: a GraphQL query over a one-day window on one zone, requesting the documented event fields. Pipe the response through jq and keep only rows where the action is block — those are your 1020s (and the 1005/1006 family). Replace <ZONE_TAG> and <API_TOKEN> with your zone tag and an API token that has the Analytics read permission for the zone.

echo '{ "query":
  "query ListFirewallEvents($zoneTag: string, $filter: FirewallEventsAdaptiveFilter_InputObject) {
    viewer {
      zones(filter: { zoneTag: $zoneTag }) {
        firewallEventsAdaptive(
          filter: $filter
          limit: 25
          orderBy: [datetime_DESC]
        ) {
          action
          datetime
          source
          clientIP
          clientAsn
          clientCountryName
          clientRequestPath
          clientRequestQuery
          userAgent
        }
      }
    }
  }",
  "variables": {
    "zoneTag": "<ZONE_TAG>",
    "filter": {
      "datetime_geq": "2026-09-07T00:00:00Z",
      "datetime_leq": "2026-09-08T23:59:59Z"
    }
  }
}' | tr -d '\n' | curl --silent \
https://api.cloudflare.com/client/v4/graphql \
--header "Authorization: Bearer <API_TOKEN>" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- | jq '.data.viewer.zones[0].firewallEventsAdaptive[] | select(.action == "block")'

Match the returned clientIP, clientAsn, and datetime rows against the reporter's Ray ID and timestamp. The dataset's documented fields do not include the Ray ID itself, so the reliable join key is client IP plus a time window — which is exactly why you should collect both from the visitor before the 24-hour retention window closes.

The dataset respects the same plan limits as the dashboard. If a query reaches too far back, the API returns an error in the shape cannot request data older than 2678400s (the seconds in 31 days — the Enterprise ceiling). Before writing a long historical query, you can read your zone's actual limits from the settings node with a query like this, again via the same curl pattern:

# Ask the API for your zone's real retention limits instead of guessing:
#   maxDuration, maxPageSize, notOlderThan for firewallEventsAdaptive
echo '{ "query":
  "{
    viewer {
      zones(filter: { zoneTag: \"<ZONE_TAG>\" }) {
        settings {
          firewallEventsAdaptive {
            maxDuration
            maxPageSize
            notOlderThan
          }
        }
      }
    }
  }"
}' | tr -d '\n' | curl --silent \
https://api.cloudflare.com/client/v4/graphql \
--header "Authorization: Bearer <API_TOKEN>" \
--header "Accept: application/json" \
--header "Content-Type: application/json" \
--data @- | jq .

Then fix the rule, not the symptom

Once the event row names the rule, resist the urge to add one more IP to an allowlist and close the ticket. That is how firewall rulesets rot: a chain of single-IP exceptions that nobody remembers, each one a small hole in the zero-trust posture. Treat the 1020 report as a review trigger for the rule that fired:

  • Is the rule still necessary? Country and ASN blocks written during a specific abuse campaign often outlive the campaign. If the rule exists because of one incident years ago, the incident's threat model is probably gone.
  • Is it narrower than it looks? A country-level block takes out every legitimate user behind VPNs and cloud egress in that country, not just the abusers. If the goal is blocking one scraper fleet, target the ASN or the specific user-agent/path fingerprint instead.
  • Does the action match the intent? Cloudflare's WAF actions are not all-or-nothing. For suspicious-but-possibly-human traffic, a Managed Challenge (or the legacy challenge action) proves humanness without nuking the session; reserve Block for traffic you never want through.
  • Was it a false positive on a legit path? If a real customer or an internal tool trips the rule, the official remedy is an IP Access Rule with mode Allow for that IP — placed deliberately, documented in the rule notes, and reviewed on a schedule.

The official Error 1020 resolution for owners ends with the same two options: update the firewall rule, or allow the visitor's IP address in IP Access Rules. Both are policy decisions, and both should be made with the rule's full match history in front of you — which is the Security Events trace above.

After you change the rule, verify from the same vantage point as the reporter. If you can reach the URL from the previously blocked network and the Security Events log shows the request now passing (or being challenged and cleared), the fix is done. If the block persists, the event row will show a different rule matched — keep walking the log until no block action remains for that client.

Related Cloudflare error posts

Error 1020 sits in the middle of a family of Cloudflare edge errors that people routinely confuse with each other. These are the ones we have already torn apart on this blog:

Rule of thumb: 1016/1101/1102/524 mean Cloudflare or your origin failed. 1020 means a policy said no. When the error is a policy, the fix is a trace and a decision — and now you have both the trace path and the decision framework.

C2CZ

Hyper-specific engineering guides: Cloudflare Workers, Turso, self-hosting, network security, offensive security, independent operations.