Edgeweir
Guides

Rules, IP lists, and GeoIP

Site rules, global rules, bulk redirects, IP lists, and the node-local GeoIP databases.

Concepts

TermDefinition
RuleAn expression plus an action in one phase. Site rules apply to one site; global rules apply to every site in every cluster.
PhaseA fixed point in request processing where rules run; there are 9.
ExpressionA typed, wirefilter-style condition, for example ip.src in $blocked.
Value expressionAn expression that computes a string per request, used as a redirect target, a rewrite path, a request or response header value, or the value of a set query parameter, for example concat("/new", http.request.uri.path).
Bulk redirectsA site's table of exact-match redirects, up to 5000 per site.
IP listA named set of IP addresses and CIDRs that expressions reference as $name; allow and block lists also apply to every site directly.

The console parses expressions and checks their fields, types, and actions before publishing a syntax tree; nodes validate the whole configuration and compile the tree. The configuration contains no executable Lua text.

Edit site rules

  1. Open Sites, select the site, and open the Rules tab.

  2. Click Add rule next to the target phase.

  3. Enter the rule name and Expression. Insert condition appends a common condition (path prefix, Host equals, file extension, IP range, IP list, country, ASN, User-Agent contains, request method, Cookie equals, query parameter equals, User-Agent wildcard, Referer wildcard) and selects its example value, so typing replaces it; Insert field appends a field, and for Cookie, Query parameter, and Request header you first enter the name in the field that appears and press Enter or click Insert; both join with and. When the expression is invalid, "Character N: reason" appears below the editor, for example "Character 11: Ordered comparisons need a number field"; an expression the console refuses on save shows its position and reason the same way.

  4. Select Action, fill in its fields, and turn on Enabled. A new rule starts disabled with the expression true (every request), as does a rule saved through the API without enabled; check the condition and the action before you save.

  5. Drag the handle on the left of a rule to reorder rules within a phase.

  6. Click Save. The console shows Saved and publishes a new configuration revision ("Rules and IP lists updated").

  7. Verify: for example, the expression http.request.uri.path eq "/blocked" with the Block action:

    curl -sI -H 'Host: www.example.com' http://<node IP>/blocked

    The response is 403 with X-Edgeweir-Error: policy-denied.

Global rules are edited on the Global rules page with the same editor (Origin override has no Origin group), apply to every site, and are published to every cluster on save. API: GET and PUT /api/v1/platform-rules.

Disabled rules are not sent to nodes.

Phases and actions

Phases run in the order of this table.

PhaseActionsEffect
Request transformRewrite path, Request headerRewrites the origin path and query parameters; sets or removes request headers
RedirectRedirectReturns 301, 302, 303, 307, or 308; bulk redirects are looked up after the rules
ConfigurationOverride settingsOverrides site settings per request, see Override settings
Custom WAFBlock, Log, Allow, ChallengeBlock returns 403 or 451; Log only writes a log line; Allow skips the remaining custom WAF rules of the same scope; Challenge makes visitors pass a challenge first
Rate limitRate limitFixed-window counting; over the limit returns 429 or 403
CacheOverride settingsOverrides only cache bypass, HTTPS redirect, and Gzip
OriginRequest header, Origin overrideSets or removes headers sent to the origin; picks an origin group and overrides the origin Host, SNI, and port
Response transformResponse headerSets, appends, or removes response headers based on status and response headers
CompressionCompression algorithmsLimits the compression algorithms of the response and their preference order

Action fields

ActionFieldValuesDefault
BlockStatus code403 / 451403
ChallengeChallenge typeCookie redirect / JavaScript / Proof of work / Image captchaJavaScript
RedirectTargetStatic: an absolute path on the site (starts with /, not //), or an http(s) URL without credentials or whitespace; no backslashes or control characters, up to 4096 characters. Expression: a value expression, see Dynamic targets and query parametersStatic, /
RedirectStatus code301 / 302 / 303 / 307 / 308301
RedirectKeep query stringOn / offOff
Rewrite pathTargetStatic: starts with /, not //; no ?, #, or \. Expression: a value expressionStatic, /
Rewrite pathKeep query stringOn / offOn
Redirect / Rewrite pathSet query parametersUp to 16 pairs of Parameter name and Parameter value, one more per Add parameter; names are 1–64 letters, digits, or . _ ~ -, unique; a Static value is printable ASCII, up to 256 characters, an Expression value a value expression, see Header and query parameter valuesNone
Redirect / Rewrite pathRemove query parametersParameter names, comma-separated, up to 16; not also in Set query parametersEmpty
Request header / Response headerHeader name1–64 token characters, not a protected headerx-custom
Request header / Response headerValueStatic: up to 4096 characters, no control characters. Expression: a value expression, see Header and query parameter valuesStatic, empty
Request header / Response headerRemove headerOn / off; a removed header has no valueOff
Response headerAppendOn / off; on, adds a line next to the response's lines of the same header (for example Link or Vary); off, replaces themOff
Override settingsEach settingSee Override settingsBypass cache On, everything else Unchanged
Origin overrideOrigin groupDefault group or one of the site's origin groups, see Origin groupsThe site's first origin group; Default group without one
Origin overrideOrigin HostHost name or IP, optionally with a port, as an origin's Origin Host; empty leaves it unchangedEmpty
Origin overrideSNIHost name; empty leaves it unchangedEmpty
Origin overridePort1–65535; empty leaves it unchangedEmpty
Compression algorithmsPreference orderSome of Zstandard, Brotli, and Gzip: Add algorithm appends one, the arrows reorder them; an empty list shows No compressionNo compression
Rate limitPresetLoose (300 per minute), Standard (100 per minute), Strict (20 per minute), or Custom; Custom shows the next two fieldsStandard
Rate limitRequests per window1–100000100
Rate limitWindow (seconds)1–360060
Rate limitRate limit keyip.src, http.host, tls.ja4, or http.request.headers.<name> (pick Request header and enter the name)ip.src
Rate limitStatus code429 / 403429

Protected headers cannot be set or removed by rules: Host, Authorization, Proxy-Authorization, Cookie, Set-Cookie, Content-Length, Transfer-Encoding, Connection, Upgrade, TE, Trailer, CDN-Loop, and headers starting with X-Edgeweir-.

Static request and response header values and value expressions are written into the configuration revision and sent to nodes. Do not put API keys or other secrets in them.

Origin override changes at least one thing: an origin group other than Default group, or one of Origin Host, SNI, and Port.

Override settings

Every setting of Override settings starts as Unchanged; numbers left empty stay unchanged.

FieldValuesPhasesEffect
Bypass cacheUnchanged / On / OffConfiguration, CacheBypasses or uses the cache for the request
Redirect HTTP to HTTPSUnchanged / On / OffConfiguration, CacheRedirects HTTP requests to HTTPS
GzipUnchanged / On / OffConfiguration, CacheOff: the response does not use gzip; On: allows gzip again after an earlier rule turned it off
Brotli, ZstandardUnchanged / On / OffConfigurationAs Gzip
WebSocketUnchanged / On / OffConfigurationOverrides the site's WebSocket
Under AttackUnchanged / On / OffConfigurationOverrides the site's Under Attack; global Under Attack is unaffected
CC mitigationUnchanged / On / OffConfigurationOff: the request is exempt from CC level challenges and automatic per-IP bans; it is still counted
CC highest levelUnchanged / Cookie redirect / JavaScript / Proof of work / Image captchaConfigurationThe request's CC level does not exceed the chosen level
Origin connect timeout (s)0.1–120ConfigurationOverrides the pool's connect timeout
Origin send timeout (s), Origin read timeout (s)0.1–3600ConfigurationOverride the pool's send and read timeouts
Log sample rate (%)0–100ConfigurationThe access log sample rate of the request
Body limit (MiB)0–10240, 0 for no limitConfigurationOverrides the site's request body limit; needs the node capability site-content-v1

A later matching rule overrides an earlier one setting by setting. Compression switches apply only among the algorithms the site has turned on; rules cannot turn on an algorithm the site has off.

Dynamic targets and query parameters

  1. Next to Target of a redirect or rewrite path rule, select Expression.

  2. Enter a value expression. When "Character N: reason" appears below the editor, fix it using Functions and Fields.

  3. Switch Keep query string as needed, and fill in Set query parameters and Remove query parameters.

  4. Click Save.

  5. Verify: for example, a redirect rule with the expression starts_with(http.request.uri.path, "/old/"), the target regex_replace(http.request.uri.path, "^/old/", "/new/"), Keep query string on, and utm_source in Remove query parameters:

    curl -sI -H 'Host: www.example.com' 'http://<node IP>/old/a?utm_source=x&id=1'

    The response is 301 with Location: /new/a?id=1.

A value expression is a string literal, a string field, or a function call that returns a string:

concat("https://www.example.com", http.request.uri.path)
regex_replace(http.request.uri.path, "^/old/(.*)$", "/new/${1}")
wildcard_replace(http.request.full_uri, "https://*.example.com/*", "https://example.com/${1}/${2}")
ItemBehavior
Redirect resultA path starting with a single /, or an http(s) URL with a host and without credentials; no whitespace, control characters, or backslashes
Rewrite resultStarts with a single /; no ?, #, \, or control characters
Invalid resultThe request gets 503 (X-Edgeweir-Error: policy-unavailable)
Default query stringRedirects drop the request's query string; rewrites keep it
OrderA redirect with Keep query string on first appends the request's query string to the target (with & when the target has one); a rewrite with Keep query string off first clears the query string. Then parameters named in Remove query parameters or Set query parameters are removed from the whole query string, and Set query parameters are appended in name order
Parameter namesThe part of each parameter before its first =, case-sensitive
Parameter valuesEvery character other than letters, digits, and - . _ ~ is percent-encoded
Fragment and empty queryA fragment after # in the target stays last; without parameters no ? is left

Header and query parameter values

  1. Next to Value of a request or response header rule, or after the name in a row of Set query parameters, select Expression.

  2. Enter a value expression, for example ip.geoip.country for a request header X-Client-Country of the origin phase and http.request.id for X-Req. When "Character N: reason" appears below the editor, fix it using Functions and Fields.

  3. Turn on Append when a response header needs another line next to the response's own.

  4. Click Save. Rule changes are hot updates; nginx does not reload.

  5. Verify: for example, with a request header rule X-Req = http.request.id in the origin phase, the origin receives an X-Req equal to the response header X-Request-Id; two response transform rules that append Link give the response two Link lines:

    curl -sI -H 'Host: www.example.com' 'http://<node IP>/' | grep -i '^link:'
ItemBehavior
PhasesRequest headers: request transform and origin; response headers: response transform (response fields available); query parameters: the phases of redirects and rewrites
Header valuesAt most 4096 bytes without control characters (\x00–\x1f, \x7f); an invalid result or a failed evaluation (a pattern over its execution budget, a function result over 8192 bytes) skips that header action and the request goes on; there is no 503
Logging skipped valuesEach rule writes at most one NOTICE-level nginx error log line per node per 60 seconds, edgeweir: header value skipped site=<site ID> rule=<rule ID>, without the header name or value; nginx appends the client IP, request line, and Host to log lines written during a request
AppendAdds one more line next to the lines of the same header, without merging or deduplicating; later rules that read http.response.headers["name"] see every line joined with , ; not together with Remove header
Query parameter valuesPercent-encoded as before (every byte other than letters, digits, and - . _ ~); a failed evaluation gets the request a 503 (X-Edgeweir-Error: policy-unavailable), as with dynamic targets
When values are computedRequest phase values use the request at that point (the path, query string, and http.request.uri.args after rewrites); response header values are computed in the edge layer's header filter, for cache hits and origin responses alike
Node requirementrules-v3, see Node capabilities and publishing

Execution order

ItemBehavior
Allow and block listsRun first. An address in a block list gets 403; an address in an allow list is exempt from the block lists but not from rules; an address in both is allowed
ScopeIn each phase, global rules run before site rules; within a scope, in list order
Terminating actionsBlock, redirect (bulk redirects included), and exceeding a rate limit end the request
Bulk redirectsLooked up after the global and site rules of the redirect phase
AllowSkips only the remaining custom WAF rules of the same scope, not the other scope and not rate limits; a site allow rule cannot bypass a block in the global rules
StackingOther actions accumulate; a later action overrides an earlier setting; override settings and origin overrides apply field by field, and a later compression rule replaces an earlier one
OrderingDragging changes order only within a phase

Action behavior

ActionBehavior
Rewrite pathChanges the path and query string sent to the origin; expressions in later phases see the rewritten path, query string, query parameters, and extension (http.request.full_uri stays the same); cache rules, the cache key, purges, and bulk redirects still use the request before the rewrite
Response transformApplies to cache hits and origin responses
Override settings: Gzip, Brotli, ZstandardAffect only the response and never bypass the cache. On sites that compress at the edge (any algorithm on), the cache still holds the same uncompressed object; on sites that do not, Gzip off removes Accept-Encoding towards the origin and the cache tells variants apart by the origin's Vary; when the origin sends no Vary: Accept-Encoding, the request may hit a previously cached compressed object
Override settings: Redirect HTTP to HTTPSRequests get 503 when the site has no certificate
Override settings: Log sample rateRecords matching requests at that rate; their logs are kept even when the site's access log sample rate is Off, see Access logs
Origin overrideLoad balancing, retries, health checks, and session affinity work as usual among the origins of the chosen group; Port applies to every origin of the group; Origin Host does not affect S3 origins; the cache key does not include the origin group, see Origin groups
Compression algorithmsOnly algorithms on the list that the site has on and override settings did not turn off are negotiated by the q-values of the request's Accept-Encoding, with the list order breaking ties; No compression turns compression off; the cache is not bypassed
Block, rate limit exceededResponse header X-Edgeweir-Error: policy-denied; rate-limited responses also carry Retry-After (the window in seconds)
ChallengeA request with a pass of a sufficient level continues with the following rules; otherwise it gets the challenge page (non-GET/HEAD requests get 403 with X-Edgeweir-Challenge: required). Allow rules skip Under Attack and CC challenges, but a challenge rule that matched before the allow still applies. See Challenges and CC mitigation
LogDoes not change the response. Nodes count the matching requests per rule and minute (not the node's own prefetches), and the Log rule matches card on the site's Security tab lists the most-matched rules over a time range (global rules marked "(global)", deleted rules shown as "Deleted rule"); the numbers are approximate: each node reports at most 20 rules per minute. While some nodes of the site's cluster lack the capability rule-log-v1, the card shows "Some nodes of the site's cluster do not report these matches". Each rule also writes at most one NOTICE-level nginx error log line per node per 60 seconds, containing the site ID and rule ID; nginx appends the client IP, request line, and Host to log lines written during a request

Rate limiting

ItemBehavior
ScopeA fixed window per node, not a network-wide quota; each site counts separately, and rate limits in the global rules are also counted per site
KeyCounted by an MD5 digest of the scope, rule ID, and key value
MemoryA fixed 256 KiB shared memory partition per published site (node flag --rate-limit-dict-kb), holding about 1980 counters, never borrowed across sites; at most 512 published sites per cluster (128 MiB in total by default); log deduplication uses another 1 MiB
Out of memoryWhen a site's partition is full, requests of new clients pass uncounted while clients that already have a counter stay limited; the node writes at most one WARN-level nginx error log line per site per minute with the running total. A node without the site's partition returns 503 (X-Edgeweir-Error: rate-limit-unavailable)
Reload and restartCounters survive an nginx reload; they reset when the node restarts or when a site is removed and added back
Hot updatesRule and list changes for the same set of sites do not reload; adding or removing sites reloads, and existing partitions keep their names and sizes

Expressions

ip.src in $blocked
http.request.uri.path matches "^/(admin|private)/" and not ssl eq true
http.request.method in {"POST" "PUT"}
http.request.headers["x-region"] eq "nz"
ip.geoip.country eq "NZ" and ip.geoip.asnum in {64512 64513}
http.response.code ge 500
lower(http.host) eq "www.example.com"
len(http.request.uri.query) gt 1024
url_decode(http.request.uri.query) contains "<script"
not starts_with(http.request.uri.path, "/api/")
http.request.uri.path.extension in {"jpg" "png" "webp"}
http.request.cookies["role"] eq "admin"
http.user_agent wildcard "*curl*"
http.referer strict wildcard "https://*.example.com/*"
substring(sha256(http.request.uri.path), 0, 8) eq "a1b2c3d4"

Fields

FieldTypeValue
http.hostStringThe request Host
http.request.methodStringThe request method
http.request.uri.pathStringThe path after nginx normalization
http.request.uri.path.extensionStringThe text after the last . of the path's last segment, lowercase; empty string without one
http.request.uri.queryStringThe query string without ?
http.request.uriStringThe raw request URI (path plus query)
http.request.full_uriStringscheme://, the Host (lowercase, without port), and the raw request URI; rewrites do not change it
http.request.headers["name"]StringCase-insensitive name; multiple values joined with ,
http.request.cookies["name"]StringThe raw value (not decoded, quotes kept) of the first cookie of that name in the request's Cookie header; the name is case-sensitive, 1–64 token characters; empty string without one. Cookies are separated by ;, spaces and tabs around a pair, its name, and its value are ignored, and a part without = is skipped; several Cookie headers are joined with ;
http.request.uri.args["name"]StringThe raw value of the first query parameter of that name (neither name nor value decoded; combine with url_decode); the name is the part before the first =, case-sensitive, 1–64 printable ASCII characters other than " # & =; a parameter without = has an empty value; empty string without the parameter; follows rewrites
http.referer, http.user_agentStringThe same as http.request.headers["referer"] and ["user-agent"], changed by request header rules too
http.request.versionStringHTTP/1.0, HTTP/1.1, HTTP/2.0, or HTTP/3.0
http.request.schemeStringhttp or https
http.request.idStringThe node's request ID, the same as the response header X-Request-Id
http.request.timestamp.secIntegerUnix seconds when the node received the request
edge.server_portIntegerThe port of the listener that received the request
http.response.codeIntegerResponse status; response transform and compression phases only
http.response.headers["name"]StringResponse header; response transform and compression phases only
http.response.content_type.media_typeStringThe response's Content-Type without parameters, lowercase; response transform and compression phases only
http.response.cache_statusStringThe edge cache's status: HIT, MISS, BYPASS, EXPIRED, STALE, UPDATING, or REVALIDATED (as X-Cache); empty string for responses the node made itself (blocks, redirects, error pages) and for WebSocket and gRPC, which never pass the cache; response transform and compression phases only
ip.srcIPThe visitor address under the cluster's client IP setting: the TCP client address when direct; the address in the PROXY header with the PROXY protocol (the UDP peer for HTTP/3); the address the trusted proxies' header names in the trusted header mode
ip.peerIPThe direct peer: the TCP client address (the UDP peer for HTTP/3), whatever the client IP setting; equal to ip.src in direct mode. Needs client-ip-v1
sslBooleantrue for HTTPS requests
ip.geoip.countryStringISO country code; empty string without a record
ip.geoip.subdivisionStringFirst-level subdivision code from the City MMDB, or its English name when it has no code; empty string without a record or when the City MMDB's country differs from ip.geoip.country
ip.geoip.asnumIntegerASN; 0 without a record
ip.geoip.as_nameStringAS name from the database that gave the ASN: IPinfo Lite's as_name or the ASN MMDB's autonomous_system_organization; empty string without a record
tls.ja4StringJA4 TLS client fingerprint of the connection; empty string over plain HTTP, see JA4

Operators and literals

SyntaxTypesDescription
eq, neAllIP values compare by address or CIDR containment
lt, le, gt, geIntegerOrdered comparison
containsStringSubstring match
matchesStringRegular expression match
wildcard "pattern"StringWhole-string wildcard match: * matches zero or more bytes (at most 8 of them), \* and \\ are literals, other backslashes are invalid; ASCII letters are case-insensitive; patterns are at most 1024 bytes without control characters
strict wildcard "pattern"StringAs wildcard, case-sensitive
in {…}AllSet, elements separated by whitespace
in $nameIPReferences an IP list; fields only, not function results
not, and, or, ( )—Precedence not → and → or
Literals—Strings in double quotes (JSON escapes); integers; true / false; IPs and CIDRs unquoted; a bare true is a valid expression
function(argument, …)—A function can be the left side of a comparison, with the operators of its return type; a function that returns a boolean can be a condition on its own

Functions

Every string is handled as UTF-8 bytes. Arguments are fields, string literals, or other function calls.

FunctionReturnsDescription
lower(s), upper(s)StringConverts ASCII letters only
len(s)IntegerNumber of bytes
starts_with(s, prefix), ends_with(s, suffix)BooleanByte-wise prefix or suffix; an empty string always matches
url_decode(s)StringDecodes once: %XX (hexadecimal, case-insensitive) becomes a byte and + a space; incomplete or non-hexadecimal % sequences stay as they are
concat(s1, s2, …)StringJoins 2–8 arguments in order
regex_replace(s, "pattern", "replacement")StringReplaces the first match; patterns as in Regular expressions; without a match the string is returned unchanged
wildcard_replace(s, "wildcard", "replacement"[, "s"])StringThe wildcard pattern must match the whole string: * matches zero or more bytes (at most 8 of them), \* and \\ are literals; ASCII case-insensitive by default, case-sensitive with a fourth argument "s"; earlier * take the shortest match; without a match the string is returned unchanged
url_encode(s)StringEncodes every byte other than the RFC 3986 unreserved characters (letters, digits, and - . _ ~) as %XX (uppercase hexadecimal)
base64_encode(s)StringStandard alphabet with padding
base64_decode(s)StringAccepts the standard and the URL-safe alphabet (mixed too), with or without padding, and ignores unused trailing bits; other characters, whitespace, misplaced or excess padding, or a length that leaves 1 when divided by 4 give an empty string
md5(s), sha1(s), sha256(s)StringThe digest in lowercase hexadecimal
substring(s, start[, length])StringBytes from start (0-based; negative counts from the end, clamped to the first byte), up to the end without a length; empty when the start is not below the length of s. start is an integer literal from -65536 to 65536, length from 0 to 65536
to_string(x)StringIntegers in decimal, booleans as true / false, IPs as the address text the node sees, strings unchanged; the argument can be a field or function of any type
ItemRule
WhereConditions (cache rule conditions included) can use every function except regex_replace and wildcard_replace; value expressions can use all of them, regex_replace and wildcard_replace at most once each per expression
Argument typesStrings, except to_string (any type) and the start and length of substring (integer literals)
Literal argumentsPatterns, wildcards, replacements, and "s" must be string literals; wildcards and replacements are at most 1024 bytes without control characters
Replacements${1}–${8} refer to the pattern's capture groups or the wildcard's *, up to their number; groups that did not take part in the match become empty; any other $ is a literal; captures keep the case of the original string
NestingAt most 4 levels
Result lengthA function result longer than 8192 bytes fails evaluation
Evaluation failureA pattern over its execution budget, a result that is too long, or an invalid dynamic target gets the request a 503 (X-Edgeweir-Error: policy-unavailable)

IP semantics

ItemBehavior
IPv4-mapped addresses::ffff:a.b.c.d equals the IPv4 address; mapped CIDRs with a prefix shorter than 96 bits are ambiguous and refused
IPv4-compatible form::a.b.c.d is a plain IPv6 address (::1.2.3.4 is saved as ::102:304/128), not equal to IPv4
NAT64A distinct IPv6 address, not equal to IPv4
CIDRHost bits are cleared before saving
RefusedLeading zeros (octal ambiguity) and zone IDs (%)

Regular expressions

matches and regex_replace use the same subset. Console validation, node validation and node execution (PCRE) accept the same subset; constructs whose meaning differs between the engines are refused.

ItemRule
SubjectThe value's UTF-8 bytes, case-sensitive; ., [^…], \D, \W match one byte (a CJK character is 3 bytes)
Length and charactersUp to 256 printable ASCII characters; write others as \t \n \r \f or \x00–\x7f
Anchors^ start of the value; $ end of the value; \b \B ASCII word boundary
Characters. is any byte except \n; \d \D \w \W are ASCII; a backslash before ASCII punctuation matches that character; escape literal ] { }
Classes[…] [^…] of characters, \d \D \w \W and ranges such as a-z; - is literal only first or last, elsewhere \-; escape [ inside a class
Quantifiers* + ? {n} {n,} {n,m} (n ≤ m ≤ 1000, no leading zeros), optionally lazy with ?; only after a character, class or escape
Groups( ) and |; groups take no quantifier
Unsupported\s \S \v (write an explicit class such as [ \t\r\n\f]), \xHH above \x7f, \z \A \Q \p{…} \K and other escapes, backreferences and octal, groups starting with (?, possessive and stacked quantifiers, {,n}, [[:alpha:]], empty classes and []…]
Execution budgetNodes use PCRE with a match limit of 10000 and a depth limit of 100; an execution error returns 503 (X-Edgeweir-Error: policy-unavailable)

A saved rule or cache rule condition that uses a construct no longer supported (such as \s) makes saving its site or its rules fail with RULE_INVALID; rewrite it and save again. Other publications (other sites, ACME challenges, system settings) go on: the rule keeps its last compiled form and the alert "Stored rule no longer valid; its last compiled form is kept" fires; such a rule that was never compiled holds its site back from the nodes. Rules of disabled sites are not compiled.

The language is a wirefilter-style subset, not a complete wirefilter implementation.

Complexity limits

ItemLimit
Expression length4096 characters; 16384 for cache rule conditions
Tokens512
Nesting16 levels
Basic conditions, functions, and arguments128 in total
Set elements256
Function nesting4 levels
Function results8192 bytes
Rules64 per site; 32 global rules
Bulk redirects5000 per site

Bulk redirects

A site's table of exact-match redirects: each entry redirects one source to one static target. For prefix or wildcard redirects, use a redirect rule with wildcard_replace.

  1. Open Sites, select the site, and open the Bulk redirects tab.

  2. Click Add redirect, enter Source and Target, select Status code, and turn on Keep query string as needed.

  3. Or click Import, enter one entry per line in Import redirects ("One per line: source target [status]"), turn on Replace existing entries as needed, and click Import.

  4. Click Save. The console shows Saved and publishes a new configuration revision ("Rules and IP lists updated").

  5. Verify:

    curl -sI -H 'Host: www.example.com' http://<node IP>/old

    The response has the entry's status code and its target in Location.

FieldValuesDefault
Source/path (every domain of the site) or host/path (that domain only); 2–512 bytes without whitespace, ?, or control characters; the host is lowercase and one of the site's domains or one label under a wildcard domain of the site/
TargetAs a static redirect target, up to 1024 bytes/
Status code301 / 302 / 307 / 308301
Keep query stringOn / off; on appends the request's query string to the target (with & when the target has one)Off
ItemBehavior
MatchingExact match on the Host (lowercase, without port) and normalized path of the client's original request, before any rewrite; the query string takes no part; host/path entries are looked up before /path entries
OrderAfter the global and site rules of the redirect phase; a request that matched a redirect rule never reaches the table
ImportFields separated by whitespace (by commas when a line has no whitespace), status 301 by default; empty lines and lines starting with # are skipped; an entry with the same source as an existing one replaces it; Replace existing entries replaces the whole table; an invalid line shows "Line N is invalid" and nothing is imported
List50 entries per page; Filter searches sources and targets
UpdatesNodes apply them without reloading nginx
AuditChanges are audited as site.bulk_redirects_update (with the entry count)
Limit5000 entries per site, sources unique
Node requirementrules-v2, see Node capabilities and publishing

IP lists

  1. Open IP lists and click Create list.
  2. Enter Name: starts with a letter or underscore, contains only letters, digits, and underscores, 1–64 characters.
  3. Select Action: Referenced by rules, Block, or Allow.
  4. Enter entries in IP addresses and CIDRs, separated by newlines, spaces, or commas.
  5. Click Save.
  6. Verify: the list shows $name and "N entries", block and allow lists also a Block or Allow badge; reference it in rules or cache rule conditions with ip.src in $name.
ActionEffect
Referenced by rulesOnly a set for rules to reference
BlockA block list: applies to every site of every cluster without a rule; matching addresses get 403 before any rule runs
AllowAn allow list: applies to every site of every cluster; matching addresses are exempt from block lists and bans, but not from rules
ItemBehavior
NameAll lists share one namespace and names are unique; a name cannot change after creation; saving rules binds names to list IDs
ReferencesAny site rule, global rule, or cache rule condition can reference any list, block and allow lists included; the Allow lists and Block lists of L4 apps can use any list as well
L4 appsThe Block and Allow actions apply to sites only; L4 apps check only the lists they selected
ChangesEntries and Action can change at any time; creating, changing, or deleting a list publishes a new revision to every cluster ("Rules and IP lists updated"); nodes apply it without reload
DeletionA list referenced by a rule, a cache rule condition, or an L4 app cannot be deleted ("The IP list is used by …", naming the first 5 users: rule names, site rules with their site; sites whose cache rules use it; L4 app names)
EntriesIPv4 / IPv6 addresses or CIDRs; host bits cleared, deduplicated, sorted; leading zeros and zone IDs refused
QuotaUp to 128 lists and 50,000 entries in total; up to 10,000 entries per list; a change that does not add entries always saves
RollbackSite configuration rollbacks keep the current lists and global rules; a rollback that references a deleted list is refused

API: GET and POST /api/v1/ip-lists, PUT and DELETE /api/v1/ip-lists/{id}.

Node capabilities and publishing

ItemBehavior
CapabilitiesRules and block/allow lists need the node capability rules-v1; ip.geoip.country and ip.geoip.subdivision need geoip-city-v1; ip.geoip.asnum needs geoip-asn-v1; the challenge action needs challenge-v1; tls.ja4 (field or rate limit key) needs ja4-v1; when ip.geoip.subdivision is used, the console also checks geoip-subdivision-v1 (not written into the configuration)
Rule engine extensionsAny of these needs rules-v2: functions and http.request.full_uri, http.request.uri.path.extension, http.response.content_type.media_type; expression targets, query parameter edits, and a redirect with Keep query string on or a rewrite with it off; origin overrides; the compression phase; the overrides available only in the configuration phase and Gzip On; cache rule conditions not in the builder's shape and Browser TTL (s); bulk redirects; origin groups other than the default group
Expression fields and header valuesAny of these needs rules-v3: http.request.cookies[…], http.request.uri.args[…], http.referer, http.user_agent, http.request.version, http.request.scheme, http.request.id, http.request.timestamp.sec, edge.server_port, ip.geoip.as_name (also geoip-asn-v1), http.response.cache_status; url_encode, base64_encode, base64_decode, md5, sha1, sha256, substring, to_string; wildcard and strict wildcard; expression values of request headers, response headers, and query parameters; response header Append; redirect status 303; {{time}} and {{path}} in error pages
Direct peerConfigurations that read ip.peer need client-ip-v1
Existing configurationsConfigurations that use none of the extensions stay as they were and do not need rules-v2; cache rules in the builder's shape are still sent as the former structured conditions; configurations that use none of the rules-v3 items stay byte for byte the same as well
Console and AccessKeysA save is published even when an active node of the cluster lacks a required capability; such nodes keep their last-known-good configuration and Clusters & nodes shows Upgrade required, see Node upgrades
Service accounts and background jobsWhen a configuration they publish introduces a new capability, every active node of the cluster is checked, including temporarily offline ones; if any lacks it, the publish is refused (NODE_CAPABILITY_REQUIRED, "Some nodes don't support … yet: {nodes}") and the configuration and revision stay unchanged
UIWhile an active node of the cluster lacks rules-v2, the Rules, Cache, and Bulk redirects tabs show "Some nodes of the site's cluster do not support the rule extensions yet"; the extensions cannot be picked there, and settings already in place can still be changed or cleared; Bulk redirects is read-only. While one lacks rules-v3, the Rules tab shows "Some nodes of the site's cluster do not support it yet": the menus leave out the rules-v3 fields and conditions, and Expression values, Append, and 303 cannot be newly picked, while those in place can still be changed or cleared; the Error pages tab leaves out {{time}} and {{path}} and shows the same note when a template uses them
Unknown capabilitiesNodes reject configurations with unknown capabilities or enum values and keep last-known-good

Configure GeoIP databases

GeoIP fields read MMDB files on the node. Nodes download no updates and send no visitor addresses to data vendors.

DatabaseFieldsSourceLicense and updatesAttribution
IPinfo LiteCountry, ASNBundled in node release images; package and archive nodes download it from IPinfo (free account)CC BY-SA 4.0; IPinfo updates daily, images carry a snapshot from their build dayIP address data is powered by IPinfo
City MMDB, e.g. DB-IP Lite CityCountry, first-level subdivisionDownloaded by the operatorDB-IP Lite: CC BY 4.0, monthly updatesIP Geolocation by DB-IP
ASN MMDB, e.g. DB-IP Lite ASNASNDownloaded by the operatorSame as aboveSame as above
ItemBehavior
PrecedenceCountry and ASN come from IPinfo Lite first, then from the City / ASN MMDB when IPinfo has no record
SubdivisionComes only from the City MMDB, and only when the City MMDB's country matches the final country
Bundled data/usr/share/edgeweir-node/geoip/ipinfo_lite.mmdb, checked at build time against the sha256 IPinfo publishes; NOTICE in the same directory records the download time and sha256
Console attributionProtection settings → GeoIP databases carries the IPinfo attribution link
  1. Container nodes running a release image need no configuration for country and ASN; for newer data, pull a newer image, or mount a separately downloaded copy and set EDGEWEIR_GEOIP_IPINFO.

  2. Package or archive nodes: download ipinfo_lite.mmdb from IPinfo. To match on subdivisions, also download a City MMDB. Check source, license, and integrity, and record the download date.

  3. Put the files in a read-only directory the node can read, and set the node environment variables:

    /etc/default/edgeweir-node
    EDGEWEIR_GEOIP_IPINFO=/etc/edgeweir-node/geoip/ipinfo_lite.mmdb
    EDGEWEIR_GEOIP_CITY=/etc/edgeweir-node/geoip/dbip-city-lite.mmdb

    For container deployments, mount the directory and pass the same variables with -e.

  4. Restart the node to load the databases:

    sudo systemctl restart edgeweir-node
  5. Verify: Protection settings → GeoIP databases shows "Country: Ready" and "ASN: Ready" for the node, plus "Subdivision: Ready" when a City MMDB is configured.

VariableFlagDefaultDescription
EDGEWEIR_GEOIP_IPINFO--geoip-ipinfoautoIPinfo Lite MMDB path; auto uses the database bundled in the image when present, off disables it
EDGEWEIR_GEOIP_CITY--geoip-cityEmptyCity MMDB path
EDGEWEIR_GEOIP_ASN--geoip-asnEmptyASN MMDB path
ItemBehavior
Capability reportingA node with country data (IPinfo Lite or a City MMDB) reports geoip-city-v1, one with a City MMDB reports geoip-subdivision-v1, one with ASN data reports geoip-asn-v1; current nodes also report geoip-country-v1
Older nodesA node that does not report geoip-country-v1 reports geoip-city-v1 only with a City MMDB, and the console treats it as having geoip-subdivision-v1
Missing capabilityA node rejects configurations that use GeoIP fields it lacks and keeps last-known-good
Invalid fileThe node agent does not start when a configured MMDB file is invalid or of the wrong database type; an invalid bundled IPinfo Lite database is logged and left unused
LookupsThe agent reads the files and serves results to Lua workers over a local Unix socket with mode 0600; each worker caches up to 10,000 results for 5 minutes; a lookup times out after 200 milliseconds
Lookup failureWhen site rules, global rules (value expressions included), or cache rule conditions use GeoIP fields, every request of that site needs a lookup; while the service is unavailable, those requests get 503
UpdatesReplace the file or image on one node, restart, and verify before updating the others; never overwrite an MMDB file in use

Limits

ItemDescription
Rate limitingPer-node fixed windows only; no network-wide quota and no sliding window
ExpressionsA wirefilter-style subset; built-in functions only, no custom functions or raw Lua
String replacementregex_replace and wildcard_replace only in value expressions (redirect targets, rewrite paths, header values, and query parameter values), once each per expression; regex_replace replaces only the first match
Cookies and query parametersRead by name, the first value, not decoded; there is no list of every cookie or parameter, and names take no wildcards
Bulk redirectsExact matches only, static targets
Protected headersSee Action fields; rules cannot change them
CompressionOverride settings and compression rules choose only among the algorithms the site has on
Origin groupsThe cache key does not include the origin group
GeoIP dataThe bundled IPinfo Lite database is a snapshot from the image build day; subdivisions need an operator-provided City MMDB; accuracy depends on the chosen database

Troubleshooting

SymptomCauseAction
"Character N: reason" below the editorUnsupported syntax, field, type, function, or regular expression at that position; the reason names the problemFix it using the syntax tables above
Saving shows "Check field of rule “name”"That field of the rule is invalid, for example the target format, a repeated parameter name, or a parameter both set and removed; an invalid expression shows the position and reasonFix it using the action field table
Saving shows "Invalid rule"The action does not belong to the phase, a protected header, or an invalid redirect target or rewrite path; an origin override picks an origin group the site does not haveFix it using the action field table; add an origin of that group on the Origins tab first
"The site does not serve …"A bulk redirect source names a host that is not a domain of the siteUse a domain of the site, or write /path
"Line N is invalid"The field count, source, target, or status code of that imported line is invalidFix the line and import again
"N invalid"The bulk redirect table has invalid entries or repeated sourcesFix the marked entries
"Some nodes of the site's cluster do not support the rule extensions yet"An active node of the cluster lacks rules-v2Upgrade the nodes, see Node upgrades
"No such IP list: …"The referenced lists (named) do not existCreate the list in IP lists first, or fix the name
"IP list name already exists"A list with that name existsUse another name
"The IP list is used by …"Deleting a list the listed rules, sites' cache rule conditions, or L4 apps still referenceRemove the reference from those rules and L4 apps first
"IP list limit reached (128 lists, 50,000 entries)"Over quotaMerge or delete lists
A node shows Upgrade requiredThe node lacks a capability the configuration needs (rules-v1, rules-v2, a GeoIP capability, and so on) and keeps its last-known-good configurationUpgrade the node or configure the GeoIP databases
"Some nodes don't support … yet: {nodes}"A configuration published by a service account or a background job needs rules-v1, rules-v2, or a GeoIP capability that an active node of the cluster lacksUpgrade the nodes or configure the GeoIP databases
503 with X-Edgeweir-Error: policy-unavailableA regular expression exceeded its budget, a function result exceeded 8192 bytes, a dynamic target, rewrite path, or query parameter value expression was invalid, or a GeoIP lookup failedSimplify the pattern or expression; check what the value expression computes; check the node's GeoIP service
A request or response header with an expression value is missing, and the node logs header value skipped site=… rule=…The value the rule computed exceeds 4096 bytes, has a control character, or its evaluation failed; the node skips that header actionLimit or clean the value with substring, url_encode, and the like; a rule logs once per 60 seconds
"Some nodes of the site's cluster do not support it yet" (Rules tab)An active node of the cluster lacks rules-v3Upgrade the nodes, see Node upgrades
A rule that redirects HTTP to HTTPS makes requests return 503The site has no certificateSelect a certificate on the HTTPS tab
Rate limits are not shared across nodesRate limits count per nodeScale the threshold by the number of nodes
Some visitors are not rate limited and the node log shows rate limit partition fullThe site's rate-limit partition is full and new clients are not countedRaise the node flag --rate-limit-dict-kb
Edit on GitHub

On this page