{
  "@context": "https://schema.org",
  "@id": "https://aggr.aymericbeaumet.com/items/dev-37signals-com/2026-02-16-introducing-upright-an-open-source-synthetic-monitoring/#webpage",
  "@type": [
    "WebPage",
    "ArchiveComponent"
  ],
  "articleBody": "We’re open-sourcing [Upright](https://github.com/basecamp/upright), the synthetic monitoring system we built to watch over Basecamp, HEY, Fizzy and our many other services.\n\nUpright runs health checks from multiple geographic locations and tells us when something breaks. It’s a Rails engine that you deploy to cheap VPS nodes around the world using [Kamal](https://dev.37signals.com/kamal-2/). Each node runs probes against your services and reports back via Prometheus metrics, so you can alert with AlertManager and visualize with Grafana.\n\n* * *\n\n## Why we built it\n\nWe’ve used Pingdom for years and it’s generally worked fine, but has been somewhat of a black box. We couldn’t customize checks, couldn’t run authenticated browser flows without handing over more cash, and couldn’t control exactly where checks ran from. Occasionally, probes would fail and trigger a page, but the issue would resolve itself before we could investigate.\n\nWe wanted something we owned, something we could extend, and something that fit into the open source observability stack we already use.\n\nUpright started as an internal tool to complement our existing monitoring. It now handles all of our synthetic monitoring across five global sites.\n\n* * *\n\n## What it does\n\nUpright supports four types of probes:\n\n**Playwright probes** are the headline feature. These are real browser checks that can log into your app, navigate through flows, and verify that things work end-to-end. When a check fails, Upright captures a video recording and logs so you can see exactly what went wrong. You write them as Ruby classes using the [Playwright API](https://playwright-ruby-client.vercel.app/docs/api/page).\n\n**HTTP probes** are simple health checks — hit a URL, check the status code. They run every 30 seconds from every site. Logs are in a familiar cURL format.\n\n**SMTP probes** verify EHLO responsiveness, STARTTLS support, and SSL certificate validity.\n\n**Traceroute probes** map the network path to your services with hop-by-hop latency tracking, thanks to MTR reports.\n\nAll probes run from every site you deploy to, optionally staggered by a few seconds. Upright uses this multi-site data to distinguish between real outages and regional blips. If one site in Amsterdam can’t reach your app but four others can, that’s a regional issue, not a full outage.\n\n* * *\n\n## How it’s built\n\nUpright is a Rails engine. You create a new Rails app, add the gem, run the install generator, and you have a working monitoring system. It uses:\n\n- **SQLite** for probe results storage\n- [**Solid Queue**](https://dev.37signals.com/solid-queue-v1-0/) for background and recurring jobs\n- **Kamal** for multi-server deployment\n- [**Prometheus**](https://dev.37signals.com/prometheus-metrics-at-37signals/) for metrics and uptime queries\n- **AlertManager** for notifications\n- **OpenTelemetry** for tracing and logging\n- **Dark and light mode** for the dashboard UI\n\nThe architecture is simple: deploy Upright to a few cheap VPS nodes (DigitalOcean, Hetzner, etc.), point DNS subdomains at each one, and configure your probes. Our setup runs across five sites — four DigitalOcean droplets (2 vCPU, 4GB RAM, ~$24/mo each) and one Hetzner server (3 vCPU, 4GB RAM) — for about $110/mo total. You could run a minimal two-site setup purely on Hetzner for under $20/mo.\n\nEach node identifies itself via environment variables and runs probes independently. Metrics are scraped locally with an [OpenTelemetry collector](https://dev.37signals.com/kamal-prometheus/), and then fed back to the primary site which runs Prometheus and Alertmanager.\n\nWe actually send the metrics to three Prometheus instances, one in each of our datacenters as well as the primary Upright instance, providing full redundancy.\n\n* * *\n\n## Getting started\n\n```\nrails new my-upright --database=sqlite3 --skip-test\ncd my-upright\nbundle add upright\nbin/rails generate upright:install\nbin/rails db:migrate\nbin/dev\n```\n\nThe install generator creates everything you need: Prometheus and AlertManager configs, OpenTelemetry collector config, probe directories, Kamal deploy templates, and a Docker Compose file for local development services.\n\nDefine an HTTP probe in `probes/http_probes.yml`:\n\n```\n- name: \"37signals.com\"\n  url: \"https://37signals.com/\"\n\n- name: \"Help Ticket Creation\"\n  url: \"https://help.37signals.com/billing/tickets/new\"\n  expected_status: 301\n```\n\nOr write a Playwright probe that logs into your app and checks a real user flow:\n\n```\nclass Probes::Playwright::Bc4ProjectProbe < Upright::Probes::Playwright::Base\n  authenticate_with_form :bc4\n\n  def probe_name = \"Basecamp 4: view project\"\n\n  def check\n    page.goto(\"https://3.basecamp.com/5228151\")\n\n    page.get_by_text(\"37s Monitoring HQ\").visible?\n  end\nend\n```\n\nCheck out the [README](https://github.com/basecamp/upright) for the full setup guide, including multi-site Kamal deployment, alerting rules, and authentication configuration.\n\n* * *\n\n## Try it out\n\nUpright is available now on [RubyGems](https://rubygems.org/gems/upright) and [GitHub](https://github.com/basecamp/upright) under the MIT license.\n",
  "authors": [
    "Lewis Buckley"
  ],
  "captured_at": "2026-09-19T21:30:23.395188495+00:00",
  "category": "programming",
  "content_html": "<p>We’re open-sourcing <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/basecamp/upright\">Upright</a>, the synthetic monitoring system we built to watch over Basecamp, HEY, Fizzy and our many other services.</p>\n<p>Upright runs health checks from multiple geographic locations and tells us when something breaks. It’s a Rails engine that you deploy to cheap VPS nodes around the world using <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://dev.37signals.com/kamal-2/\">Kamal</a>. Each node runs probes against your services and reports back via Prometheus metrics, so you can alert with AlertManager and visualize with Grafana.</p>\n<hr />\n<h2>Why we built it</h2>\n<p>We’ve used Pingdom for years and it’s generally worked fine, but has been somewhat of a black box. We couldn’t customize checks, couldn’t run authenticated browser flows without handing over more cash, and couldn’t control exactly where checks ran from. Occasionally, probes would fail and trigger a page, but the issue would resolve itself before we could investigate.</p>\n<p>We wanted something we owned, something we could extend, and something that fit into the open source observability stack we already use.</p>\n<p>Upright started as an internal tool to complement our existing monitoring. It now handles all of our synthetic monitoring across five global sites.</p>\n<hr />\n<h2>What it does</h2>\n<p>Upright supports four types of probes:</p>\n<p><strong>Playwright probes</strong> are the headline feature. These are real browser checks that can log into your app, navigate through flows, and verify that things work end-to-end. When a check fails, Upright captures a video recording and logs so you can see exactly what went wrong. You write them as Ruby classes using the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://playwright-ruby-client.vercel.app/docs/api/page\">Playwright API</a>.</p>\n<p><strong>HTTP probes</strong> are simple health checks — hit a URL, check the status code. They run every 30 seconds from every site. Logs are in a familiar cURL format.</p>\n<p><strong>SMTP probes</strong> verify EHLO responsiveness, STARTTLS support, and SSL certificate validity.</p>\n<p><strong>Traceroute probes</strong> map the network path to your services with hop-by-hop latency tracking, thanks to MTR reports.</p>\n<p>All probes run from every site you deploy to, optionally staggered by a few seconds. Upright uses this multi-site data to distinguish between real outages and regional blips. If one site in Amsterdam can’t reach your app but four others can, that’s a regional issue, not a full outage.</p>\n<hr />\n<h2>How it’s built</h2>\n<p>Upright is a Rails engine. You create a new Rails app, add the gem, run the install generator, and you have a working monitoring system. It uses:</p>\n<ul>\n<li><strong>SQLite</strong> for probe results storage</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://dev.37signals.com/solid-queue-v1-0/\"><strong>Solid Queue</strong></a> for background and recurring jobs</li>\n<li><strong>Kamal</strong> for multi-server deployment</li>\n<li><a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://dev.37signals.com/prometheus-metrics-at-37signals/\"><strong>Prometheus</strong></a> for metrics and uptime queries</li>\n<li><strong>AlertManager</strong> for notifications</li>\n<li><strong>OpenTelemetry</strong> for tracing and logging</li>\n<li><strong>Dark and light mode</strong> for the dashboard UI</li>\n</ul>\n<p>The architecture is simple: deploy Upright to a few cheap VPS nodes (DigitalOcean, Hetzner, etc.), point DNS subdomains at each one, and configure your probes. Our setup runs across five sites — four DigitalOcean droplets (2 vCPU, 4GB RAM, ~$24/mo each) and one Hetzner server (3 vCPU, 4GB RAM) — for about $110/mo total. You could run a minimal two-site setup purely on Hetzner for under $20/mo.</p>\n<p>Each node identifies itself via environment variables and runs probes independently. Metrics are scraped locally with an <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://dev.37signals.com/kamal-prometheus/\">OpenTelemetry collector</a>, and then fed back to the primary site which runs Prometheus and Alertmanager.</p>\n<p>We actually send the metrics to three Prometheus instances, one in each of our datacenters as well as the primary Upright instance, providing full redundancy.</p>\n<hr />\n<h2>Getting started</h2>\n<pre><code><span class=\"code-snippet\">rails new my-upright --database=sqlite3 --skip-test\ncd my-upright\nbundle add upright\nbin/rails generate upright:install\nbin/rails db:migrate\nbin/dev\n</span></code></pre>\n<p>The install generator creates everything you need: Prometheus and AlertManager configs, OpenTelemetry collector config, probe directories, Kamal deploy templates, and a Docker Compose file for local development services.</p>\n<p>Define an HTTP probe in <code>probes/http_probes.yml</code>:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"YAML\"><span class=\"syntax-source syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-block syntax-sequence syntax-item syntax-yaml\">-</span> <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">name</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>37signals.com<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span>\n  <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">url</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>https://37signals.com/<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span>\n\n<span class=\"syntax-punctuation syntax-definition syntax-block syntax-sequence syntax-item syntax-yaml\">-</span> <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">name</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>Help Ticket Creation<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span>\n  <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">url</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-string syntax-quoted syntax-double syntax-yaml\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-yaml\">&quot;</span>https://help.37signals.com/billing/tickets/new<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-yaml\">&quot;</span></span>\n  <span class=\"syntax-string syntax-unquoted syntax-plain syntax-out syntax-yaml\"><span class=\"syntax-entity syntax-name syntax-tag syntax-yaml\">expected_status</span></span><span class=\"syntax-punctuation syntax-separator syntax-key-value syntax-mapping syntax-yaml\">:</span> <span class=\"syntax-constant syntax-numeric syntax-integer syntax-yaml\">301</span>\n</span></span></code></pre>\n<p>Or write a Playwright probe that logs into your app and checks a real user flow:</p>\n<pre><code><span class=\"code-snippet\" data-language=\"Ruby\"><span class=\"syntax-source syntax-ruby\"><span class=\"syntax-meta syntax-class syntax-ruby\"><span class=\"syntax-keyword syntax-control syntax-class syntax-ruby\">class</span></span><span class=\"syntax-meta syntax-class syntax-ruby\"> </span><span class=\"syntax-meta syntax-class syntax-ruby\"><span class=\"syntax-entity syntax-name syntax-class syntax-ruby\"><span class=\"syntax-support syntax-other syntax-namespace syntax-ruby\">Probes</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">::</span><span class=\"syntax-support syntax-other syntax-namespace syntax-ruby\">Playwright</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">::</span>Bc4ProjectProbe</span></span><span class=\"syntax-meta syntax-class syntax-ruby\"> <span class=\"syntax-punctuation syntax-separator syntax-inheritance syntax-ruby\">&lt;</span></span><span class=\"syntax-meta syntax-class syntax-ruby\"> </span><span class=\"syntax-meta syntax-class syntax-ruby\"><span class=\"syntax-entity syntax-other syntax-inherited-class syntax-ruby\"><span class=\"syntax-support syntax-other syntax-namespace syntax-ruby\">Upright</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">::</span><span class=\"syntax-support syntax-other syntax-namespace syntax-ruby\">Probes</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">::</span><span class=\"syntax-support syntax-other syntax-namespace syntax-ruby\">Playwright</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">::</span>Base</span></span>\n  authenticate_with_form <span class=\"syntax-constant syntax-other syntax-symbol syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-constant syntax-ruby\">:</span>bc4</span>\n\n  <span class=\"syntax-meta syntax-function syntax-ruby\"><span class=\"syntax-keyword syntax-control syntax-def syntax-ruby\">def</span></span><span class=\"syntax-meta syntax-function syntax-ruby\"> <span class=\"syntax-entity syntax-name syntax-function syntax-ruby\">probe_name</span></span><span class=\"syntax-meta syntax-function syntax-ruby\"> = &quot;</span><span class=\"syntax-meta syntax-function syntax-parameters syntax-ruby\"><span class=\"syntax-variable syntax-parameter syntax-ruby\">Basecamp</span> 4</span><span class=\"syntax-meta syntax-function syntax-parameters syntax-default-value syntax-ruby\"><span class=\"syntax-punctuation syntax-separator syntax-ruby\">:</span></span><span class=\"syntax-meta syntax-function syntax-parameters syntax-default-value syntax-ruby\"> view project<span class=\"syntax-string syntax-quoted syntax-double syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-ruby\">&quot;</span>\n\n  def check\n    page.goto(<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-ruby\">&quot;</span></span><span class=\"syntax-constant syntax-other syntax-symbol syntax-ruby\">https<span class=\"syntax-punctuation syntax-definition syntax-constant syntax-ruby\">:</span></span><span class=\"syntax-string syntax-regexp syntax-classic syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-ruby\">/</span></span><span class=\"syntax-string syntax-regexp syntax-classic syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-ruby\">/</span></span><span class=\"syntax-constant syntax-numeric syntax-ruby\">3</span><span class=\"syntax-punctuation syntax-accessor syntax-ruby\">.</span>basecamp<span class=\"syntax-punctuation syntax-accessor syntax-ruby\">.</span>com<span class=\"syntax-keyword syntax-operator syntax-arithmetic syntax-ruby\">/</span><span class=\"syntax-constant syntax-numeric syntax-ruby\">5228151</span><span class=\"syntax-string syntax-quoted syntax-double syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-ruby\">&quot;</span>)\n\n    page.get_by_text(<span class=\"syntax-punctuation syntax-definition syntax-string syntax-end syntax-ruby\">&quot;</span></span>37s <span class=\"syntax-variable syntax-other syntax-constant syntax-ruby\">Monitoring</span> <span class=\"syntax-variable syntax-other syntax-constant syntax-ruby\">HQ</span><span class=\"syntax-string syntax-quoted syntax-double syntax-ruby\"><span class=\"syntax-punctuation syntax-definition syntax-string syntax-begin syntax-ruby\">&quot;</span>).visible?\n  end\nend\n</span></span></span></span></code></pre>\n<p>Check out the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/basecamp/upright\">README</a> for the full setup guide, including multi-site Kamal deployment, alerting rules, and authentication configuration.</p>\n<hr />\n<h2>Try it out</h2>\n<p>Upright is available now on <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://rubygems.org/gems/upright\">RubyGems</a> and <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"https://github.com/basecamp/upright\">GitHub</a> under the MIT license.</p>\n",
  "content_markdown": "We’re open-sourcing [Upright](https://github.com/basecamp/upright), the synthetic monitoring system we built to watch over Basecamp, HEY, Fizzy and our many other services.\n\nUpright runs health checks from multiple geographic locations and tells us when something breaks. It’s a Rails engine that you deploy to cheap VPS nodes around the world using [Kamal](https://dev.37signals.com/kamal-2/). Each node runs probes against your services and reports back via Prometheus metrics, so you can alert with AlertManager and visualize with Grafana.\n\n* * *\n\n## Why we built it\n\nWe’ve used Pingdom for years and it’s generally worked fine, but has been somewhat of a black box. We couldn’t customize checks, couldn’t run authenticated browser flows without handing over more cash, and couldn’t control exactly where checks ran from. Occasionally, probes would fail and trigger a page, but the issue would resolve itself before we could investigate.\n\nWe wanted something we owned, something we could extend, and something that fit into the open source observability stack we already use.\n\nUpright started as an internal tool to complement our existing monitoring. It now handles all of our synthetic monitoring across five global sites.\n\n* * *\n\n## What it does\n\nUpright supports four types of probes:\n\n**Playwright probes** are the headline feature. These are real browser checks that can log into your app, navigate through flows, and verify that things work end-to-end. When a check fails, Upright captures a video recording and logs so you can see exactly what went wrong. You write them as Ruby classes using the [Playwright API](https://playwright-ruby-client.vercel.app/docs/api/page).\n\n**HTTP probes** are simple health checks — hit a URL, check the status code. They run every 30 seconds from every site. Logs are in a familiar cURL format.\n\n**SMTP probes** verify EHLO responsiveness, STARTTLS support, and SSL certificate validity.\n\n**Traceroute probes** map the network path to your services with hop-by-hop latency tracking, thanks to MTR reports.\n\nAll probes run from every site you deploy to, optionally staggered by a few seconds. Upright uses this multi-site data to distinguish between real outages and regional blips. If one site in Amsterdam can’t reach your app but four others can, that’s a regional issue, not a full outage.\n\n* * *\n\n## How it’s built\n\nUpright is a Rails engine. You create a new Rails app, add the gem, run the install generator, and you have a working monitoring system. It uses:\n\n- **SQLite** for probe results storage\n- [**Solid Queue**](https://dev.37signals.com/solid-queue-v1-0/) for background and recurring jobs\n- **Kamal** for multi-server deployment\n- [**Prometheus**](https://dev.37signals.com/prometheus-metrics-at-37signals/) for metrics and uptime queries\n- **AlertManager** for notifications\n- **OpenTelemetry** for tracing and logging\n- **Dark and light mode** for the dashboard UI\n\nThe architecture is simple: deploy Upright to a few cheap VPS nodes (DigitalOcean, Hetzner, etc.), point DNS subdomains at each one, and configure your probes. Our setup runs across five sites — four DigitalOcean droplets (2 vCPU, 4GB RAM, ~$24/mo each) and one Hetzner server (3 vCPU, 4GB RAM) — for about $110/mo total. You could run a minimal two-site setup purely on Hetzner for under $20/mo.\n\nEach node identifies itself via environment variables and runs probes independently. Metrics are scraped locally with an [OpenTelemetry collector](https://dev.37signals.com/kamal-prometheus/), and then fed back to the primary site which runs Prometheus and Alertmanager.\n\nWe actually send the metrics to three Prometheus instances, one in each of our datacenters as well as the primary Upright instance, providing full redundancy.\n\n* * *\n\n## Getting started\n\n```\nrails new my-upright --database=sqlite3 --skip-test\ncd my-upright\nbundle add upright\nbin/rails generate upright:install\nbin/rails db:migrate\nbin/dev\n```\n\nThe install generator creates everything you need: Prometheus and AlertManager configs, OpenTelemetry collector config, probe directories, Kamal deploy templates, and a Docker Compose file for local development services.\n\nDefine an HTTP probe in `probes/http_probes.yml`:\n\n```\n- name: \"37signals.com\"\n  url: \"https://37signals.com/\"\n\n- name: \"Help Ticket Creation\"\n  url: \"https://help.37signals.com/billing/tickets/new\"\n  expected_status: 301\n```\n\nOr write a Playwright probe that logs into your app and checks a real user flow:\n\n```\nclass Probes::Playwright::Bc4ProjectProbe < Upright::Probes::Playwright::Base\n  authenticate_with_form :bc4\n\n  def probe_name = \"Basecamp 4: view project\"\n\n  def check\n    page.goto(\"https://3.basecamp.com/5228151\")\n\n    page.get_by_text(\"37s Monitoring HQ\").visible?\n  end\nend\n```\n\nCheck out the [README](https://github.com/basecamp/upright) for the full setup guide, including multi-site Kamal deployment, alerting rules, and authentication configuration.\n\n* * *\n\n## Try it out\n\nUpright is available now on [RubyGems](https://rubygems.org/gems/upright) and [GitHub](https://github.com/basecamp/upright) under the MIT license.\n",
  "dateCreated": "2026-09-19T21:30:23.395188495+00:00",
  "date_created": "2026-02-16T18:00:00+00:00",
  "date_modified": "2026-02-16T18:00:00+00:00",
  "date_published": "2026-02-16T18:00:00+00:00",
  "description": "We’re open-sourcing Upright, the synthetic monitoring system we built to watch over Basecamp, HEY, Fizzy and our many other services.",
  "external_url": "https://dev.37signals.com/introducing-upright/",
  "git": {
    "commit": "ebb0941c5c06a2ab2377342d607535262a3e969e",
    "path": "items/dev-37signals-com/2026/02/2026-02-16-introducing-upright-an-open-source-synthetic-monitoring.md",
    "permalink": "https://github.com/aymericbeaumet/aggr-instance/blob/ebb0941c5c06a2ab2377342d607535262a3e969e/items/dev-37signals-com/2026/02/2026-02-16-introducing-upright-an-open-source-synthetic-monitoring.md"
  },
  "id": "urn:aggr:item:4dab0b57520aedf1ce6495e4067fbe153af81319",
  "isBasedOn": {
    "@id": "https://dev.37signals.com/introducing-upright/"
  },
  "mainEntity": {
    "@id": "https://dev.37signals.com/introducing-upright/",
    "@type": "CreativeWork",
    "archivedAt": {
      "@id": "https://aggr.aymericbeaumet.com/items/dev-37signals-com/2026-02-16-introducing-upright-an-open-source-synthetic-monitoring/#webpage"
    },
    "author": [
      {
        "@type": "Person",
        "name": "Lewis Buckley"
      }
    ],
    "dateModified": "2026-02-16T18:00:00+00:00",
    "datePublished": "2026-02-16T18:00:00+00:00",
    "headline": "Introducing Upright: An Open Source Synthetic Monitoring System",
    "timeRequired": "PT4M",
    "url": "https://dev.37signals.com/introducing-upright/",
    "wordCount": 708
  },
  "name": "Archived snapshot: Introducing Upright: An Open Source Synthetic Monitoring System",
  "preview": null,
  "reading_minutes": 4,
  "replicated_at": null,
  "source": "dev-37signals-com",
  "source_name": "37signals Dev",
  "summary": "We’re open-sourcing Upright, the synthetic monitoring system we built to watch over Basecamp, HEY, Fizzy and our many other services.",
  "tags": [],
  "temporalCoverage": "2026-09-19T21:30:23.395188495+00:00",
  "title": "Introducing Upright: An Open Source Synthetic Monitoring System",
  "url": "https://aggr.aymericbeaumet.com/items/dev-37signals-com/2026-02-16-introducing-upright-an-open-source-synthetic-monitoring/",
  "word_count": 708
}