Cloudflare Firewall Rules with Expression Syntax

The most surprising thing about Cloudflare’s Expression Syntax for Firewall Rules is how it lets you build incredibly granular logic that goes far beyond simple IP or country blocking, effectively turning your edge into a programmable WAF and access control layer.

Let’s see it in action. Imagine you want to allow access to your /admin path only from specific IP addresses or specific countries, and only if the request comes over HTTPS.

Here’s what that rule looks like in the Cloudflare dashboard, using the Expression Syntax:

(ip.src in {192.168.1.1 10.0.0.5}) or (ip.geoip.country in {"US" "CA"}) or (cf.threat_score lt 10)

When this rule matches, we can choose an action, like "Block" or "Log" or "Managed Challenge."

Let’s break down the mental model. Firewall Rules are essentially if-then statements executed at Cloudflare’s edge. The if part is the expression, and the then part is the action you choose. The Expression Syntax is the language you use to build that if condition.

Cloudflare exposes a vast array of request attributes you can inspect. These include:

  • ip.src: The originating IP address of the request.
  • ip.geoip.country: The two-letter country code for the originating IP.
  • http.request.uri.path: The path component of the request URI (e.g., /admin, /api/v1/users).
  • http.request.method: The HTTP method (e.g., GET, POST).
  • cf.threat_score: Cloudflare’s internal score indicating the likelihood of a request being malicious (0-30).
  • cf.bot_management.score: A score from Cloudflare Bot Management indicating how likely the request is from a bot.
  • ssl: A boolean indicating if the connection is using SSL/TLS.
  • http.user_agent: The User-Agent string sent by the client.

You combine these attributes using logical operators (and, or, not) and comparison operators (eq, ne, gt, lt, ge, le, in, contains, startswith, endswith).

For our /admin example, the rule is:

(http.request.uri.path eq "/admin") and ((ip.src in {192.168.1.1 10.0.0.5}) or (ip.geoip.country in {"US" "CA"})) and (ssl eq true)

This translates to: "If the request path is exactly /admin AND (the source IP is either 192.168.1.1 or 10.0.0.5 OR the country is US or CA) AND the connection is using SSL, then take the specified action."

You can also use the not operator. For instance, to block requests that aren’t from your trusted IPs or countries:

(http.request.uri.path eq "/admin") and not ((ip.src in {192.168.1.1 10.0.0.5}) or (ip.geoip.country in {"US" "CA"})) and (ssl eq true)

This rule would block anyone trying to access /admin who isn’t coming from one of the specified IPs or countries, and isn’t using SSL.

The in operator is particularly powerful for lists. {192.168.1.1 10.0.0.5} is a set of IPs. {"US" "CA"} is a set of country codes.

You can also use wildcards within strings. For example, to match all subdirectories of /api:

http.request.uri.path contains "/api/"

Or to match specific User-Agent strings:

http.user_agent contains "MyCustomBot/1.0"

The order of operations matters, so use parentheses liberally to ensure your logic is evaluated correctly. Cloudflare evaluates expressions from left to right, respecting standard operator precedence, but parentheses make it unambiguous.

A less obvious but incredibly useful function is cf.threat_score. You can use it to automatically block requests that Cloudflare’s systems deem highly suspicious. A rule like (cf.threat_score gt 15) will block requests with a threat score greater than 15, effectively acting as a basic WAF. You can combine this with other rules. For example, to allow access to your public site but block suspicious traffic to your admin area:

(http.request.uri.path eq "/admin") and (cf.threat_score gt 10)

This rule would block requests to /admin if Cloudflare’s threat score for that request is above 10.

One common pitfall is forgetting to include ssl eq true when you intend to restrict access to HTTPS. If you only check the path and IP, someone could potentially bypass your rule by making an unencrypted request (if your origin server allows it).

Another advanced technique is using and to create AND conditions across different types of checks. For example, to allow only trusted IPs from the US to access a specific API endpoint:

(http.request.uri.path eq "/api/v2/sensitive") and (ip.geoip.country eq "US") and (ip.src in {192.168.1.1 10.0.0.5})

This ensures all three conditions must be met.

The next step after mastering basic expression syntax is understanding how to chain multiple Firewall Rules together, and how their order of execution impacts your security posture.

Want structured learning?

Take the full Cloudflare course →