Most teams create an Amazon RDS database, connect their application, and assume everything is safe because AWS is managing it. But"managed" does not mean "fully monitored." RDS automatically sends basic metrics to CloudWatch — CPU usage, memory, storage, connections, and replica lag. These are useful, but they only show the basic health of the database infrastructure.
The real problem starts when critical database-level signals are missed. Replicas drift hours behind the primary. The Postgres process restarts in the middle of the night without anyone noticing. None of these conditions crash the database. None of them fill the logs. None of them are guaranteed to surface in the default CloudWatch metrics. And if alerts are not created for them, small issues can silently grow into real production failures while every dashboard on screen still looks green.
The same gap exists for teams running Postgres on their own EC2 or except there, even the basic metrics aren't published for free.
So, what does Amazon RDS monitor by default, which critical alerts are usually missing, and how can you close those gaps before they turn into real production failures?
The Five Alarms Every Production Postgres Should Have:
Every production Postgres setup needs a small set of alerts that catch failure modes that are easy to miss during normal operations. The obvious outages are straightforward to detect, but the more dangerous issues are often silent: the database remains reachable, logs stay clean, and the system appears healthy until the impact surfaces later as degraded performance, stale reads, or a full production incident.
So, these are the top 5 metrices which we have to use the setup
Alarm | Watches | Triggers when |
|---|---|---|
Postgres Down | Database availability | Connections drop to 0 |
Too Many Connections | Connection count | > 80% of max_connections |
Not Enough Connections | Connection count | Below normal baseline |
Replication Lag | Replica delay | Lag > 30s sustained |
Postgres Restarted | Restart events | Postmaster start time changes |
Note- Flow of Replication :
YOUR APP
│
│── WRITE ──▶ PRIMARY DB
│ │
│ │ Step 1: Write to WAL
│ │ Step 2: Update data file
│ │ Step 3: Confirm to app
│ │
│ │── WAL stream ──▶ READ REPLICA
│ │
│ │ Step 5: Receive WAL
│ │ Step 6: Apply change
│ │ Step 7: Now in sync
│
│── READ ───▶ READ REPLICA
(gets fresh data after ReplicaLag clears)
If you decide to set up replication on your RDS instance, meaning you create a read replica or use replication slots, AWS will automatically start sending some extra metrics (eg. ReplicaLag) to CloudWatch on top of the ones above. You do not need to configure anything extra for these either. Just enabling the replication feature is enough and AWS handles the rest.
How to Actually Wire These Alerts Up
There are three different approaches you can take here. You can use the native CloudWatch metrics AWS already publishes, write a Lambda function that pushes its own metrics, or run postgres_exporter to expose Postgres data on a "/metrics" endpoint that any scraper(Datadog, Cloudwatch, Prometheus) can read.
Approach 1: Native CloudWatch
If you're on RDS and you want the fastest possible setup, you're already most of the way there.
AWS automatically publishes a baseline of metrics for every RDS instance — CPU usage, memory, free storage space, connection count, replica lag, and a handful of others. These metrics start flowing into CloudWatch the moment your database is up. You don't install anything. You don't run any agents. You just open the CloudWatch console, or write a few lines of Terraform, and create alarms on the metrics that are already there.
Metric / Signal | Alerts to set |
|---|---|
DatabaseConnections | Postgres Down when it drops to 0, Too Many Connections when it goes too high, Not Enough Connections when it falls below your normal baseline |
ReplicaLag | Replication Lag when lag stays above your threshold for too long |
RDS event subscriptions | Postgres Restarted when an unexpected restart event is emitted |
Approach 2: AWS Lambda + custom SQL
The Lambda has two ways to grab the data you need, and the same function can use both. The first way is Performance Insights — PI exposes a set of database-level counters that RDS collects in the background, and the Lambda calls pi.get_resource_metrics() to pull whatever it needs. The second way is custom SQL — the Lambda opens a connection to Postgres using psycopg2, runs the queries you've written against the pg_stat_* views, and pushes the results to CloudWatch with cloudwatch.put_metric_data(). EventBridge fires the function every five minutes, and from there you alarm on the custom metrics the same way you would on any AWS-published one.
The reason most RDS shops end up here is the control. There's no fixed metric list. The Lambda only publishes what you tell it to. Add a query, get a new metric. Delete a query, the metric stops flowing. And because there's no extra infrastructure — no EC2, no agent, no exposed port — the whole thing is a few Terraform resources and about a hundred lines of Python that lives in your repo like any other small service.
Approach 3: postgres_exporter
If your Postgres is not on RDS, or your team already uses Prometheus, Grafana, or Datadog, postgres_exporter is usually the standard choice.
It is a small open-source Go service that connects to Postgres with a monitoring user, runs a set of built-in queries, and exposes the results on a "/metrics" endpoint, usually on port 9187. A scraper then collects those metrics and sends them to whatever monitoring system you are using.
What makes this approach useful is that it fits the way Prometheus-style monitoring already works. The exporter does not push anything out. It just waits for something to scrape it. That means Prometheus, Datadog, or the CloudWatch agent can all read from the same exporter if you want them to.
On RDS, things look a little different. You can't install anything on the RDS instance itself, because RDS is fully managed by AWS, no shell access, no way to drop a binary or a container onto the database host. So if you want postgres_exporter on RDS, you have to run it somewhere else. The standard pattern is to launch a small EC2 instance, or an ECS container, in the same VPC as your RDS database, run the exporter there, and point it at the RDS endpoint. The exporter connects to RDS over the private network on port 5432, and your scraper connects to the exporter on port 9187.
Alarm | Built-in metric in postgres_exporter |
|---|---|
Postgres Down | pg_up |
Too Many Connections | not exposed by default (Custom query) |
Not Enough Connections | not exposed by default (Custom query) |
Replication Lag | pg_replication_lag |
Postgres Restarted | pg_postmaster_start_time_seconds |
About queries.yml: this is where your own SQL queries live, and the exporter runs them on the same schedule as the built in. If you'd rather skip the built in metrics entirely and write custom queries for every alarm yourself, including the ones already covered by defaults so, you can. The exporter is happy to run only what you give it.


