DBA Scripts: Get Database Backup History

🔧Part of the DBA-Tools Project, copy/paste SQL Server scripts and health checks.In: Backup & Recovery

Most backup checks answer one question: what’s the latest backup. That’s the right question most of the time, but it hides everything that happened before it. A backup job that ran reliably for a year and then quietly changed behaviour 3 weeks ago still passes a “latest backup is recent” check.

This script skips the summary and gives you the raw history instead: every backup event across every database for the trailing 2 months, in one list. That’s what you actually need to spot a pattern, not just a snapshot.

It’s the detail view behind the summary scripts. When something looks wrong in backup coverage or chain integrity, this is where you go to see exactly what happened and when.


Why Database Backup History Matters

A single point-in-time check tells you the current state. History tells you the trend, and trends catch problems that a snapshot can’t:

  • A backup job that used to run nightly and now runs every other night, without anyone changing the schedule on purpose
  • Backup duration creeping upward over weeks, an early signal of I/O contention or a growing database outrunning its backup window
  • Backup size trends that don’t match expected data growth, worth investigating either way
  • A gap in the timeline that a “most recent backup” check would never reveal, because the most recent one still looks fine

Raw history is also the evidence a compliance review or post-incident investigation actually wants: not “is it fine right now,” but “show me what actually happened.”


When to Run This Script

  • Investigating whether a backup job’s behaviour has changed over time
  • Audits or compliance reviews that need historical evidence, not a current snapshot
  • After a backup job or schedule change, to confirm the new pattern is what you expect
  • Capacity planning, using backup size trends as a proxy for data growth
  • Incident investigation, to reconstruct exactly what backup activity happened around a specific time

The Script

Run the following script against your SQL Server instance.

✓ Verified
  • Tested on: SQL Server 2025 (RTM CU8), Windows lab instance
  • Last verified: 2026-09-07 (saved output from a real run, Get-DatabaseBackupHistory-20260907-222341.csv)
  • Permissions: db_datareader on msdb
  • Safety: read-only, impact low
/*
Script Name : Get-DatabaseBackupHistory
Category    : backups-and-recovery
Purpose     : Review detailed backup history for all databases over the last 2 months.
Author      : Peter Whyte (https://sqldba.blog/dba-scripts-get-database-backup-history/)
Requires    : db_datareader on msdb
*/
-- SAFE:ReadOnly
-- IMPACT:Low
SET NOCOUNT ON;

DECLARE @MonthsBack INT = 2;

SELECT
    bs.server_name,
    bs.database_name,
    bs.user_name,
    bs.backup_start_date,
    bs.backup_finish_date,
    bs.type,
    CAST(bs.backup_size / 1024.0 / 1024 AS DECIMAL(18,2)) AS backup_size_mb,
    bs.recovery_model
FROM msdb.dbo.backupset AS bs
WHERE bs.backup_start_date >= DATEADD(MONTH, -@MonthsBack, GETDATE())
ORDER BY bs.backup_finish_date DESC;

The script returns one row per backup event recorded in msdb.dbo.backupset over the trailing 2 months, newest first.


How To Run From The Repo

Clone DBA Tools, initialize and run the script:

# Clone dba-tools repo:
git clone https://github.com/peterwhyte-lgtm/dba-tools

# Initialize environment:
cd dba-tools
.\Initialize-Environment.ps1

# Review backup history for the last 2 months across all databases:
.\run.ps1 Get-DatabaseBackupHistory

# To run against a remote sql server:
.\run.ps1 Get-DatabaseBackupHistory -ServerInstance SQLSERVER01

This script lives in the repo at:


Example Output

Run against a lab instance, newest backup first.

Get-DatabaseBackupHistory results in SSMS: database, user, start and finish times, backup type, size in MB, recovery model

Every row is one backup event, so the same database repeats down the list. The zzchain_db rows carry all three types against one database, a full (D) with differentials (I) and log backups (L) around it, while the databases above them show full backups only. Read down one database’s rows for the cadence rather than across a single row.


Understanding the Results

server_name
The instance that ran the backup, as msdb recorded it. Normally the instance you are connected to. A different name in older rows means the instance has been renamed since, or the history came in with a restored msdb.
database_name
The database backed up. One row per backup event, so a database with frequent log backups dominates the list. Filter on it when you want one database’s timeline.
user_name
The login that ran the backup. A service account points at an Agent job or a third party backup tool. A personal login means somebody ran it by hand, which is the usual explanation for a one off row outside the normal cadence.
backup_start_date
backup_finish_date
When the backup started and when it finished. The gap between them is the duration, which msdb does not store as a column of its own.Act when the gap widens over successive rows for the same database. A backup outgrowing its window is an early signal, not an incident, and it is the trend this script exists to show.
type
The backup type as msdb codes it: D is a full database backup, I a differential, L a transaction log backup. The rarer codes are F (file or filegroup), G (differential file), P (partial) and Q (differential partial), documented with the rest of backupset.Act when a database in FULL recovery shows only D rows. With no L rows the log is never backed up, so it is not truncating and will keep growing.
backup_size_mb
The size of the backup set in MB, converted from the bytes msdb stores in backup_size. This is the size of the data backed up, not the file on disk: with backup compression on, the on disk figure is compressed_backup_size, which this script does not return.
recovery_model
The recovery model the database was in when that backup ran, not necessarily the model it is in now. A switch between models shows up as a change partway down one database’s rows.

There’s no severity banding here; this is a timeline, not a health check. Read it for pattern, not for pass/fail:

A steady, regular cadence is what you want to see: full backups on schedule, differentials and log backups filling the gaps between them at consistent intervals.

A single cluster of activity followed by silence is a real problem even though no individual row looks wrong. It usually means a scheduled job stopped running, or was only ever run once manually and never actually scheduled.

Backup size or duration drifting upward over successive rows for the same database is worth tracking even when nothing is currently broken. It’s an early signal, not an incident.


Common Causes

  • A backup job that ran once as a manual or one-time task and was never actually put on a schedule
  • A scheduled job that started failing silently, with no failure alerting configured to catch it
  • A change to server or storage that stopped the job (a disabled SQL Agent, a moved backup target, a changed service account) without anyone connecting the dots back to backup history
  • Migration or lab databases seeded once and never folded into a real recurring backup schedule

Best Practices

  • Review backup history, not just latest backup status, on a regular cadence
  • Alert on backup job failures directly rather than relying on someone noticing a gap in history later
  • Track backup duration and size trends per database, not just presence or absence of a recent backup
  • When onboarding an inherited server, pull the full history first; it tells you what the backup regime has actually been doing, not just what it’s configured to do

Related Scripts

You may also find these scripts useful:


Frequently Asked Questions

Where does SQL Server store backup history?

In msdb, mainly backupset, backupmediafamily, and backupfile. This history persists until msdb‘s own maintenance jobs prune it, so it’s usually available well beyond the 2 months this script defaults to.

How far back can I query backup history?

As far as msdb retention allows, which depends on whether history cleanup jobs are running and how they’re configured. Change the @MonthsBack variable at the top of the script to look further back if your instance retains more.

Does this replace a backup monitoring tool?

Not for alerting. This is a manual, on-demand history query, not a scheduled check with notifications. Pair it with proper job failure alerting; use this script when you need to see the actual pattern behind a concern, not to be told one exists.


Summary

A recent backup and a healthy backup regime aren’t the same thing. This script trades the reassuring single-row snapshot for the full timeline, because the timeline is what actually shows whether a backup job is behaving the way you think it is.

Pull this whenever “latest backup” isn’t enough, whether that’s an audit, an investigation, or just getting to know a server you’ve inherited. The pattern tells you more than any single row ever will.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *