This guide assumes a working Zabbix server (the architecture post covers the larger picture) and at least one web application you'd like to monitor end-to-end. No agent install is needed web scenarios run from the server or a proxy.

Get-Service style monitoring tells you the process is running. Synthetic web checks tell you the user experience is working that the load balancer routes correctly, the auth flow returns a session, the app responds in time, and the page contains what it should. Zabbix has had this since 2.0; almost nobody uses it past "ping the homepage".

What a Web Scenario Is

A scenario is an ordered list of HTTP steps. Each step:

  • Makes a request (URL, method, headers, body).
  • Asserts on the response (status code, response time, content match).
  • Optionally extracts variables for later steps.

The whole scenario produces metrics: per-step success/failure, response time, transferred bytes and a pass/fail aggregate.

Create a Scenario

In Data Collection -> Hosts -> {host} -> Web -> Create scenario:

  • Name: Customer login flow
  • Application: web (groups related items together)
  • Update interval: 60s (don't go below 30s without good reason)
  • Attempts: 3 (retries on transient failure)
  • Agent: pick a realistic User-Agent string
  • Variables: pre-populate {username}, {password}, etc.
  • Headers: persistent headers across all steps (e.g. Accept: application/json)

Then add steps under the Steps tab.

A Login Flow Three Steps

Step 1: GET the login page (extract the CSRF token).

  • URL: https://app.example.com/login

  • Required status codes: 200

  • Required string: name="csrf"

  • Variables:

    {csrf}=regex:name="csrf"\s+value="([^"]+)"
    

The {csrf} variable is now populated from the HTML for use by step 2.

Step 2: POST credentials with the CSRF token.

  • URL: https://app.example.com/login

  • Type: POST

  • Post fields:

    username={username}
    password={password}
    csrf={csrf}
    
  • Required status codes: 302 (most apps redirect on success)

  • Follow redirects: tick

Step 3: GET an authenticated endpoint.

  • URL: https://app.example.com/api/me
  • Required status codes: 200
  • Required string: "user_id"

Cookie state is automatic across steps. The session cookie set by step 2 carries into step 3 without you doing anything. That's the whole point of multi-step scenarios.

Required Status Codes Be Specific

Required status codes: 200,204

Not 200-399. A 301 from your auth endpoint when you expected 302 is a real bug you want the trigger to fire. Specific status codes in scenarios are a feature, not pedantry.

Required String Content Assertions

A 200 response that returns the wrong content is the most common production bug that simple ping-style monitoring misses entirely. The Required string field is a regex; a step fails if the response body doesn't match.

Required string: "version":\s*"\d+\.\d+\.\d+"

Useful idioms:

  • "status"\s*:\s*"ok" JSON health endpoint
  • <title>Dashboard</title> page identity
  • Powered by Nginx wrong content type returned (proxy is serving the upstream's error page)

Hiding Secrets User Macros

Don't paste passwords into the scenario UI. Use secret macros at the host (or template) level:

Data Collection -> Hosts -> {host} -> Macros:

  • Name: {$WEB_USERNAME}, Type: Text, Value: monitor-bot
  • Name: {$WEB_PASSWORD}, Type: Secret text, Value:

Reference them in the scenario's Variables section:

{username}={$WEB_USERNAME}
{password}={$WEB_PASSWORD}

Secret macros are write-only in the UI. Once saved, they show as ******. Anyone with read access to the host config sees the macro name but not the value. This is what makes web scenarios safe to commit to template-as-code workflows.

What You Get For Free Items

After you save a scenario, Zabbix auto-creates these items per scenario plus per step:

Item What it measures
web.test.in[Scenario,,bps] Inbound bandwidth
web.test.fail[Scenario] Step number that failed (0 if ok)
web.test.error[Scenario] Last error message
web.test.in[Scenario,Step,bps] Per-step bandwidth
web.test.rspcode[Scenario,Step] Step's HTTP response code
web.test.time[Scenario,Step,resp] Step's response time

You don't create these by hand. They show up under Latest data immediately.

Triggers Worth Adding

last(/host/web.test.fail[Login]) <> 0

Any failure. The web.test.fail value is the step number (1, 2, 3) so you also know which step broke.

avg(/host/web.test.time[Login,Step3,resp],5m) > 2

P50 latency on step 3 over five minutes exceeded 2 seconds. SLA-style alerting.

nodata(/host/web.test.fail[Login],10m) = 1

The scenario didn't run in the last 10 minutes probably the proxy is dead, not the app. Different from "the app is broken", needs a different action.

Running From the Right Place

By default scenarios run on the server. For an app that has different behavior depending on which network it's reached from (geo-routing, internal vs external endpoints, behind-VPN admin pages), run the scenario from a proxy in that network:

Data Collection -> Hosts -> {host} -> set Monitored by proxy to proxy-eu-west.

The same scenario now executes from the EU proxy, hitting whatever DNS/routing the proxy sees. For multi-region SLA reporting, deploy one host per region all monitoring the same scenario, each via its local proxy.

SLA Reporting Service Tree

Zabbix 6+ has a built-in Services view that turns trigger states into SLA-style reports.

Services -> Edit -> Create service:

  • Name: Customer Portal
  • Status calculation rule: Most critical of child services
  • Add children: per-region scenarios, DB up-trigger, payment gateway up-trigger.

Now Reports -> SLA reports shows monthly uptime per service. The same definition produces both the dashboard widget and the executive PDF.

Multi-Step + JSON API

For pure-API monitoring, replace the form-encoded login with a JSON request:

Step 1 POST JSON to login:

  • URL: https://api.example.com/auth/token

  • Type: POST

  • Body type: Raw data

  • Headers: Content-Type: application/json

  • Raw post:

    {"user":"{$API_USER}","password":"{$API_PASSWORD}"}
    
  • Variables:

    {token}=regex:"access_token":"([^"]+)"
    

Step 2 GET with bearer token:

  • URL: https://api.example.com/v1/health
  • Headers: Authorization: Bearer {token}
  • Required string: "healthy":\s*true

The same pattern works for any modern API. A handful of these per service is enough to catch nearly every external-symptom outage before customers do.

Operational Notes

  • Don't reuse a real user account. Create a monitor-bot (or synthetic-user@) account with the minimum permissions the scenario needs. Real-user creds ending up in scenario logs is a real risk.
  • Proxy-side DNS matters. A scenario that resolves correctly from the server but not the proxy will fail on the proxy. Test with nslookup from the proxy host before pointing scenarios at it.
  • TLS issues are common. Verify peer and Verify host default off. Turn them on for production scenarios that's how you catch the cert expired before the customers do.
  • Don't poll faster than the app's caching layer. A 10s interval against a CDN-cached endpoint just measures the CDN. Pick an interval that exercises the cache miss path occasionally.
  • Combine with the quieting alerts post. Synthetic checks fire false positives during scheduled maintenance windows; mute the right scenarios during deploys.

What to Do Next

A web scenario is the closest Zabbix gets to "what would a customer see right now". Multi-step flows, content assertions, secret macros for credentials, per-region proxies for geo-coverage, and the Services tree for SLA reporting. None of it requires an extra product or a paid SaaS it's been in Zabbix for a decade. The hardest part is deciding what your "customer journey" actually is; once you have that, it's an evening of clicking and you have synthetic monitoring that beats most third-party tools.

Three concrete moves to deploy synthetic monitoring this week:

  1. Write down the one customer journey you most want to know is alive. Login, search, checkout, queue submission whatever your app's "this is broken means the business is down" flow is. Pick one. Don't try to monitor the whole site.
  2. Run the scenario through two proxies in different regions. A single-region check tells you "the app is up". A two-region check tells you "the app is up and the network path from the customer reaches it" which is the answer you actually want at 3 AM.
  3. Turn on Verify peer and Verify host from day one. The default-off TLS verification means an expired cert silently passes the synthetic check. Catching the expired cert before customers do is half the value of synthetic monitoring; don't disable it.

Pairs naturally with the quieting alerts post (synthetic checks fire false positives during deploys; mute them deliberately) and the PSK and TLS post (the same TLS hygiene you apply to your monitoring traffic should apply to the apps you're monitoring).