{"id":55,"date":"2026-01-11T22:11:34","date_gmt":"2026-01-11T22:11:34","guid":{"rendered":"https:\/\/platformsignals.dev\/?p=55"},"modified":"2026-01-12T11:31:27","modified_gmt":"2026-01-12T11:31:27","slug":"building-multi-region-synthetic-monitoring-with-grafana-open-source-for-free","status":"publish","type":"post","link":"https:\/\/platformsignals.dev\/?p=55","title":{"rendered":"Building Multi-Region Synthetic Monitoring with Grafana Open Source (For Free)"},"content":{"rendered":"\n\n\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Multi-Region Synthetics | Platform Signals<\/title>\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n    <link href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.0.0\/css\/all.min.css\" rel=\"stylesheet\">\n    <link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n    <link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@300;400;600;700&#038;family=JetBrains+Mono:wght@400;700&#038;display=swap\" rel=\"stylesheet\">\n    <script>\n        tailwind.config = {\n            theme: {\n                extend: {\n                    fontFamily: {\n                        sans: ['Inter', 'sans-serif'],\n                        mono: ['JetBrains Mono', 'monospace'],\n                    },\n                    colors: {\n                        slate: {\n                            850: '#151f2e',\n                            900: '#0f172a',\n                            950: '#020617',\n                        },\n                        emerald: {\n                            400: '#34d399',\n                            500: '#10b981',\n                            900: '#064e3b',\n                        }\n                    },\n                    typography: (theme) => ({\n                        DEFAULT: {\n                            css: {\n                                color: theme('colors.slate.300'),\n                                h1: { color: theme('colors.white') },\n                                h2: { color: theme('colors.white') },\n                                h3: { color: theme('colors.slate.100') },\n                                strong: { color: theme('colors.white') },\n                                code: { color: theme('colors.emerald.400') },\n                            },\n                        },\n                    }),\n                }\n            }\n        }\n    <\/script>\n    <style>\n        body {\n            background-color: #475569;\n            color: #e2e8f0;\n        }\n        \/* Custom scrollbar for code blocks *\/\n        pre::-webkit-scrollbar {\n            height: 8px;\n        }\n        pre::-webkit-scrollbar-track {\n            background: #1e293b;\n        }\n        pre::-webkit-scrollbar-thumb {\n            background: #334155;\n            border-radius: 4px;\n        }\n        .prose h2 {\n            font-size: 1.5rem;\n            font-weight: 700;\n            margin-top: 2.5rem;\n            margin-bottom: 1rem;\n            letter-spacing: -0.025em;\n        }\n        .prose h3 {\n            font-size: 1.25rem;\n            font-weight: 600;\n            margin-top: 2rem;\n            margin-bottom: 0.75rem;\n        }\n        .prose p {\n            margin-bottom: 1.25rem;\n            line-height: 1.75;\n        }\n        .prose ul {\n            list-style-type: disc;\n            padding-left: 1.5rem;\n            margin-bottom: 1.25rem;\n        }\n        .prose li {\n            margin-bottom: 0.5rem;\n        }\n    <\/style>\n<\/head>\n<body class=\"antialiased min-h-screen flex flex-col\">\n\n    <!-- Navigation -->\n    <nav class=\"border-b border-slate-800 bg-slate-900\/90 backdrop-blur sticky top-0 z-50\">\n        <div class=\"max-w-4xl mx-auto px-6 py-4 flex justify-between items-center\">\n            <a href=\"#\" class=\"font-mono font-bold text-lg text-emerald-400 tracking-tight flex items-center gap-2\">\n                <i class=\"fa-solid fa-signal text-sm\"><\/i> PLATFORM_SIGNALS\n            <\/a>\n            <div class=\"hidden md:flex gap-6 text-sm font-medium text-slate-400\">\n               \n                <a href=\"about.html\" class=\"hover:text-emerald-400 transition-colors\">About<\/a>\n                <a href=\"contact.html\" class=\"hover:text-emerald-400 transition-colors\">Contact<\/a>\n            <\/div>\n        <\/div>\n    <\/nav>\n\n    <!-- Main Content -->\n    <main class=\"flex-grow\">\n        <article class=\"max-w-3xl mx-auto px-6 py-16\">\n            \n            <!-- Article Header -->\n            <header class=\"mb-12 border-b border-slate-800 pb-10\">\n                <div class=\"flex flex-wrap gap-4 text-xs font-mono mb-6 uppercase tracking-wider\">\n                    <span class=\"text-emerald-400 bg-emerald-900\/20 px-2 py-1 rounded border border-emerald-900\/50\">\n                        <i class=\"fa-solid fa-eye mr-1\"><\/i> Observability\n                    <\/span>\n                    <span class=\"text-slate-500 flex items-center\">\n                        <i class=\"fa-regular fa-calendar mr-2\"><\/i> Jan 11, 2026\n                    <\/span>\n                    <span class=\"text-slate-500 flex items-center\">\n                        <i class=\"fa-regular fa-user mr-2\"><\/i> the_silent_node\n                    <\/span>\n                <\/div>\n                \n               \n                \n                <p class=\"text-xl text-slate-400 font-light leading-relaxed\">\n                    Why synthetic monitoring is expensive because it&#8217;s outsourced, not because it&#8217;s hard. Here is how to take ownership back.\n                <\/p>\n            <\/header>\n\n            <!-- Article Body -->\n            <div class=\"prose text-slate-300\">\n                \n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">01.<\/span>\n                    Why synthetic monitoring doesn\u2019t need to be expensive\n                <\/h2>\n                <p>\n                    Most discussions around synthetic monitoring immediately lead to SaaS tools: Datadog, Dynatrace, New Relic \u2014 all powerful, all expensive, all usage-based.\n                <\/p>\n                <p>\n                    But here\u2019s the uncomfortable truth: <strong>Synthetic monitoring is not expensive because it\u2019s hard. It\u2019s expensive because it\u2019s usually outsourced.<\/strong>\n                <\/p>\n                <div class=\"bg-slate-800\/50 border-l-4 border-emerald-500 p-4 my-6 text-slate-200 italic\">\n                    This is not a \u201ccheap alternative\u201d \u2014 it\u2019s an ownership-first design, the kind real SRE teams use.\n                <\/div>\n                <p>\n                    In this post, I\u2019ll show how to build private, multi-region synthetic monitoring using Grafana Open Source tools, with:\n                <\/p>\n                <ul class=\"list-disc pl-5 space-y-2 marker:text-emerald-500\">\n                    <li>Real browser tests<\/li>\n                    <li>Multiple geographic vantage points<\/li>\n                    <li>No SaaS, No trials, No money<\/li>\n                <\/ul>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">02.<\/span>\n                    The problem with traditional uptime monitoring\n                <\/h2>\n                <p>\n                    Classic monitoring asks: <em>\u201cIs the server responding?\u201d<\/em><br>\n                    Users ask: <em>\u201cCan I actually use the site?\u201d<\/em>\n                <\/p>\n                <p>\n                    These are not the same question. A website can return HTTP 200, have healthy servers, and show green dashboards, yet still be unusable because JavaScript fails, navigation breaks, or only one region is affected. This gap is exactly where <strong>synthetic monitoring<\/strong> lives.\n                <\/p>\n\n                <h3 class=\"text-white mt-8 mb-4\">What we\u2019re going to build<\/h3>\n                <p>We\u2019ll build a system that answers one question: <em>\u201cCan a real user complete a real journey from different parts of the world?\u201d<\/em><\/p>\n                \n                <div class=\"bg-slate-900 border border-slate-700 rounded-lg p-6 my-6\">\n                    <h4 class=\"text-white font-mono text-sm uppercase mb-4 border-b border-slate-700 pb-2\">The Stack (100% Open Source)<\/h4>\n                    <ul class=\"space-y-3 font-mono text-sm text-slate-400\">\n                        <li class=\"flex items-start gap-2\">\n                            <span class=\"text-emerald-400\">\u279c<\/span>\n                            <span><strong class=\"text-slate-200\">k6 OSS (browser mode)<\/strong>: runs real Chromium journeys<\/span>\n                        <\/li>\n                        <li class=\"flex items-start gap-2\">\n                            <span class=\"text-emerald-400\">\u279c<\/span>\n                            <span><strong class=\"text-slate-200\">Grafana OSS<\/strong>: visualisation (optional but powerful)<\/span>\n                        <\/li>\n                        <li class=\"flex items-start gap-2\">\n                            <span class=\"text-emerald-400\">\u279c<\/span>\n                            <span><strong class=\"text-slate-200\">Your own runners<\/strong>: define the regions<\/span>\n                        <\/li>\n                        <li class=\"flex items-start gap-2\">\n                            <span class=\"text-emerald-400\">\u279c<\/span>\n                            <span><strong class=\"text-slate-200\">Cost<\/strong>: \u20b90 \/ $0 (excluding optional VPS later)<\/span>\n                        <\/li>\n                    <\/ul>\n                <\/div>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">03.<\/span>\n                    Key idea: regions are where you run, not a feature\n                <\/h2>\n                <p>\n                    This is the most important concept to understand. <strong>k6 Open Source does not \u201chave regions\u201d. Regions come from where the test is executed.<\/strong>\n                <\/p>\n                <p>\n                    If you run the same test from London, Frankfurt, and Mumbai, you have multi-region monitoring \u2014 regardless of the tool. This is how the internet actually works.\n                <\/p>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">04.<\/span>\n                    Step 1: Write a real browser synthetic journey\n                <\/h2>\n                <p>Instead of pinging an endpoint, we test a user journey. Example: Open a hub, click an article, wait like a real user.<\/p>\n\n                <!-- Code Block -->\n                <div class=\"relative my-8 group\">\n                    <div class=\"absolute -top-3 left-4 bg-slate-800 text-xs text-emerald-400 px-2 py-1 rounded border border-slate-700 font-mono z-10\">\n                        k6-browser-script.js\n                    <\/div>\n                    <pre class=\"bg-slate-900 border border-slate-800 rounded-lg p-6 pt-8 overflow-x-auto text-sm font-mono text-slate-300 leading-relaxed shadow-inner\"><code>import { browser } from 'k6\/browser';\nimport { check } from 'k6';\n\nexport const options = {\n  scenarios: {\n    journey: {\n      executor: 'shared-iterations',\n      vus: 1,\n      iterations: 1,\n      options: {\n        browser: { type: 'chromium' },\n      },\n    },\n  },\n  tags: {\n    job: 'platformsignals_browser_synthetic',\n    instance: __ENV.INSTANCE || 'local',\n  },\n};\n\nexport default async function () {\n  const page = await browser.newPage();\n\n  await page.goto(\n    'https:\/\/www.platformsignals.dev\/',\n    { waitUntil: 'domcontentloaded' }\n  );\n\n  const title = await page.title();\n  check(title, {\n    'homepage loaded': (t) =>\n      t.toLowerCase().includes('platform') ||\n      t.toLowerCase().includes('signals'),\n  });\n\n  const links = await page.$$eval('a', (as) =>\n    as\n      .map((a) => a.href)\n      .filter((h) => h && h.startsWith('https:\/\/www.platformsignals.dev'))\n      .slice(0, 3)\n  );\n\n  for (const link of links) {\n    await page.goto(link, { waitUntil: 'domcontentloaded' });\n    await page.waitForTimeout(2000); \/\/ simulate reading time\n  }\n\n  await page.close();\n}<\/code><\/pre>\n                <\/div>\n                <p>This test doesn\u2019t care about servers. It cares about <strong>experience<\/strong>.<\/p>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">05.<\/span>\n                    Step 2: Make it \u201cmulti-region\u201d\n                <\/h2>\n                <p>\n                    Here\u2019s the trick that makes everything click. You run the same script, but label <em>where<\/em> it runs via environment variables:\n                <\/p>\n                <div class=\"bg-slate-950 p-4 rounded border border-slate-800 font-mono text-sm text-emerald-300 mb-6\">\n                    <div>INSTANCE=uk-london k6 run browser-platformsignals.js<\/div>\n                    <div>INSTANCE=eu-frankfurt k6 run browser-platformsignals.js<\/div>\n                    <div>INSTANCE=ap-mumbai k6 run browser-platformsignals.js<\/div>\n                <\/div>\n                <p>\n                    <code>INSTANCE<\/code> is just a label. The machine location is the region. The script stays identical everywhere.\n                <\/p>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">06.<\/span>\n                    Visualising &#038; Alerting\n                <\/h2>\n                <p>\n                    Once you emit metrics (optional step), Grafana OSS lets you answer real questions. Is Frankfurt slower than London? Are failures regional or global?\n                <\/p>\n                <p>\n                    The key benefit isn\u2019t pretty charts \u2014 it\u2019s context. A single failure is noise. The same journey failing in two regions is a signal.\n                <\/p>\n\n                <h3 class=\"text-white mb-4\">Alerting philosophy<\/h3>\n                <p>\n                    The biggest mistake teams make is alerting on every synthetic failure. With browser tests, that\u2019s a recipe for noise.\n                <\/p>\n                <div class=\"grid md:grid-cols-2 gap-6 my-6\">\n                    <div class=\"bg-red-900\/10 border border-red-900\/30 p-4 rounded\">\n                        <h4 class=\"text-red-400 font-bold text-sm uppercase mb-2\">Do NOT Alert On<\/h4>\n                        <ul class=\"text-sm space-y-1 text-slate-400 list-none pl-0\">\n                            <li><i class=\"fa-solid fa-xmark text-red-500 mr-2\"><\/i> One failed run<\/li>\n                            <li><i class=\"fa-solid fa-xmark text-red-500 mr-2\"><\/i> One slow page<\/li>\n                            <li><i class=\"fa-solid fa-xmark text-red-500 mr-2\"><\/i> One region blip<\/li>\n                        <\/ul>\n                    <\/div>\n                    <div class=\"bg-emerald-900\/10 border border-emerald-900\/30 p-4 rounded\">\n                        <h4 class=\"text-emerald-400 font-bold text-sm uppercase mb-2\">DO Alert On<\/h4>\n                        <ul class=\"text-sm space-y-1 text-slate-400 list-none pl-0\">\n                            <li><i class=\"fa-solid fa-check text-emerald-500 mr-2\"><\/i> Same journey failing across regions<\/li>\n                            <li><i class=\"fa-solid fa-check text-emerald-500 mr-2\"><\/i> Sustained failure over time<\/li>\n                            <li><i class=\"fa-solid fa-check text-emerald-500 mr-2\"><\/i> Error budget burn<\/li>\n                        <\/ul>\n                    <\/div>\n                <\/div>\n\n                <h2 class=\"text-white flex items-center gap-2\">\n                    <span class=\"text-emerald-500\/50 text-sm font-mono\">07.<\/span>\n                    OSS vs SaaS: The Honest Comparison\n                <\/h2>\n                \n                <div class=\"overflow-x-auto mb-8\">\n                    <table class=\"w-full text-left border-collapse text-sm\">\n                        <thead>\n                            <tr class=\"border-b border-slate-700 text-emerald-400 font-mono uppercase text-xs\">\n                                <th class=\"py-3 px-4\">Aspect<\/th>\n                                <th class=\"py-3 px-4\">Grafana OSS Approach<\/th>\n                                <th class=\"py-3 px-4\">SaaS Synthetics<\/th>\n                            <\/tr>\n                        <\/thead>\n                        <tbody class=\"text-slate-300\">\n                            <tr class=\"border-b border-slate-800 hover:bg-slate-800\/30\">\n                                <td class=\"py-3 px-4 font-semibold\">Cost<\/td>\n                                <td class=\"py-3 px-4\">Free \/ Predictable<\/td>\n                                <td class=\"py-3 px-4 text-slate-500\">Usage-based ($$$)<\/td>\n                            <\/tr>\n                            <tr class=\"border-b border-slate-800 hover:bg-slate-800\/30\">\n                                <td class=\"py-3 px-4 font-semibold\">Privacy<\/td>\n                                <td class=\"py-3 px-4\">Full Control<\/td>\n                                <td class=\"py-3 px-4 text-slate-500\">Vendor-managed<\/td>\n                            <\/tr>\n                            <tr class=\"border-b border-slate-800 hover:bg-slate-800\/30\">\n                                <td class=\"py-3 px-4 font-semibold\">Regions<\/td>\n                                <td class=\"py-3 px-4\">You decide<\/td>\n                                <td class=\"py-3 px-4 text-slate-500\">Vendor decides<\/td>\n                            <\/tr>\n                            <tr class=\"border-b border-slate-800 hover:bg-slate-800\/30\">\n                                <td class=\"py-3 px-4 font-semibold\">Learning Value<\/td>\n                                <td class=\"py-3 px-4\">Very High<\/td>\n                                <td class=\"py-3 px-4 text-slate-500\">Low<\/td>\n                            <\/tr>\n                            <tr class=\"border-b border-slate-800 hover:bg-slate-800\/30\">\n                                <td class=\"py-3 px-4 font-semibold\">Setup Effort<\/td>\n                                <td class=\"py-3 px-4 text-slate-500\">Higher<\/td>\n                                <td class=\"py-3 px-4\">Lower<\/td>\n                            <\/tr>\n                        <\/tbody>\n                    <\/table>\n                <\/div>\n\n                <h3 class=\"text-white mt-10 mb-4\">The real takeaway<\/h3>\n                <p>\n                    Synthetic monitoring is not about tools. It\u2019s about where you stand and what you observe. If you control the journey and the vantage point, you don\u2019t need to pay to understand reliability. Grafana Open Source gives you the building blocks. The architecture \u2014 and the insight \u2014 are up to you.\n                <\/p>\n\n            <\/div>\n\n        <\/article>\n    <\/main>\n\n    <!-- Footer with Disclaimer -->\n    <footer class=\"border-t border-slate-800 bg-slate-950 py-12\">\n        <div class=\"max-w-3xl mx-auto px-6\">\n            <div class=\"grid md:grid-cols-2 gap-8 mb-8\">\n                <div>\n                    <h5 class=\"text-white font-bold font-mono mb-4 flex items-center gap-2\">\n                        <i class=\"fa-solid fa-signal text-emerald-500 text-xs\"><\/i> PLATFORM_SIGNALS\n                    <\/h5>\n                    <p class=\"text-slate-500 text-sm\">\n                        Building systems that fail gracefully and recover instantly.\n                    <\/p>\n                <\/div>\n            <\/div>\n            \n            <!-- Disclaimer Block -->\n            <div class=\"bg-slate-900\/50 border border-slate-800 rounded p-6 text-xs text-slate-500\">\n                <h6 class=\"font-bold text-slate-400 mb-2 uppercase tracking-wide text-[10px]\">Disclaimer<\/h6>\n                <p>\n                    All content on this website represents the personal opinions of the author(s). The views expressed here are independent and do not represent the opinions of any employer, company, or organization with which the author(s) are or have been associated. This is a personal blog focused on platform engineering, technology signals, and developer experience.\n                <\/p>\n            <\/div>\n            \n            <div class=\"mt-8 text-center text-slate-600 text-xs font-mono\">\n                &copy; 2024 The Silent Node. All systems nominal.\n            <\/div>\n        <\/div>\n    <\/footer>\n\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Multi-Region Synthetics | Platform Signals PLATFORM_SIGNALS About Contact Observability Jan 11, 2026 the_silent_node Why synthetic monitoring is expensive because it&#8217;s outsourced, not because it&#8217;s hard. Here is how to take ownership back. 01. Why synthetic monitoring doesn\u2019t need to be expensive Most discussions around synthetic monitoring immediately lead to SaaS tools: Datadog, Dynatrace, New Relic [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25],"tags":[14,15,24,33,31,32,26],"class_list":["post-55","post","type-post","status-publish","format-standard","hentry","category-observability","tag-architecture","tag-sre","tag-sre-culter","tag-sre-tutorial","tag-browser-test","tag-free-browser-test","tag-low-latency"],"_links":{"self":[{"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/posts\/55","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=55"}],"version-history":[{"count":5,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/posts\/55\/revisions"}],"predecessor-version":[{"id":74,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=\/wp\/v2\/posts\/55\/revisions\/74"}],"wp:attachment":[{"href":"https:\/\/platformsignals.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=55"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=55"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/platformsignals.dev\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=55"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}