← Back to home

Stop RDS on a schedule — and survive the 7-day forced restart

Stopping non-production databases overnight is obvious money. The part that catches people is that RDS will not stay stopped: at seven days AWS starts the instance back up. Here is what actually bills while an instance is stopped, why the 7-day rule breaks the naive script, and the approach that holds up.

What "stopped" actually saves

Stopping an RDS instance stops the compute charge — the instance-hour rate. It does not stop everything:

For a Multi-AZ deployment the instance-hour saving roughly doubles, because you are no longer paying for the standby compute either. The storage side is unchanged.

Worth knowing

Read replicas cannot be stopped. Aurora is a different model — you stop the cluster, not an instance, and the same 7-day limit applies to it. This guide is about standard RDS instances; the reconciliation pattern below applies to all three.

The 7-day forced restart

This is the behavior that breaks homegrown scheduling:

The rule

Amazon RDS keeps an instance in the stopped state for a maximum of seven days. At the limit, RDS automatically starts the instance to apply pending maintenance. It does not ask, and it does not re-stop afterward.

A nightly cron that stops at 19:00 and starts at 08:00 never hits this — the instance is only ever stopped for ~13 hours. But the moment someone stops a database "for now" (a paused project, a staging env between test cycles, a long holiday), the single-shot approach fails silently: seven days later RDS starts it, and it runs — and bills the full instance-hour rate — until a human notices. For a db.m5.large that is $0.178/hour, about $128/month, quietly resuming.

Why a single stop is the wrong primitive

The failure is architectural, not a bug in anyone's script. "Stop this instance" is an imperative — it fires once and assumes the world stays put. AWS's own maintenance behavior violates that assumption. The correct primitive is declarative: "this instance should be stopped during these hours," enforced repeatedly. When the 7-day restart brings the instance back inside a scheduled-off window, the next reconciliation notices it is running when it should not be, and stops it again.

A durable approach: reconcile, don't fire-and-forget

Run a small Lambda on an EventBridge schedule (say every 10 minutes). On each run it resolves, for the current time, whether each tagged instance should be running, and enforces that — handling the transient states RDS returns so it does not throw when an instance is mid-transition.

# enforce_rds.py — invoked by an EventBridge rule every 10 minutes
import boto3

rds = boto3.client("rds")

def desired_running(tags: dict, now) -> bool:
    # parse your schedule tag, e.g. "9-17 mon-fri", in the resource's timezone
    # return True if `now` falls inside a working window, else False
    ...

def handler(event, context):
    now = ...  # timezone-aware current time
    paginator = rds.get_paginator("describe_db_instances")
    for page in paginator.paginate():
        for db in page["DBInstances"]:
            tags = {t["Key"]: t["Value"]
                    for t in rds.list_tags_for_resource(
                        ResourceName=db["DBInstanceArn"])["TagList"]}
            if "uptime:schedule" not in tags:
                continue

            state = db["DBInstanceStatus"]
            want = desired_running(tags, now)
            db_id = db["DBInstanceIdentifier"]

            # Only act from a stable state; ignore modifying/backing-up/etc.
            if want and state == "stopped":
                rds.start_db_instance(DBInstanceIdentifier=db_id)
            elif not want and state == "available":
                # this branch also re-stops after the 7-day forced start
                rds.stop_db_instance(DBInstanceIdentifier=db_id)

The important line is the last branch: because it runs on every interval, it re-stops an instance that the 7-day rule brought back. A one-shot script has no equivalent. The parts that need care in production:

This is the model Uptime Scheduler runs inside your account: a continuous reconciler rather than a cron that hopes the instance stays put. It is also why the 7-day restart is a non-event for us — and for AWS's own Instance Scheduler, which reconciles the same way. See the comparison if that is the tool you are weighing.

FAQ

Does a stopped RDS instance still cost money?

Yes. You stop paying instance-hours (e.g. ~$0.082/hr for db.t3.medium, $0.178/hr for db.m5.large in us-east-1) but keep paying for storage, Provisioned IOPS, and backups.

Why does my stopped RDS instance start itself again?

RDS keeps an instance stopped for at most seven days, then auto-starts it to apply maintenance. Single-shot stop scripts get caught by this; a reconciling scheduler re-stops it.

How do you stop RDS reliably on a schedule?

Reconcile desired state on an interval instead of firing one stop. Each run re-stops anything that is running when it should be off — including after the 7-day forced start.

Find the databases worth scheduling first

upscan reports idle RDS instances and what they cost — run upscan --resources rds --region us-east-1. Free CLI, no signup; start from the number.