Synthetic vs real user monitoring: a step-by-step guide
Your application is performing beautifully in your lab environment, but users across the globe report intermittent slowdowns and timeouts. You have dashboards lighting up with metrics, yet you still can't pinpoint whether the problem is with your infrastructure, the user's network, or somewhere in between. This is where understanding the difference between synthetic monitoring and real user monitoring becomes essential for building reliable systems at scale.
In this 94th installment of our monitoring series, we'll explore when and how to deploy both synthetic and real user monitoring strategies. You'll learn to combine scripted, controlled checks with genuine user telemetry to get a complete picture of your application's health and performance.
What is synthetic monitoring?
Synthetic monitoring runs automated, scripted checks against your application from predefined locations and network conditions. Think of it as a tireless bot that performs the same user journey repeatedly, 24/7, measuring response times and success rates with precision.
Synthetic checks are deterministic. You control the test script, the geographical locations, the network conditions, and the timing. This consistency makes synthetic monitoring excellent for detecting outages before they affect real users. A common implementation uses tools like Prometheus Blackbox Exporter or commercial offerings that execute HTTP requests, DNS lookups, and TCP connections on a schedule.
Here's a simple example using Prometheus Blackbox configuration:
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1", "HTTP/2"]
valid_status_codes: [200, 201]
method: GET
headers:
User-Agent: "Prometheus Blackbox"
global:
scrape_interval: 30s
scrape_timeout: 10s
scrape_configs:
- job_name: 'synthetic-checks'
static_configs:
- targets:
- https://api.example.com/health
- https://api.example.com/checkout
- https://api.example.com/payments
relabel_configs:
- source_labels: [__address__]
target_label: instance
- source_labels: [__scheme__]
target_label: scheme
metric_relabel_configs:
- source_labels: [__name__]
regex: 'probe_.*'
action: keepThis configuration runs HTTP checks every 30 seconds against three critical endpoints. Grafana can then visualize probe_success and probe_duration_seconds metrics to surface availability trends instantly.
What is real user monitoring?
Real user monitoring (RUM) captures actual browser and application telemetry from genuine users on their devices, networks, and in their real locations. Unlike synthetic scripts, RUM reflects the messy reality of the Internet: variable latencies, different device capabilities, and unpredictable network conditions.
RUM typically involves instrumenting your frontend with a lightweight SDK that collects metrics like Core Web Vitals, navigation timing, resource load times, and error events. When a user loads your page or interacts with your app, the SDK sends this raw telemetry to your monitoring backend.
Consider a checkout flow in an e-commerce platform. A synthetic check might verify that your payment API responds in under 200ms from a datacenter in us-east-1. But RUM data reveals that users on 4G networks from rural areas see 2-3 second latencies, and 15% of them abandon their carts because your progress indicator doesn't feel responsive enough. Synthetic monitoring would have missed this entirely.
Instrumenting RUM is straightforward with libraries like web-vitals or @opentelemetry/sdk-trace-web. Here's a minimal example:
// Minimal RUM instrumentation
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals';
const sendMetric = (metric) => {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
delta: metric.delta,
timestamp: new Date().toISOString(),
url: window.location.href,
user_id: getCurrentUserId(),
});
// Send via beacon or fetch
navigator.sendBeacon(
'https://rum.example.com/v1/metrics',
body
);
};
getCLS(sendMetric);
getFID(sendMetric);
getFCP(sendMetric);
getLCP(sendMetric);
getTTFB(sendMetric);Each metric sent includes Core Web Vitals data along with context like the page URL and user ID. Over time, you accumulate a rich dataset showing how real users experience your application under diverse conditions.
Using synthetic monitoring for availability
Synthetic monitoring excels at detecting availability issues and validating that critical paths work end-to-end. Your goal is to catch outages in minutes, not hours, and alert the team before customer support tickets pile up.
A reliable synthetic strategy focuses on your highest-value user journeys. For a SaaS platform, this might be login, creating a resource, and triggering an important API call. For an e-commerce site, it's browse → add to cart → checkout → payment confirmation.
Set up separate checks for each environment and region you care about. If you serve users in APAC and Europe, run checks from datacenters in both regions. This helps you distinguish between regional outages and global failures.
Alerting rules should be aggressive but not hair-trigger. A single failed probe can be noise; three consecutive failures is worth waking someone up. Here's an example Prometheus alert rule:
groups:
- name: synthetic_alerts
rules:
- alert: SyntheticCheckFailure
expr: increase(probe_success{job="synthetic-checks"}[5m]) == 0
for: 2m
annotations:
summary: "Synthetic check failing: {{ $labels.instance }}"
description: "{{ $labels.instance }} has failed 3+ times in 5 minutes"
- alert: SyntheticHighLatency
expr: probe_duration_seconds{job="synthetic-checks"} > 3
for: 5m
annotations:
summary: "High latency on {{ $labels.instance }}"
description: "Response time exceeded 3s for 5 minutes"Note that synthetic checks measure server and network latency, not browser rendering time. A synthetic check might show your API is fast, but if your frontend JavaScript is slow to parse or hydrate, real users will still suffer.
Using real user monitoring for performance insights
RUM tells you what users actually experience. It captures the full transaction lifecycle: DNS resolution, TCP connection, TLS handshake, HTTP request, response, and browser rendering. This end-to-end view is impossible to replicate synthetically without enormous infrastructure overhead.
Performance budgets are a practical use of RUM data. Set a target like "95th percentile LCP under 2.5 seconds" and track it in Grafana. When the metric drifts, investigate. Was a new frontend dependency added? Did traffic patterns shift to a slower demographic? Is a third-party script degrading performance?
RUM also surfaces issues that synthetic checks cannot detect. Examples include:
- JavaScript errors in specific browsers or on specific user devices
- Slow third-party integrations (analytics, ads, payment providers)
- Network timeouts from cellular connections with high packet loss
- Memory pressure causing garbage collection pauses
- Rendering jank or layout shifts during heavy user interaction
When building a RUM pipeline, sample strategically. Collecting 100% of sessions from every user is expensive and often unnecessary. A 10% sample of all sessions, stratified by geography and device type, typically provides sufficient signal. You can increase sampling for error events or specific user cohorts.
Correlate RUM metrics with your business outcomes. In a travel app, longer page load times correlate with lower conversion rates on the flight booking flow. By analyzing RUM percentiles and business metrics side-by-side, you can justify performance optimization investments.
Combining synthetic and real user monitoring
The two approaches are complementary, not competitive. Synthetic tells you if something is broken; RUM tells you how broken it feels to actual users.
Use synthetic monitoring to:
- Alert on availability issues immediately
- Validate API contracts and HTTP status codes
- Test from multiple geographic regions consistently
- Perform transaction-level checks (login flow, payment processing)
- Catch issues before they reach users (during deployments, config changes)
Use real user monitoring to:
- Identify performance bottlenecks experienced by real users
- Track Core Web Vitals and business-critical metrics
- Understand browser and device-specific issues
- Correlate user experience with external factors (time of day, network type, geography)
- Spot trends and regressions over time
In practice, you might alert on synthetic failures immediately but reserve performance-based alerts for RUM percentiles. A single slow RUM request is noise; the 95th percentile crossing a budget is actionable.
Common mistakes
Relying only on synthetic checks: Many teams instrument synthetic monitoring and assume they're done. But synthetic checks run from your datacenter under controlled conditions. Users behind firewalls, on poor networks, or using older browsers will report problems that synthetic monitoring never detected.
Collecting 100% of RUM data: Sending every user interaction to your backend is expensive and creates noise. Sample intelligently. A 5-10% sample of successful sessions plus 100% of errors strikes a good balance.
Not correlating synthetic and RUM data: When something goes wrong, compare both datasets. If synthetic checks pass but RUM metrics degrade, the issue is likely client-side or network-related, not server availability. This correlation saves debugging time.
Ignoring geography and device diversity: A user on a 3G network from India will have vastly different performance characteristics than a user in a modern office on 5G. Use RUM to segment by location, device, and network type. Investigate outliers.
Setting alerts on synthetic latency without context: An API that responds in 500ms is fast by infrastructure standards but might feel slow if it's being called 50 times per page load. Alert on business-level metrics (conversion rate, checkout completion time) instead of raw latencies.
Key takeaways
- Synthetic monitoring runs scripted, deterministic checks to catch availability issues and ensure critical user journeys work end-to-end.
- Real user monitoring captures actual telemetry from browsers and devices in the wild, revealing performance issues that synthetic checks miss.
- Use synthetic for rapid alerting on outages and infrastructure problems; use RUM to understand what real users experience and identify optimization opportunities.
- Sample RUM data strategically (5-10% for success, 100% for errors) to balance cost and visibility.
- Always correlate synthetic and RUM data when investigating incidents; each tells part of the story.
- Set synthetic alerts on availability and business-critical transaction paths; set RUM alerts on performance budgets and percentiles, not individual requests.
- Segment RUM data by geography, device type, and network condition to surface real user diversity that synthetic checks cannot replicate.






Comments
Loading comments…