Home / Databases / SQL Server

DatabasesHow-toIntermediate

Know the moment SQL Server restarts or fails over: startup jobs, AG role-change alerts and event-log checks

Unexpected restarts and failovers often go unnoticed until users complain. Set up an Agent job that emails on every service start with the node, start time and reason clues; add availability group role-change alerts; and check cluster and Windows events to find out why it happened.

Deepak Bhardwaj5 min read

Applies toSQL Server 2016–2025 on Windows (standalone, FCI and availability groups)

A restart, a cluster failover or an availability group role change are all events you should hear about from your own monitoring, within a minute. Three building blocks cover them all.

Event Detection
SQL Server service (re)start, FCI failover, server reboot Agent job scheduled "Start automatically when SQL Server Agent starts"
Availability group role change Agent alert on error 1480 (and AG health checks)
Agent restarted on its own Same startup job; compare SQL Server and Agent start times

1. Startup notification job

USE msdb;
DECLARE @job_id uniqueidentifier;
EXEC dbo.sp_add_job @job_name = N'DBA - Notify - SQL Server started', @owner_login_name = N'sa',
     @category_name = N'[Uncategorized (Local)]', @job_id = @job_id OUTPUT;

EXEC dbo.sp_add_jobstep @job_id = @job_id, @step_name = N'Send notification', @subsystem = N'TSQL', @database_name = N'master',
     @command = N'
WAITFOR DELAY ''00:01:00'';   -- let Database Mail and the network settle after startup
DECLARE @body nvarchar(max), @subject nvarchar(255);
SELECT @subject = CONCAT(N''SQL Server started: '', @@SERVERNAME, N'' on node '', CAST(SERVERPROPERTY(''ComputerNamePhysicalNetBIOS'') AS nvarchar(128)));
SELECT @body = CONCAT(
  N''Instance:            '', @@SERVERNAME, CHAR(10),
  N''Running on node:     '', CAST(SERVERPROPERTY(''ComputerNamePhysicalNetBIOS'') AS nvarchar(128)), CHAR(10),
  N''SQL Server started:  '', CONVERT(nvarchar(19), sqlserver_start_time, 120), CHAR(10),
  N''Agent started:       '', CONVERT(nvarchar(19), GETDATE(), 120), CHAR(10),
  N''Windows uptime (min):'', ms_ticks / 60000, CHAR(10),
  N''Version:             '', CAST(SERVERPROPERTY(''ProductVersion'') AS nvarchar(32)), N'' '', CAST(SERVERPROPERTY(''ProductUpdateLevel'') AS nvarchar(32)), CHAR(10),
  N''Clustered (FCI):     '', CAST(SERVERPROPERTY(''IsClustered'') AS nvarchar(5)))
FROM sys.dm_os_sys_info;
EXEC msdb.dbo.sp_send_dbmail @profile_name = N''DBA'', @recipients = N''dba-team@corp.example.com'', @subject = @subject, @body = @body;';

EXEC dbo.sp_add_jobschedule @job_id = @job_id, @name = N'At Agent start', @freq_type = 64;
EXEC dbo.sp_add_jobserver @job_id = @job_id;

How to read the email:

What you see Likely cause
Windows uptime ≈ SQL start time Server reboot (patching, crash, host issue)
Windows uptime long, SQL start recent SQL Server service restarted (manual restart, crash, FCI failover to a node that was already up)
Node name changed since last email FCI failover
SQL start much earlier than Agent start Only Agent restarted

Add an event-log write (RAISERROR ... WITH LOG) to the same step if your monitoring tool reads the Application log.

2. Availability group role-change alerts

When a replica changes role, SQL Server logs error 1480 ("The ... database ... is changing roles from ... to ... because ..."). Create an Agent alert on every replica:

EXEC msdb.dbo.sp_add_alert @name = N'AG role change (1480)', @message_id = 1480, @severity = 0,
     @include_event_description_in = 1, @delay_between_responses = 60;
EXEC msdb.dbo.sp_add_notification @alert_name = N'AG role change (1480)', @operator_name = N'DBA On-call', @notification_method = 1;

Other useful AG messages to alert on (check that they're logged on your version: SELECT message_id, is_event_logged FROM sys.messages WHERE language_id = 1033 AND message_id IN (...)):

Message Meaning
35264 Data movement suspended for a database
35265 Data movement resumed
41404, 41405 AG offline / not ready for automatic failover

An alert only fires if the message is written to the Windows Application log. If is_event_logged = 0, make it logged with sp_altermessage message_id, 'WITH_LOG', 'true' where that's supported, or detect it from AG DMVs instead.

3. Find out why it happened

-- current and previous error logs: startup, shutdown, failover reasons
EXEC master.dbo.xp_readerrorlog 1, 1, N'shutdown';      -- previous log
EXEC master.dbo.xp_readerrorlog 0, 1, N'Server process ID';   -- startup lines in current log
EXEC master.dbo.xp_readerrorlog 0, 1, N'1480';

-- AG health right now
SELECT ag.name, ar.replica_server_name, ars.role_desc, ars.connected_state_desc, ars.synchronization_health_desc
FROM sys.availability_groups ag
JOIN sys.availability_replicas ar ON ar.group_id = ag.group_id
JOIN sys.dm_hadr_availability_replica_states ars ON ars.replica_id = ar.replica_id;
# Windows: unexpected shutdowns (6008), user/initiated restarts (1074), cluster resource failures
Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = 6008, 1074; StartTime = (Get-Date).AddDays(-2) } |
    Select-Object TimeCreated, Id, Message | Format-List

# cluster: generate the cluster log for the last 60 minutes on all nodes
Get-ClusterLog -Destination C:\Temp\ClusterLogs -TimeSpan 60 -UseLocalTime
Get-WinEvent -LogName 'Microsoft-Windows-FailoverClustering/Operational' -MaxEvents 50 | Select-Object TimeCreated, Id, Message
Clue Points to
System event 1074 with a user or TrustedInstaller Planned or patch reboot
System event 6008 Unexpected shutdown (crash, power, host)
Cluster log: resource failed health check / lease timeout SQL Server unresponsive, or node under heavy pressure
Cluster log: lost quorum / node removed Network or witness problem
SQL error log ends without a clean "shutdown" message Crash or forced kill; check for dump files in the LOG folder

4. Test it

  • Restart the SQL Server Agent service: you should get an email (Agent start only).
  • In a maintenance window, fail over the FCI or AG: you should get the startup email (FCI) or the 1480 alert (AG) from each replica.

Tip

Keep the startup job on every instance as part of your build standard. It costs nothing, and it turns "the database was slow at 3 am" into "the cluster failed over at 02:57 because the lease timed out".