Real User Monitoring (RUM) measures Core Web Vitals in the browsers of your real visitors. You don't change your site: the edge adds a small script to your HTML pages, and the browser reports to your own domain. RUM is off by default and can be set per zone and per name.
How it works
- The edge adds
<script defer src="/__waf/rum.js?v=…">before</head>of HTML pages. The script is about 5 KB compressed and served by the edge itself. - The browser measures LCP, INP, CLS, FCP and TTFB while the page is open.
- When the page is hidden (tab switched, closed or navigated away), it sends one beacon to
/__waf/rumon your domain. Later reports of the same view only carry what changed, usually INP and CLS. - The edge validates the beacon, adds the device class and country, discards the IP address and User-Agent, and forwards it. Data shows up in the report about a minute later.
The script is only added to 200 answers to GET requests with an HTML Content-Type, outside excluded paths, never to pages the edge generates (challenges, block pages, access gate) and according to the sample rate. It is added when the page is served, so it also reaches pages served from the edge cache, and cached copies never contain it. Compressed pages are decompressed, injected and compressed again.
Turn it on
Go to Analytics › Real performance, choose a Sample rate and select Enable. Afterwards, the Settings of the page let you change the sample rate and the Excluded paths, or Turn off measurement.
openwaf-cloud ctl zones set example.com rum.enabled=true rum.sample_rate=0.25 rum.exclude_paths=/wp-admin/,/wp-login.php --reason "measure web vitals"rum.sample_rate is read as a number and rum.exclude_paths as a comma-separated list (rum.exclude_paths= empties it).
curl -s -X PATCH https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/edge \
-H "Authorization: Bearer $OPENWAF_TOKEN" \
-H "Content-Type: application/json" \
-H "X-Change-Reason: measure web vitals" \
-d '{"rum": {"enabled": true, "sample_rate": 0.25, "exclude_paths": ["/wp-admin/"]}}'update_settings with {"zone": "example.com", "settings": {"rum": {"enabled": true}}, "reason": "measure web vitals"}, first confirm=false, then confirm=true with expected_version.
Like every edge setting, each change creates a zone version, is recorded in the audit log and can be reverted.
Settings
| Key | Values | Default |
|---|---|---|
rum.enabled | true, false | false |
rum.sample_rate | More than 0 and at most 1: the share of page views that get the script (0.25 = one in four). | 1 |
rum.exclude_paths | Path prefixes that never get the script. | [] |
Values are validated even while RUM is off: a sample_rate of 0 or above 1, a relative path or an unknown field is rejected with 422.
Excluded paths
Up to 50 prefixes. Each one starts with /, is at most 256 bytes, has no ? or # and appears only once; spaces are trimmed and empty lines dropped. Exclude pages you don't want measured, for example /admin or /checkout.
Per name
Each field set for a name replaces the zone's for that name:
openwaf-cloud ctl zones set example.com --host shop.example.com rum.sample_rate=1 --reason "measure the whole shop"
openwaf-cloud ctl zones set example.com --host admin.example.com rum.enabled=false --reason "no RUM on the admin"
openwaf-cloud ctl zones set example.com --host admin.example.com --unset rum --reason "inherit the zone"In the API: {"hosts": {"admin.example.com": {"rum": {"enabled": false}}}}; {"rum": null} inside the name inherits the zone again.
Content-Security-Policy
If your origin sends a Content-Security-Policy, it must let the script load and report to your own origin:
- allow
'self'inscript-srcandconnect-src, or - send a
nonceinscript-src: the edge reuses it for its script.
When the policy would block the script, the edge doesn't inject it, and the report warns how many page views weren't measured because of the CSP.
Read the report
Analytics › Real performance is laid out like the field data of PageSpeed Insights:
- device tabs Mobile (default), Desktop, Tablet (only when there are visits) and All;
- This page or Whole site;
- the Core Web Vitals Assessment and one tile per metric with its p75 and the share of good, needs-improvement and poor views (Expand view adds thresholds, trend and sample size);
- Diagnose performance issues: LCP breakdown, LCP element, interactions and scripts behind INP, layout shifts, and breakdowns by page, country, edge node, connection and over time.
Filters (period 24 h, 7 or 30 days; country; edge node; page) live in the address, so you can share a view.
openwaf-cloud ctl rum report example.com # last 7 days
openwaf-cloud ctl rum report example.com --since 24h --device mobile --country ES
openwaf-cloud ctl rum report example.com --since 30d --by country --jsonOptions: --since/--until (a duration such as 24h or 7d, or RFC 3339), --host, --path, --country, --node, --device, --connection, --nav-type and --by path|country|node|device|connection. It prints the p75 of each metric with its rating, the distribution, the comparison with the previous period, the LCP breakdown, the chosen breakdown and the worst elements; --json prints the API answer.
curl -s "https://dash.openwaf.dev/v1/orgs/$ORG/zones/$ZONE/rum/report?since=2026-10-20T00:00:00Z&device=mobile" \
-H "Authorization: Bearer $OPENWAF_TOKEN"See the RUM API reference for every route and parameter.
rum_report is read-only. Ask, for example, "How is LCP on mobile in Spain this week?"; the assistant calls:
{"zone": "example.com", "period": "7d", "device": "mobile", "country": "ES"}period is 24h, 7d (default) or 30d; filters device, country, node, path, host, connection, nav_type and by work as in the API.