Get started Dashboard
Monitoring coverage · Updated
Part 1 of Observability for startups

What a SaaS Startup Should Monitor in Production

Explore with AI

Start with what your users feel: an outside check on your health route and login page, error rate and latency for each route, database connections and slow queries, a heartbeat on every background job, and sign-in and access failures. Only a few of these should wake someone up: the app being down, errors or latency staying high on a critical route, a missed job, and the database running out of connections.

On this page

A SaaS startup should monitor six things in production. First, whether people outside your network can reach the app. Second, error rate and latency for each route. Third, the health of the database, cache and third-party APIs you depend on. Fourth, background work that has stopped running. Fifth, sign-in, access and security events. Sixth, the business signals that show the product works for each customer, including any AI features. Everything else can wait until one of these sends you looking for more.

This needs very little tooling at the start. A health route, one metrics endpoint, structured logs, an outside uptime check and a short list of alerts cover the first year for most teams. The rest of this page goes through each layer, shows the code and config to set it up, lists the alerts worth paging on, and covers the mistakes that hurt small teams a few months after launch.

What production monitoring means for a young SaaS

Monitoring is a fixed set of checks, metrics and alerts aimed at the ways you already know the system can fail. Observability is the ability to ask new questions about the system after the fact, using logs, metrics and traces. An early SaaS needs good monitoring much sooner than it needs full observability.

The signals come in three kinds:

  • Logs: one record per event, such as a failed payment or a rejected login.
  • Metrics: numbers over time, such as requests per minute, the share of requests that fail, or open database connections.
  • Traces: the path of a single request through your services, database and outside APIs.

If you run one service and a hosted database, logs and a handful of metrics will answer most questions. Tracing becomes worth its cost once a request fans out across several services or async jobs. Until then, logs that share a request id will usually do the job. Having led the Workers observability team at Cloudflare, my view is simple: nobody can debug what the code never emitted. So instrument early, while it’s cheap.

Why a multi-tenant app breaks quietly

In a single-tenant app, an outage is obvious. A multi-tenant SaaS can fail for a few organisations while every global chart looks fine. A query might only go slow for tenants with large datasets. A job might stop processing one organisation’s queue. A feature flag might misfire for one plan tier. In each case the first signal tends to be a support ticket.

A fast rollback doesn’t help if nobody knows something needs rolling back. That’s the gap monitoring has to close, and it’s why the list below starts with what customers feel and works inwards.

Decision flow for whether a production signal should page someone or go to a daily summary

graph TB A[New production signal] --> B{Would a user notice it?} B -->|no| C[Daily summary or dashboard] B -->|yes| D{Sustained for several minutes?} D -->|no| C D -->|yes| E{Needs action from a person?} E -->|no| F[Tune or delete the alert] E -->|yes| G[Page the on-call person] style F fill:#fee2e2,stroke:#fca5a5,color:#7f1d1d style G fill:#d1fae5,stroke:#6ee7b7,color:#065f46

The six layers to monitor, ordered by what users feel first

1. Can someone outside your network reach the app?

All your internal monitoring assumes the infrastructure is reachable. A DNS mistake, an expired TLS certificate or a load balancer with no healthy targets can take you offline, and your internal metrics will never see it because the requests never arrive. An outside uptime check catches this. Point it at a few key endpoints: the health route, the login page, and one authenticated API call if you can script the login reliably. Keep these checks dull and cheap to maintain. Exercising every feature is the job of your internal metrics and end-to-end tests.

The health route has to do real work. A route that returns 200 without touching anything only tells you the process is alive.

2. Errors and latency on each route

Start with three numbers for every route: rate (requests per second), errors (5xx responses) and duration (a latency histogram). Track 5xx counts by route, so a jump straight after a deploy points at that deploy. Track p95 latency on critical routes such as checkout, login and the main API calls. Export these in an open format such as Prometheus, so a later change of vendor stays an option.

3. The database, the cache and outside APIs

A healthy API sitting on a Postgres instance that’s running out of connections looks fine on your dashboard until the moment it doesn’t. Watch:

  • Open connections against the limit. Supabase’s published detection checks flag client connections at 80% of max_connections, and that threshold works on any Postgres.
  • Statements that keep getting slower. The same checks flag a statement when its mean time reaches 100 ms and doubles hour on hour, with 20 or more calls in each hour. A deploy that drops an index shows up here first.
  • Cache pressure, such as Redis evicting keys because it’s short on memory.
  • Third-party calls: error rate and latency for your payment provider, email sender and any AI API, plus spend on the AI API. Spend climbs fast when a loop keeps retrying.
  • Capacity on anything you run yourself: disk space, memory and CPU, with an alert well before each hits its limit.

4. Background work that stops

Cron jobs, queue consumers, webhooks and email sends fail by simply not happening, so no error is ever thrown. You have to watch for something that’s missing. Give each job a heartbeat that fires when it finishes, and alert when the heartbeat doesn’t arrive within the schedule plus a margin. For webhooks, log the arrival and the processing result as two separate events. Then alert when arrivals drop to zero during hours that normally see traffic. In a multi-tenant app, record queue depth or the last processed time per organisation in your logs, so one stuck tenant is visible.

5. Sign-in, access and security events

The OWASP Logging Cheat Sheet lists events to log wherever possible. They include authentication successes and failures, authorisation failures, input validation failures, session failures such as suspicious JWT validation errors, and admin actions like adding users or changing privileges. Repeated login failures for one account, or failures spread across many IP addresses, are early signs of credential stuffing and account takeover. For monitoring, a sudden rise in 401 and 403 responses usually means a broken auth flow or a changed access rule. Compare it with the same window the day before. Every log entry should record when, where, who and what.

Outside the app, keep a list of certificate expiry dates and alert before they lapse. Check that backups have finished, and run a test restore on a schedule. A backup you have never restored may not work when you need it.

6. Business signals and AI features

Some failures return 200. A signup flow that has stopped converting, invoices that never send, or an AI feature routing tickets to the wrong queue will all look healthy on an APM dashboard. Pick one or two business events per core feature, such as signups completed, payments succeeded and invoices sent, and chart them per hour.

For AI features, log the input, the output and the action your product took, all on the same trace. Then track an override rate: how often users undo or edit what the model decided. Alert on the override rate for each cohort over a rolling window. Use relative thresholds, so a sudden jump fires the alert. A fixed level that the baseline drifts up to over months won’t.

Setting up the core monitoring this week

These steps use a Node service and Postgres, but they carry over to any stack.

  1. Add a health route that queries the database.
// app/api/health/route.ts
import { db } from "@/lib/db";

export async function GET() {
  const started = Date.now();
  try {
    await db.query("select 1");
    return Response.json({ ok: true, release: process.env.RELEASE_SHA, dbMs: Date.now() - started });
  } catch (err) {
    console.error(JSON.stringify({ level: "error", event: "health.db_failed", message: String(err) }));
    return Response.json({ ok: false }, { status: 500 });
  }
}

If an orchestrator such as Kubernetes or ECS runs your app, split this into two checks. Liveness answers whether the process is alive, and a failure restarts the container. Readiness answers whether it can serve a request now, and a failure stops traffic going to it. When the database is briefly unreachable, readiness is the check that should fail.

  1. Check it from your own machine, then from outside.
curl -s -w "\n%{http_code} %{time_total}s\n" https://app.example.com/api/health
{"ok":true,"release":"3f9c2a1","dbMs":12}
200 0.184s

Then point an outside uptime checker at the same URL and at your login page.

  1. Expose RED metrics per route.
import { Counter, Histogram, Registry, collectDefaultMetrics } from "prom-client";

export const registry = new Registry();
collectDefaultMetrics({ register: registry });

export const httpDuration = new Histogram({
  name: "http_request_duration_seconds",
  help: "Duration of HTTP requests in seconds",
  labelNames: ["method", "route", "status_code"],
  buckets: [0.05, 0.1, 0.3, 0.5, 1, 2, 5],
  registers: [registry],
});

export const httpErrors = new Counter({
  name: "http_requests_errors_total",
  help: "HTTP requests that returned a 5xx status",
  labelNames: ["method", "route"],
  registers: [registry],
});

Label by route template, such as /orders/:id, and never by the raw URL. Leave tenant id and user id off metric labels.

  1. Write structured logs that carry the release. Log one JSON line per request or job, with the event name, route, tenant id, status, duration, environment and the commit SHA that produced it. Set the SHA at build time:
export RELEASE_SHA=$(git rev-parse --short HEAD)
npm run build

When errors jump, you can then filter on the release and see whether they started with a particular deploy.

  1. Read database pressure directly.
select count(*) filter (where backend_type = 'client backend') as client_connections,
       current_setting('max_connections')::int as max_connections
from pg_stat_activity;
  1. Put a heartbeat on every job.
# at the end of a successful nightly run
curl -fsS -X POST "$INVOICES_HEARTBEAT_URL"
  1. Check certificate expiry from the outside.
echo | openssl s_client -connect app.example.com:443 -servername app.example.com 2>/dev/null \
  | openssl x509 -noout -enddate

The alerts that should wake someone up

Only page someone for a sustained symptom a customer would notice. A single 500, one slow query or a short memory spike can go to a dashboard or a low-priority channel. Use two severities: page, which wakes a person, and warn, which waits for working hours.

groups:
  - name: api-availability
    rules:
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_errors_total[5m]))
            / sum(rate(http_request_duration_seconds_count[5m])) > 0.02
        for: 10m
        labels:
          severity: page
      - alert: ElevatedLatency
        expr: |
          histogram_quantile(0.95, sum by (le) (rate(http_request_duration_seconds_bucket[5m]))) > 1.5
        for: 10m
        labels:
          severity: warn

The for: 10m window stops a short blip from paging someone who opens their laptop to find it already gone, and it barely delays detection of a real problem. These thresholds are only a starting point. Set your own after watching your traffic. A sensible first paging list for a young SaaS:

  • App down: the outside health check fails on runs in a row.
  • Server errors rising: Supabase’s checks use 20 or more server errors, a rate of at least 1%, and at least double the previous hour’s rate. That shape works for any API.
  • Database connections near the limit: 80% of max_connections.
  • A missed heartbeat on any job that moves money, sends email or syncs data.
  • A jump in sign-in failures against the same window the day before.

Everything else, including slow statements, cache evictions, AI override rates and certificate expiry, goes to warn alerts and a daily summary. Every alert needs a named owner and a backup, and pages should reach a phone or a chat channel someone watches.

What a startup can leave until later

Some signals can wait until you have enough traffic and incidents to know what you’re trying to catch: distributed tracing across many services, real user monitoring, synthetic browser tests that click through every feature, and detection built from log patterns. Running your own Prometheus, Grafana and Loki is also something to put off unless someone on the team has run that stack before. If you do it, you own its uptime, storage and retention as well. Hosted free tiers go a long way at the start. Instrument in open formats from day one, so you keep the option to move backends later.

Where SaaS monitoring setups go wrong

  • Tenant id or user id as a metric label. Prometheus treats every unique combination of label values as a new time series, which can greatly increase the data stored. As the customer base grows, so does the series count, and your bill and query times follow. Fix: put per-tenant detail in structured logs or analytics events.
  • Alerting on everything. Keep the pager quiet enough that a real alert stands out. Fix: page only on sustained, user-facing symptoms. Tune or delete any alert that fires without needing action.
  • A health route that skips the database. It stays green while every real request fails. Fix: run one real query, as in step 1.
  • The uptime check runs on the same host. It goes down with the app and says nothing. Fix: run it from outside your network.
  • Watching the app and ignoring its dependencies. Fix: health checks that ping Postgres and Redis, plus metrics taken from the database itself.
  • Dashboards treated as documentation. A dashboard nobody opens outside an incident does no monitoring. Fix: delete it, and if nobody notices within a month, it wasn’t needed.
  • Secrets in log fields. Logging full request bodies puts tokens and personal data into a store with wide access. Fix: log named fields only, and redact anything that looks like a credential.
  • Preview and staging environments paging on-call. Fix: tag every event with the environment and filter paging alerts to production.
  • Backups nobody has restored. Fix: schedule a test restore and write down how long it took.

Finding the gaps in your production monitoring with Polylane

Polylane detects production issues and fixes them by opening pull requests. It connects to the platforms your team already runs on, so it can see when something breaks there, works out why, and sends a fix ready for your normal review.

See Polylane as your AI SRE.

Common questions.

What is the minimum monitoring a SaaS startup needs at launch?

Four things: a health route that queries the database, an outside uptime check on it and on the login page, structured logs that carry the release SHA, and per-route error and latency metrics. Add heartbeats on any scheduled job that moves money or sends email. This covers the failures customers notice first.

How many alerts should a small SaaS team page on?

About five: app down, sustained server errors, database connections near 80% of max_connections, a missed job heartbeat, and a jump in sign-in failures. Everything else goes to a warn channel or a daily summary. Tune or delete any alert that fires often without needing action, so real pages stand out.

Should I label metrics with tenant or customer id?

No. Prometheus treats every unique combination of label values as a new time series, so a tenant id label adds series as your customer base grows and can greatly increase the data stored. Put tenant id in structured logs or analytics events, and query per-tenant behaviour there.

Do I need distributed tracing at an early-stage SaaS?

Usually not at first. For a monolith calling Postgres and Redis directly, request-scoped logs with a correlation id answer most of the same questions. Add tracing once requests fan out across several services or async jobs.

Should I use Datadog or a set of focused tools?

Many early teams start with focused tools on free tiers. Whichever you pick, instrument in open formats such as Prometheus and OpenTelemetry, so you keep the option to switch backends later.

Which security events should a SaaS log in production?

The OWASP Logging Cheat Sheet lists authentication successes and failures, authorisation failures, input validation failures, session failures and admin actions such as privilege changes. Each entry should record when, where, who and what. Alert on bursts of failed logins for one account or from many IP addresses.

How do I monitor an AI feature in a SaaS product?

Log the input, the output and the action your product took on one trace, then track how often users override the model's decision. Alert on the override rate for each cohort over a rolling window, using relative thresholds. Sample a few percent of calls to a separate store so you can replay them against a fix.

How do I monitor cron jobs and webhooks that fail silently?

Have each job call a heartbeat URL when it finishes, and alert when the ping doesn't arrive within the schedule plus a margin. For webhooks, log arrival and the processing result as separate events. Alert when arrivals drop to zero during hours that normally see traffic.

Sources (6)

  1. Polylane full content, including How to monitor a vibe-coded app in production
  2. Polylane documentation (full text)
  3. Monitoring a SaaS in Production (DEV Community)
  4. OWASP Logging Cheat Sheet
  5. Server Management Checklist for SaaS Startups (CloudHouse Technologies)
  6. How to Monitor an AI Feature After It Ships (CodeNicely)

About the author

Boris Tane

Founder of Polylane

Boris Tane is the founder of Polylane. He previously founded Baselime, observability for the future of the cloud, which Cloudflare acquired. At Cloudflare he built and led the Workers observability team.

Related

Nobody should be on-call. Polylane watches your infra, finds what broke, and writes the fix.

Get started for free