sql server / health check checklist

SQL Server health check checklist

A practical DBA checklist for reviewing whether a SQL Server is safe enough to operate, change, or hand over.

Use it to check the basics that hurt production quietly: backups, restores, SQL Agent, monitoring, configuration, patch level, security, HA/DR, and workload pressure. For scripts and deeper DMV checks, use the SQL Server health check guide.

Guide

Checklist~11 min readUpdated 30 May 2026

Share

LinkedInXEmail

Start here

How to use this SQL Server health check checklist

Work through the table in order. The first rows are deliberately recovery and ownership checks because those failures hurt fastest.

Use the notes below the table when a row is weak or unclear. They explain what the result usually means and what to do next.

Do not turn every row into immediate change work. Sort findings into fix now, schedule, review deeper, watch, or accept for now.

Master checklist

SQL Server health check checklist

Start with the items that can stop recovery or hide failed work: scope, ownership, backups, restores, and alerts. Then move into capacity, performance, security, HA/DR, and the actions worth taking after the review.

PriorityChecklist itemAreaWhy it mattersWhat to look for
01Production instance and database scopeScopeYou cannot review SQL Server health until the production instances, databases, and dependencies are clear.Instance names, environments, critical databases, listeners, applications, reporting loads, vendor systems, and DR targets.
02Business-critical databasesScopeNot every database deserves the same attention. Critical databases need the tightest recovery, monitoring, and change checks.Revenue, operations, customer-facing, identity, integration, reporting, and batch databases that would hurt the business if unavailable.
03SQL Server ownership and escalation pathOwnershipBackups, alerts, jobs, and incidents fail slowly when nobody is clearly responsible for acting on them.Named SQL owner, change owner, backup responder, incident contact, vendor boundary, and after-hours path.
04Recent full backup historyRecoveryFull backups are the base of most restore plans. Missing or stale full backups make the rest of recovery harder.Last full backup per important database, failed runs, copy-only surprises, duration changes, and skipped databases.
05Differential and log backup coverageRecoveryDifferential and log backups usually decide how much data the company loses after a restore.Recovery model, last differential backup, last log backup, broken log-chain patterns, and databases in FULL without log backups.
06Restore test historyRecoveryBackup success does not mean restore success. Restore testing is where many backup plans stop looking good.Last restore test, target server, restored database name, duration, CHECKDB after restore, and who ran it.
07DBCC CHECKDB or integrity-check coverageIntegrityCorruption is rare until it is not. Integrity checks tell you whether backups may contain damaged data.Recent DBCC CHECKDB history, failures, skipped large databases, physical-only shortcuts, and where results are logged.
08Backup destination, retention, and free spaceRecoveryBackups can be technically successful and still useless if storage fills, cleanup deletes too early, or files never leave the server.Backup path, free space, retention period, cleanup job, off-server copy, encryption, compression, and access control.
09SQL Agent failed jobsJobsFailed jobs often hide backup, ETL, reporting, maintenance, and cleanup problems.Failed jobs, failed steps, retry loops, disabled jobs, long-running jobs, and jobs that stopped writing useful history.
10SQL Agent notifications and operatorsJobsA failed job is only useful if the right person finds out quickly enough.SQL Agent operators, Database Mail, notification settings, alert recipients, stale email addresses, and failed mail delivery.
11Critical maintenance jobsMaintenanceMaintenance jobs can keep an environment stable, or they can waste I/O and hide stale assumptions.Backup jobs, CHECKDB jobs, index/statistics jobs, cleanup jobs, history retention, schedule overlap, and runtime drift.
12Disk free space and file growthCapacityLow disk space and poor growth settings turn normal workload changes into outages or long pauses.Drive free space, database file sizes, growth increments, percent growth, max size, file placement, and recent autogrowth events.
13Database log growth and reuse waitsCapacityTransaction log problems usually point to backup gaps, long transactions, replication, AG queues, or workload changes.Log size, VLF shape, log growth, log reuse wait reason, long transactions, and last log backup.
14tempdb size, growth, and pressureCapacitytempdb pressure affects sorting, version store, spills, row versioning, and many production workloads.File count, size, growth, free space, version store, spills, allocation waits, and sudden growth.
15SQL Server error log warningsLogsThe error log often shows the first clear signs of failed backups, failed logins, I/O issues, memory pressure, and startup problems.Recent errors, warnings, login failures, stack dumps, I/O messages, memory messages, and repeated startup entries.
16Severity errors and corruption messagesLogsHigh-severity errors and corruption messages can mean damaged data, storage issues, or failing components.Severity 17-25 messages, 823, 824, 825, CHECKDB failures, suspect pages, and stack dumps.
17Blocking, deadlocks, and long transactionsPerformanceConcurrency problems can make a healthy-looking server feel broken to users.Head blockers, deadlock graphs, long transactions, lock waits, blocked sessions, and application timing.
18Top waits and resource pressurePerformanceWaits help separate CPU, memory, I/O, locking, logging, and network pressure.Top waits, signal wait time, resource waits, active requests, CPU pressure, memory grants, and I/O waits.
19Query Store status and plan regressionsPerformanceQuery Store helps find plan changes and expensive queries without guessing from one live moment.Query Store enabled state, read-write state, capture mode, size limits, top regressions, and forced plans.
20Max server memory, MAXDOP, and cost thresholdConfigCore instance settings can create avoidable memory pressure or parallelism noise when left at poor defaults.Max server memory, min server memory, MAXDOP, cost threshold for parallelism, edition limits, CPU count, and workload type.
21Storage latency and I/O stallsStorageSlow storage affects backups, restores, queries, tempdb, logs, and maintenance windows.Virtual file stats, read latency, write latency, log write waits, file placement, and storage alerts.
22Patch level and support statusSecurityUnsupported or badly outdated builds make troubleshooting, security, and upgrade planning harder.SQL Server version, product level, CU/GDR level, edition, Windows version, support lifecycle, and pending patches.
23Sysadmin access and elevated loginsSecurityToo many sysadmins makes change control, incident review, and security cleanup much harder.Sysadmin members, server roles, database owners, shared accounts, old admin logins, and application logins with elevated rights.
24Service accounts, proxies, and credentialsSecurityAutomation often runs with more access than it needs, and broken credentials can quietly break jobs.SQL Server service accounts, SQL Agent service account, proxies, credentials, job owners, and external resource access.
25Unsafe enabled features and linked serversSecurityOptional features and linked servers can widen exposure if nobody remembers why they are enabled.xp_cmdshell, Ole Automation, CLR, external scripts, ad hoc distributed queries, linked servers, and remote login mappings.
26Monitoring alerts and routingMonitoringMonitoring only helps when alerts reach people who can act and include enough detail to start diagnosis.Backup alerts, failed-job alerts, disk alerts, service alerts, severity alerts, deadlock alerts, and recipient routing.
27Alert noise and ignored warningsMonitoringToo much noise teaches people to ignore the system, including the alerts that matter.Muted alerts, repeated alerts, ignored warnings, unclear thresholds, dashboards nobody checks, and tickets closed without action.
28HA/DR status and replica healthHA / DRA green HA screen does not mean failover or recovery will work cleanly.AG health, cluster health, replica synchronization, send queue, redo queue, failover mode, backup preference, and DR lag.
29Failover readiness: jobs, logins, DNS, app behaviorHA / DRFailover breaks in the boring places: missing jobs, missing logins, DNS, firewall rules, and application connection behavior.Jobs on secondary nodes, logins, linked servers, credentials, operators, listener DNS, firewall rules, and application validation steps.
30Final action list: fix now, schedule, review deeper, watch, acceptOutputA health check is useful only when it turns findings into the next work list.Items that need immediate work, next maintenance window work, deeper review, trend watching, or documented acceptance.

Item notes

SQL Server health check checklist item notes

Use these notes after the table. Each one explains what a weak result usually means and what the next action should be.

01

Scope

Production instance and database scope

Start by deciding exactly which SQL Server instances and databases are in the review. This is partly technical and partly ownership work: production instances, reporting replicas, DR targets, vendor-managed systems, and application dependencies all need to be named before the health check means anything. If the scope is fuzzy, the later findings will be fuzzy too.

02

Scope

Business-critical databases

Use the inventory query as a helper, not as the final answer. SQL Server can show size, recovery model, owner, and state, but it cannot tell you which database runs payroll, identity, billing, production orders, or customer-facing traffic. Mark critical databases explicitly so backup, restore, monitoring, and maintenance checks are judged against the right level of risk.

Use this to list databases, size, owner, state, recovery model, and Query Store status before marking business-critical databases manually.

SQL

Database inventory helper

T-SQL · 23 lines

SELECT    d.name AS database_name,    d.state_desc,    d.recovery_model_desc,    d.compatibility_level,    d.is_read_only,    d.is_query_store_on,    SUSER_SNAME(d.owner_sid) AS database_owner,    CONVERT(decimal(18,2), SUM(mf.size) * 8.0 / 1024.0) AS size_mb,    COUNT(mf.file_id) AS file_countFROM sys.databases AS dLEFT JOIN sys.master_files AS mf    ON mf.database_id = d.database_idWHERE d.name <> N'tempdb'GROUP BY    d.name,    d.state_desc,    d.recovery_model_desc,    d.compatibility_level,    d.is_read_only,    d.is_query_store_on,    d.owner_sidORDER BY size_mb DESC, d.name;
Review before runningT-SQLUTF-823 lines

This query cannot identify business importance by itself. Use it as an inventory helper, then add application and business context.

03

Ownership

SQL Server ownership and escalation path

This item is deliberately not a SQL query. The important question is who acts when something fails: backups, jobs, alerts, storage, access, and incidents all need a named owner and a usable escalation path. If the answer is a shared mailbox, an old vendor address, or one person’s memory, write that down as a real finding.

04

Recovery

Recent full backup history

Full backup history is one of the first technical checks because it affects every restore path. Look for important databases with no recent full backup, copy-only backups being mistaken for the normal base, unusual duration changes, and backups that finish after the maintenance window. Missing full backups should be handled before lower-impact clean-up work.

Check the most recent full backups and spot missing, stale, copy-only, or unusually slow full backups.

SQL

Recent full backup history

T-SQL · 14 lines

SELECT    d.name AS database_name,    d.recovery_model_desc,    d.state_desc,    MAX(CASE WHEN bs.type = 'D' AND bs.is_copy_only = 0 THEN bs.backup_finish_date END) AS last_full_backup,    MAX(CASE WHEN bs.type = 'D' AND bs.is_copy_only = 1 THEN bs.backup_finish_date END) AS last_copy_only_full_backup,    MAX(CASE WHEN bs.type = 'D' THEN DATEDIFF(second, bs.backup_start_date, bs.backup_finish_date) END) AS last_full_duration_secondsFROM sys.databases AS dLEFT JOIN msdb.dbo.backupset AS bs    ON bs.database_name = d.name   AND bs.type = 'D'WHERE d.name <> N'tempdb'GROUP BY d.name, d.recovery_model_desc, d.state_descORDER BY last_full_backup, d.name;
Review before runningT-SQLUTF-814 lines

A copy-only full backup is useful for ad hoc work, but it is not the normal differential base.

05

Recovery

Differential and log backup coverage

Differential and log backups show whether the backup plan matches the expected data-loss window. A database in FULL recovery without regular log backups is usually a recovery problem and sometimes a log-growth problem too. Check the pattern per database, not just whether the backup jobs exist.

Review latest backup time by type and identify FULL or BULK_LOGGED databases without recent log backups.

SQL

Differential and log backup coverage

T-SQL · 27 lines

WITH LastBackups AS (    SELECT        bs.database_name,        bs.type,        MAX(bs.backup_finish_date) AS last_backup_finish_date    FROM msdb.dbo.backupset AS bs    WHERE bs.type IN ('D', 'I', 'L')    GROUP BY bs.database_name, bs.type)SELECT    d.name AS database_name,    d.recovery_model_desc,    MAX(CASE WHEN lb.type = 'D' THEN lb.last_backup_finish_date END) AS last_full_backup,    MAX(CASE WHEN lb.type = 'I' THEN lb.last_backup_finish_date END) AS last_differential_backup,    MAX(CASE WHEN lb.type = 'L' THEN lb.last_backup_finish_date END) AS last_log_backup,    CASE        WHEN d.recovery_model_desc IN (N'FULL', N'BULK_LOGGED')         AND MAX(CASE WHEN lb.type = 'L' THEN lb.last_backup_finish_date END) IS NULL        THEN N'Missing log backup history'        ELSE N'Check timing against RPO'    END AS review_noteFROM sys.databases AS dLEFT JOIN LastBackups AS lb    ON lb.database_name = d.nameWHERE d.name <> N'tempdb'GROUP BY d.name, d.recovery_model_descORDER BY d.name;
Review before runningT-SQLUTF-827 lines

This checks history in msdb. If backup history is purged aggressively, compare with your backup tool as well.

06

Recovery

Restore test history

Restore history is the quickest way to challenge a backup plan that looks fine on paper. A restore test should show the source database, destination database, timing, and whether the restored copy was checked afterward. If restore history is empty on the production server, ask where restore tests are performed and how the result is recorded.

Read restore history recorded in msdb so restore testing is not judged from backup status alone.

SQL

Restore test history

T-SQL · 15 lines

SELECT TOP (100)    rh.destination_database_name,    rh.restore_date,    rh.restore_type,    rh.recovery,    rh.replace,    bs.database_name AS source_database_name,    bs.backup_finish_date,    bmf.physical_device_nameFROM msdb.dbo.restorehistory AS rhLEFT JOIN msdb.dbo.backupset AS bs    ON bs.backup_set_id = rh.backup_set_idLEFT JOIN msdb.dbo.backupmediafamily AS bmf    ON bmf.media_set_id = bs.media_set_idORDER BY rh.restore_date DESC;
Review before runningT-SQLUTF-815 lines

Restore tests may run on a different server. Empty local history is a prompt to find where restore tests are recorded.

07

Integrity

DBCC CHECKDB or integrity-check coverage

Integrity checks matter because backups can preserve corruption just as reliably as healthy data. This query looks for SQL Agent job history that mentions CHECKDB or integrity checks, which is only a practical starting point. If the environment logs CHECKDB results elsewhere, use that source too and confirm that important databases are not being skipped.

Look for recent SQL Agent job steps that appear to run CHECKDB or integrity checks.

SQL

Integrity-check job history

T-SQL · 20 lines

SELECT TOP (100)    j.name AS job_name,    h.step_id,    h.step_name,    h.run_date,    h.run_time,    h.run_duration,    h.run_status,    h.messageFROM msdb.dbo.sysjobhistory AS hJOIN msdb.dbo.sysjobs AS j    ON j.job_id = h.job_idWHERE h.run_date >= CONVERT(int, CONVERT(char(8), DATEADD(day, -30, GETDATE()), 112))  AND (      h.step_name LIKE N'%CHECKDB%'      OR h.message LIKE N'%CHECKDB%'      OR h.step_name LIKE N'%integrity%'      OR h.message LIKE N'%integrity%'  )ORDER BY h.instance_id DESC;
Review before runningT-SQLUTF-820 lines

This reads job history only. If integrity checks are logged to tables or external tooling, review that source too.

08

Recovery

Backup destination, retention, and free space

Backup files need more than successful job status. Review where they land, whether compression and checksums are used, whether retention is long enough, and whether the destination has room to survive the next failure. If all backups live only on the SQL Server host, the recovery plan still has a major weakness.

Review recent backup destination paths, sizes, checksums, compression, and copy-only status.

SQL

Backup files and backup options

T-SQL · 19 lines

SELECT TOP (200)    bs.database_name,    CASE bs.type        WHEN 'D' THEN 'Full'        WHEN 'I' THEN 'Differential'        WHEN 'L' THEN 'Log'        ELSE bs.type    END AS backup_type,    bs.backup_finish_date,    CONVERT(decimal(18,2), bs.backup_size / 1024.0 / 1024.0) AS backup_size_mb,    CONVERT(decimal(18,2), bs.compressed_backup_size / 1024.0 / 1024.0) AS compressed_size_mb,    bs.has_backup_checksums,    bs.is_copy_only,    bmf.physical_device_nameFROM msdb.dbo.backupset AS bsLEFT JOIN msdb.dbo.backupmediafamily AS bmf    ON bmf.media_set_id = bs.media_set_idWHERE bs.backup_finish_date >= DATEADD(day, -14, SYSDATETIME())ORDER BY bs.backup_finish_date DESC;
Review before runningT-SQLUTF-819 lines

This does not verify that files still exist on disk. Use it to check where SQL Server wrote the backups and which options were recorded.

09

Jobs

SQL Agent failed jobs

Failed SQL Agent jobs often explain health issues before any dashboard does. Focus on backup, integrity, ETL, reporting, cleanup, and business-critical jobs first. Repeated failures should become fix work, not a daily habit of acknowledging the same red status.

Find recent failed SQL Agent job steps and messages.

SQL

Recent SQL Agent failures

T-SQL · 16 lines

SELECT TOP (100)    j.name AS job_name,    h.step_id,    h.step_name,    h.run_date,    h.run_time,    h.run_duration,    h.run_status,    h.sql_severity,    h.messageFROM msdb.dbo.sysjobhistory AS hJOIN msdb.dbo.sysjobs AS j    ON j.job_id = h.job_idWHERE h.run_status = 0  AND h.run_date >= CONVERT(int, CONVERT(char(8), DATEADD(day, -14, GETDATE()), 112))ORDER BY h.instance_id DESC;
Review before runningT-SQLUTF-816 lines

run_status 0 means failed. Review step-level failures, not only the job summary row.

10

Jobs

SQL Agent notifications and operators

Job notifications are only useful when they reach someone who can act. Check operators, notification settings, and Database Mail status together. A job can be correctly configured and still fail quietly if the operator email is stale or mail delivery is broken.

Check operators, job notification settings, and Database Mail profile configuration.

SQL

SQL Agent operators and notifications

T-SQL · 26 lines

SELECT    o.name AS operator_name,    o.enabled,    o.email_address,    o.last_email_date,    o.last_email_timeFROM msdb.dbo.sysoperators AS oORDER BY o.enabled DESC, o.name; SELECT    j.name AS job_name,    j.enabled AS job_enabled,    j.notify_level_email,    o.name AS email_operator_nameFROM msdb.dbo.sysjobs AS jLEFT JOIN msdb.dbo.sysoperators AS o    ON o.id = j.notify_email_operator_idWHERE j.notify_level_email <> 0   OR j.notify_email_operator_id <> 0ORDER BY j.name; SELECT    p.name AS mail_profile_name,    p.descriptionFROM msdb.dbo.sysmail_profile AS pORDER BY p.name;
Review before runningT-SQLUTF-826 lines

This shows configuration. Send a controlled test through your normal process before trusting alert delivery.

11

Maintenance

Critical maintenance jobs

Maintenance jobs should match the current workload, not the server as it existed years ago. Look for schedule overlap, long runtimes, old job owners, broad index jobs, cleanup jobs that do not clean enough, and maintenance that competes with business workloads.

Review maintenance-like jobs, schedules, enabled state, and recent runtime.

SQL

Maintenance job timing and schedules

T-SQL · 25 lines

SELECT    j.name AS job_name,    j.enabled AS job_enabled,    s.name AS schedule_name,    s.enabled AS schedule_enabled,    h.run_date,    h.run_time,    h.run_duration,    h.run_statusFROM msdb.dbo.sysjobs AS jLEFT JOIN msdb.dbo.sysjobschedules AS js    ON js.job_id = j.job_idLEFT JOIN msdb.dbo.sysschedules AS s    ON s.schedule_id = js.schedule_idLEFT JOIN msdb.dbo.sysjobhistory AS h    ON h.job_id = j.job_id   AND h.step_id = 0   AND h.run_date >= CONVERT(int, CONVERT(char(8), DATEADD(day, -14, GETDATE()), 112))WHERE j.name LIKE N'%backup%'   OR j.name LIKE N'%check%'   OR j.name LIKE N'%integrity%'   OR j.name LIKE N'%index%'   OR j.name LIKE N'%stat%'   OR j.name LIKE N'%cleanup%'ORDER BY j.name, h.instance_id DESC;
Review before runningT-SQLUTF-825 lines

Job names are not a perfect classifier. Include any local maintenance naming conventions in the review.

12

Capacity

Disk free space and file growth

File size and growth settings show whether the server can absorb normal workload changes without avoidable pauses. Percent growth, tiny growth increments, unlimited files on nearly full drives, and uneven file placement are all worth flagging before they become incidents.

Review database files, sizes, growth settings, max size, and percent growth.

SQL

Database files and growth settings

T-SQL · 19 lines

SELECT    DB_NAME(mf.database_id) AS database_name,    mf.type_desc,    mf.name AS logical_file_name,    mf.physical_name,    CONVERT(decimal(18,2), mf.size * 8.0 / 1024.0) AS size_mb,    CASE        WHEN mf.is_percent_growth = 1 THEN CONCAT(mf.growth, N'%')        ELSE CONCAT(CONVERT(decimal(18,2), mf.growth * 8.0 / 1024.0), N' MB')    END AS growth_setting,    CASE        WHEN mf.max_size = -1 THEN N'Unlimited'        WHEN mf.max_size = 0 THEN N'No growth'        ELSE CONCAT(CONVERT(decimal(18,2), mf.max_size * 8.0 / 1024.0), N' MB')    END AS max_size_setting,    mf.is_percent_growthFROM sys.master_files AS mfWHERE DB_NAME(mf.database_id) <> N'tempdb'ORDER BY database_name, mf.type_desc, mf.file_id;
Review before runningT-SQLUTF-819 lines

SQL Server does not expose drive free space through a simple safe catalog view. Pair this with OS or monitoring data.

13

Capacity

Database log growth and reuse waits

Transaction log growth usually has a reason. The log may be waiting for a log backup, an open transaction, replication, availability group movement, or another process. Find the reuse wait before shrinking files or changing recovery model, because those reactions can hide the cause without fixing it.

Check log file size and the current reason SQL Server cannot reuse log space.

SQL

Log size and reuse waits

T-SQL · 12 lines

SELECT    d.name AS database_name,    d.recovery_model_desc,    d.log_reuse_wait_desc,    CONVERT(decimal(18,2), SUM(CASE WHEN mf.type_desc = N'LOG' THEN mf.size ELSE 0 END) * 8.0 / 1024.0) AS log_size_mb,    COUNT(CASE WHEN mf.type_desc = N'LOG' THEN 1 END) AS log_file_countFROM sys.databases AS dLEFT JOIN sys.master_files AS mf    ON mf.database_id = d.database_idWHERE d.name <> N'tempdb'GROUP BY d.name, d.recovery_model_desc, d.log_reuse_wait_descORDER BY log_size_mb DESC, d.name;
Review before runningT-SQLUTF-812 lines

The reuse wait is a current state, not a full history. Check it again during the problem window if log growth is intermittent.

14

Capacity

tempdb size, growth, and pressure

tempdb supports many ordinary SQL Server operations, so pressure there can look like several different problems at once. Review file sizing, growth, free space, version store, user objects, and internal objects. If tempdb keeps growing, connect the timing to workload, row versioning, spills, or maintenance.

Review tempdb files, growth settings, free space, user objects, internal objects, and version store usage.

SQL

tempdb files and space usage

T-SQL · 18 lines

SELECT    df.file_id,    df.name AS logical_file_name,    df.type_desc,    df.physical_name,    CONVERT(decimal(18,2), df.size * 8.0 / 1024.0) AS file_size_mb,    CASE        WHEN df.is_percent_growth = 1 THEN CONCAT(df.growth, N'%')        ELSE CONCAT(CONVERT(decimal(18,2), df.growth * 8.0 / 1024.0), N' MB')    END AS growth_setting,    CONVERT(decimal(18,2), fsu.unallocated_extent_page_count * 8.0 / 1024.0) AS free_space_mb,    CONVERT(decimal(18,2), fsu.user_object_reserved_page_count * 8.0 / 1024.0) AS user_object_mb,    CONVERT(decimal(18,2), fsu.internal_object_reserved_page_count * 8.0 / 1024.0) AS internal_object_mb,    CONVERT(decimal(18,2), fsu.version_store_reserved_page_count * 8.0 / 1024.0) AS version_store_mbFROM tempdb.sys.database_files AS dfLEFT JOIN sys.dm_db_file_space_usage AS fsu    ON fsu.file_id = df.file_idORDER BY df.file_id;
Review before runningT-SQLUTF-818 lines

This is a current snapshot. Capture it during the pressure window if tempdb problems are intermittent.

15

Logs

SQL Server error log warnings

Direct error-log reading usually uses server procedures, so this page uses the system_health session instead. It is not a full replacement for reviewing SQL Server error logs, but it gives a safe read-only look at recent reported errors captured by Extended Events.

Read recent error_reported events captured by the system_health Extended Events session.

SQL

Recent reported errors from system_health

T-SQL · 24 lines

WITH RingBuffer AS (    SELECT CAST(t.target_data AS xml) AS target_data    FROM sys.dm_xe_sessions AS s    JOIN sys.dm_xe_session_targets AS t        ON t.event_session_address = s.address    WHERE s.name = N'system_health'      AND t.target_name = N'ring_buffer'),Events AS (    SELECT        DATEADD(hour, DATEDIFF(hour, SYSUTCDATETIME(), SYSDATETIME()), xed.value(N'@timestamp', N'datetime2')) AS event_time,        xed.value(N'(data[@name="error_number"]/value)[1]', N'int') AS error_number,        xed.value(N'(data[@name="severity"]/value)[1]', N'int') AS severity,        xed.value(N'(data[@name="message"]/value)[1]', N'nvarchar(4000)') AS message    FROM RingBuffer    CROSS APPLY target_data.nodes(N'//RingBufferTarget/event[@name="error_reported"]') AS XEventData(xed))SELECT TOP (100)    event_time,    error_number,    severity,    messageFROM EventsORDER BY event_time DESC;
Review before runningT-SQLUTF-824 lines

This reads system_health, not the full SQL Server error log. Use it as a safe starting point.

16

Logs

Severity errors and corruption messages

High-severity errors, corruption-related messages, and suspect pages deserve a faster response than normal hygiene findings. Use the query to look for serious error events and suspect page records, then review backups, storage, CHECKDB output, and restore options before making changes.

Check system_health for serious error reports and msdb suspect page records.

SQL

High-severity errors and suspect pages

T-SQL · 36 lines

WITH RingBuffer AS (    SELECT CAST(t.target_data AS xml) AS target_data    FROM sys.dm_xe_sessions AS s    JOIN sys.dm_xe_session_targets AS t        ON t.event_session_address = s.address    WHERE s.name = N'system_health'      AND t.target_name = N'ring_buffer'),Events AS (    SELECT        DATEADD(hour, DATEDIFF(hour, SYSUTCDATETIME(), SYSDATETIME()), xed.value(N'@timestamp', N'datetime2')) AS event_time,        xed.value(N'(data[@name="error_number"]/value)[1]', N'int') AS error_number,        xed.value(N'(data[@name="severity"]/value)[1]', N'int') AS severity,        xed.value(N'(data[@name="message"]/value)[1]', N'nvarchar(4000)') AS message    FROM RingBuffer    CROSS APPLY target_data.nodes(N'//RingBufferTarget/event[@name="error_reported"]') AS XEventData(xed))SELECT TOP (100)    event_time,    error_number,    severity,    messageFROM EventsWHERE severity >= 17   OR error_number IN (823, 824, 825)ORDER BY event_time DESC; SELECT    DB_NAME(database_id) AS database_name,    file_id,    page_id,    event_type,    error_count,    last_update_dateFROM msdb.dbo.suspect_pagesORDER BY last_update_date DESC;
Review before runningT-SQLUTF-836 lines

Investigate any rows carefully. Serious errors should be checked against storage, backups, and integrity-check output.

17

Performance

Blocking, deadlocks, and long transactions

Blocking and long transactions are health-check items because they often expose application behavior, missing indexes, transaction scope, or workload timing. Capture the sessions, waits, commands, and transaction age before killing anything. The goal is to understand the pattern, not just clear the symptom.

Capture active blocking, waits, request details, and transaction age.

SQL

Blocking, active requests, and open transactions

T-SQL · 31 lines

SELECT    r.session_id,    r.blocking_session_id,    r.status,    r.command,    r.wait_type,    r.wait_time,    r.wait_resource,    r.open_transaction_count,    DB_NAME(r.database_id) AS database_name,    s.host_name,    s.program_name,    s.login_nameFROM sys.dm_exec_requests AS rJOIN sys.dm_exec_sessions AS s    ON s.session_id = r.session_idWHERE r.session_id <> @@SPID  AND (r.blocking_session_id <> 0 OR r.wait_type IS NOT NULL)ORDER BY r.wait_time DESC; SELECT    st.session_id,    at.transaction_begin_time,    DATEDIFF(second, at.transaction_begin_time, SYSDATETIME()) AS transaction_age_seconds,    at.transaction_type,    at.transaction_stateFROM sys.dm_tran_active_transactions AS atJOIN sys.dm_tran_session_transactions AS st    ON st.transaction_id = at.transaction_idWHERE st.session_id <> @@SPIDORDER BY transaction_age_seconds DESC;
Review before runningT-SQLUTF-831 lines

Capture this while the issue is active. After blocking clears, the most useful details may be gone.

18

Performance

Top waits and resource pressure

Wait stats are useful direction, not a diagnosis by themselves. Use them to see whether the instance is spending time on CPU scheduling, I/O, locks, log writes, memory grants, or other pressure. Then compare that direction with current requests, workload timing, and recent incidents.

Review high-level wait categories after filtering common idle waits.

SQL

Top wait stats

T-SQL · 18 lines

SELECT TOP (25)    wait_type,    waiting_tasks_count,    wait_time_ms,    signal_wait_time_ms,    wait_time_ms - signal_wait_time_ms AS resource_wait_time_ms,    CONVERT(decimal(18,2), wait_time_ms / 1000.0) AS wait_secondsFROM sys.dm_os_wait_statsWHERE wait_type NOT IN (    N'BROKER_EVENTHANDLER', N'BROKER_RECEIVE_WAITFOR', N'BROKER_TASK_STOP',    N'BROKER_TO_FLUSH', N'BROKER_TRANSMITTER', N'CHECKPOINT_QUEUE',    N'CHKPT', N'CLR_AUTO_EVENT', N'CLR_MANUAL_EVENT', N'LAZYWRITER_SLEEP',    N'LOGMGR_QUEUE', N'REQUEST_FOR_DEADLOCK_SEARCH', N'RESOURCE_QUEUE',    N'SERVER_IDLE_CHECK', N'SLEEP_TASK', N'SLEEP_SYSTEMTASK',    N'SQLTRACE_BUFFER_FLUSH', N'WAITFOR', N'XE_DISPATCHER_WAIT',    N'XE_TIMER_EVENT')ORDER BY wait_time_ms DESC;
Review before runningT-SQLUTF-818 lines

Wait stats accumulate since last restart or manual clear. Compare with uptime and current symptoms.

19

Performance

Query Store status and plan regressions

Query Store can make performance review much less speculative, but only when it is enabled, writable, and sized sensibly. Check Query Store state first, then look for queries where recent duration is materially worse than the longer baseline.

Check Query Store state in the current database and compare recent duration with longer history.

SQL

Query Store state and recent regressions

T-SQL · 34 lines

SELECT    desired_state_desc,    actual_state_desc,    readonly_reason,    current_storage_size_mb,    max_storage_size_mb,    query_capture_mode_descFROM sys.database_query_store_options; WITH RuntimeStats AS (    SELECT        q.query_id,        qt.query_sql_text,        rs.last_execution_time,        rs.avg_duration,        AVG(rs.avg_duration) OVER (PARTITION BY q.query_id) AS overall_avg_duration    FROM sys.query_store_query AS q    JOIN sys.query_store_query_text AS qt        ON qt.query_text_id = q.query_text_id    JOIN sys.query_store_plan AS p        ON p.query_id = q.query_id    JOIN sys.query_store_runtime_stats AS rs        ON rs.plan_id = p.plan_id    WHERE rs.last_execution_time >= DATEADD(day, -7, SYSDATETIME()))SELECT TOP (25)    query_id,    last_execution_time,    CONVERT(decimal(18,2), avg_duration / 1000.0) AS avg_duration_ms,    CONVERT(decimal(18,2), overall_avg_duration / 1000.0) AS overall_avg_duration_ms,    LEFT(query_sql_text, 600) AS query_text_sampleFROM RuntimeStatsWHERE avg_duration > overall_avg_duration * 2ORDER BY avg_duration DESC;
Review before runningT-SQLUTF-834 lines

Query Store views are database-scoped. Run this in each important user database where Query Store is enabled.

20

Config

Max server memory, MAXDOP, and cost threshold

Core settings should be reviewed carefully because bad defaults can hurt, but casual changes can hurt too. Check memory, MAXDOP, cost threshold, backup compression default, and ad hoc workload handling against the actual server shape and workload before scheduling any change.

Review core settings that commonly affect memory, parallelism, backups, and ad hoc workloads.

SQL

Core instance configuration

T-SQL · 19 lines

SELECT    name,    value,    value_in_use,    is_dynamic,    is_advanced,    descriptionFROM sys.configurationsWHERE name IN (    N'max server memory (MB)',    N'min server memory (MB)',    N'max degree of parallelism',    N'cost threshold for parallelism',    N'backup compression default',    N'optimize for ad hoc workloads',    N'remote admin connections',    N'xp_cmdshell')ORDER BY name;
Review before runningT-SQLUTF-819 lines

This only reads configuration. Review workload and server shape before changing values.

21

Storage

Storage latency and I/O stalls

Storage latency affects backups, restores, data files, log files, tempdb, and query response time. The file latency query helps separate read pressure, write pressure, and log pressure by database file. Treat the numbers as a starting point and compare them with workload windows.

Use virtual file stats to compare read, write, and overall latency by file.

SQL

File latency by database file

T-SQL · 14 lines

SELECT    DB_NAME(mf.database_id) AS database_name,    mf.type_desc,    mf.name AS logical_file_name,    mf.physical_name,    vfs.num_of_reads,    vfs.num_of_writes,    CONVERT(decimal(18,2), vfs.io_stall_read_ms * 1.0 / NULLIF(vfs.num_of_reads, 0)) AS avg_read_ms,    CONVERT(decimal(18,2), vfs.io_stall_write_ms * 1.0 / NULLIF(vfs.num_of_writes, 0)) AS avg_write_ms,    CONVERT(decimal(18,2), vfs.io_stall * 1.0 / NULLIF(vfs.num_of_reads + vfs.num_of_writes, 0)) AS avg_io_msFROM sys.master_files AS mfCROSS APPLY sys.dm_io_virtual_file_stats(mf.database_id, mf.file_id) AS vfsWHERE DB_NAME(mf.database_id) <> N'tempdb'ORDER BY avg_io_ms DESC, database_name, mf.file_id;
Review before runningT-SQLUTF-814 lines

These counters accumulate since file open. Compare with uptime and workload timing.

22

Security

Patch level and support status

Patch level and support status shape the next maintenance decision. The query gives the local SQL Server build, edition, clustering, HADR state, and host shape; compare the build to Microsoft’s current support and CU information before scheduling an update.

Collect version, edition, update level, clustering, HADR, CPU, memory, and startup time.

SQL

SQL Server build and host shape

T-SQL · 14 lines

SELECT    SERVERPROPERTY('ServerName') AS server_name,    SERVERPROPERTY('MachineName') AS machine_name,    SERVERPROPERTY('Edition') AS edition,    SERVERPROPERTY('ProductVersion') AS product_version,    SERVERPROPERTY('ProductLevel') AS product_level,    SERVERPROPERTY('ProductUpdateLevel') AS product_update_level,    SERVERPROPERTY('IsClustered') AS is_clustered,    SERVERPROPERTY('IsHadrEnabled') AS is_hadr_enabled,    si.sqlserver_start_time,    si.cpu_count,    si.scheduler_count,    si.physical_memory_kb / 1024 AS physical_memory_mbFROM sys.dm_os_sys_info AS si;
Review before runningT-SQLUTF-814 lines

Compare the build with Microsoft’s current SQL Server update information before scheduling patch work.

23

Security

Sysadmin access and elevated logins

Elevated access should be boring and explainable. Review sysadmin membership, server roles, disabled logins, and old accounts. Remove access through a controlled change, especially when shared accounts or application logins have more rights than they need.

List server role membership, including sysadmin and other elevated roles.

SQL

Server roles and elevated logins

T-SQL · 11 lines

SELECT    roles.name AS server_role,    members.name AS login_name,    members.type_desc AS login_type,    members.is_disabledFROM sys.server_role_members AS srmJOIN sys.server_principals AS roles    ON roles.principal_id = srm.role_principal_idJOIN sys.server_principals AS members    ON members.principal_id = srm.member_principal_idORDER BY roles.name, members.name;
Review before runningT-SQLUTF-811 lines

Review shared accounts and application logins carefully before removing access.

24

Security

Service accounts, proxies, and credentials

Service accounts and SQL Agent proxies are where automation risk often hides. Check which accounts run SQL Server services, which proxies and credentials exist, and which jobs depend on them. Document usage before changing passwords or permissions.

Review SQL Server services, SQL Agent proxies, credentials, and proxy subsystem mappings.

SQL

Service accounts, proxies, and credentials

T-SQL · 23 lines

SELECT    servicename,    startup_type_desc,    status_desc,    service_account,    last_startup_timeFROM sys.dm_server_servicesORDER BY servicename; SELECT    c.name AS credential_name,    c.credential_identity,    p.name AS proxy_name,    p.enabled AS proxy_enabled,    s.subsystemFROM sys.credentials AS cLEFT JOIN msdb.dbo.sysproxies AS p    ON p.credential_id = c.credential_idLEFT JOIN msdb.dbo.sysproxysubsystem AS ps    ON ps.proxy_id = p.proxy_idLEFT JOIN msdb.dbo.syssubsystems AS s    ON s.subsystem_id = ps.subsystem_idORDER BY c.name, p.name, s.subsystem;
Review before runningT-SQLUTF-823 lines

Credential metadata is visible, but secret values are not. Document usage before changing accounts.

25

Security

Unsafe enabled features and linked servers

Optional features and linked servers should have a current reason to exist. Some are legitimate in specific environments, but they should not be enabled just because an old job once needed them. Review enabled features, linked-server targets, and remote login mappings.

Check selected surface-area features and linked-server configuration.

SQL

Enabled features and linked servers

T-SQL · 25 lines

SELECT    name,    value_in_use,    descriptionFROM sys.configurationsWHERE name IN (    N'xp_cmdshell',    N'Ole Automation Procedures',    N'clr enabled',    N'external scripts enabled',    N'Ad Hoc Distributed Queries')ORDER BY name; SELECT    name AS linked_server_name,    product,    provider,    data_source,    is_linked,    is_remote_login_enabled,    is_rpc_out_enabledFROM sys.serversWHERE server_id <> 0ORDER BY name;
Review before runningT-SQLUTF-825 lines

Some features are legitimate when controlled. The finding is an enabled feature without a current reason or owner.

26

Monitoring

Monitoring alerts and routing

SQL Server Agent alerts and operators are not the whole monitoring setup, but they are a useful local check. Confirm that important alerts exist, notifications point to current operators, and Database Mail is configured. Then compare this with the external monitoring system if one exists.

Review SQL Agent alerts, configured operators, and notification routing.

SQL

SQL Agent alerts and routing

T-SQL · 22 lines

SELECT    a.name AS alert_name,    a.enabled,    a.message_id,    a.severity,    a.delay_between_responses,    a.last_occurrence_date,    a.last_occurrence_timeFROM msdb.dbo.sysalerts AS aORDER BY a.enabled DESC, a.name; SELECT    a.name AS alert_name,    o.name AS operator_name,    o.email_address,    n.notification_methodFROM msdb.dbo.sysnotifications AS nJOIN msdb.dbo.sysalerts AS a    ON a.id = n.alert_idJOIN msdb.dbo.sysoperators AS o    ON o.id = n.operator_idORDER BY a.name, o.name;
Review before runningT-SQLUTF-822 lines

This is local SQL Agent alerting only. Compare it with your central monitoring system.

27

Monitoring

Alert noise and ignored warnings

Noisy alerts are a process problem more than a SQL metadata problem. Review repeated warnings, muted monitors, ignored tickets, and thresholds that no longer match the workload. If people have learned to ignore an alert, either tune it or turn it into real fix work.

28

HA / DR

HA/DR status and replica health

HA/DR health needs more than a green dashboard. Check availability group, replica, synchronization, send queue, redo queue, and failover mode where Always On is used. If the server does not use availability groups, record the actual DR mechanism instead of forcing this query to matter.

Review availability group and replica state where Always On is used.

SQL

Always On availability group status

T-SQL · 24 lines

SELECT    ag.name AS availability_group_name,    ar.replica_server_name,    ars.role_desc,    ars.connected_state_desc,    ars.synchronization_health_desc,    ar.availability_mode_desc,    ar.failover_mode_descFROM sys.availability_groups AS agJOIN sys.availability_replicas AS ar    ON ar.group_id = ag.group_idLEFT JOIN sys.dm_hadr_availability_replica_states AS ars    ON ars.replica_id = ar.replica_idORDER BY ag.name, ar.replica_server_name; SELECT    DB_NAME(drs.database_id) AS database_name,    drs.synchronization_state_desc,    drs.synchronization_health_desc,    drs.database_state_desc,    drs.log_send_queue_size,    drs.redo_queue_sizeFROM sys.dm_hadr_database_replica_states AS drsORDER BY database_name;
Review before runningT-SQLUTF-824 lines

If the instance does not use availability groups, this should return no rows. Review the actual DR mechanism instead.

29

HA / DR

Failover readiness: jobs, logins, DNS, app behavior

Failover readiness is partly SQL Server and partly application behavior. Jobs, logins, linked servers, credentials, DNS, firewall rules, listeners, and application validation all matter. The safest answer is a recent failover test that checked the application, not only the SQL role change.

30

Output

Final action list: fix now, schedule, review deeper, watch, accept

End the review by turning findings into work. Separate items that need action now from items that belong in the next maintenance window, deeper review, trend watching, or documented acceptance. A health check that ends as a neutral list is not finished.

What to do after the SQL Server health check checklist

Do not change MAXDOP, memory, tempdb, backup schedules, index maintenance, or HA settings just because a checklist row looks suspicious. A checklist finds candidates. It does not replace workload review.

A useful review turns each finding into a clear next step: confirm the workload impact, understand the failure mode, choose the action, and schedule the change only when it still makes sense.

Decide based on the checks

Fix now

Fix in the next maintenance window

Review deeper

Watch for trend

Accept for now

Next step

Use the SQL Server health check guide when you need the read-only scripts and deeper DBA checks behind this checklist.

Use SQL Server health audit when backups, jobs, monitoring, configuration, and ownership all need review at the same time.