Clock, job cards and an envelope, representing SQL Server Agent job scheduling and notifications

SQL Server Agent Best Practices: Job Owners, Notifications, History and Scheduling

Practices that make SQL Server Agent reliable: non-personal job owners, failure notifications and the fail-safe operator, job history retention, overlapping schedules, proxies and Always On jobs.

March 30, 2022

SQL Server Agent is the invisible operations team that runs your backups, CHECKDB, index maintenance, ETL and reports. Often nobody looks at it after installation, until someone notices that the backup job has been failing silently for weeks, that job history was purged right when it was needed, or that overnight jobs stopped together with the account of an employee who left.

This guide brings the practices that make Agent reliable into one place: job owners, notifications and the fail-safe operator, job history retention, overlapping schedules, permissions, and managing jobs in Always On environments.

Managed DBA Service

Are your overnight jobs really running?

Aryasoft organizes your Agent jobs, notifications and maintenance plans, and acts on a failed job before you notice it.

Explore the Service

1. Job Owners: Don’t Use Personal Accounts

By default, the login of whoever creates a job becomes its owner. When that person leaves and their Active Directory account is disabled, or when the domain controller is briefly unreachable, the job fails because the owner’s access can’t be verified. It usually happens exactly at night, when nobody is watching.

Make jobs owned by sa or by a dedicated, non-personal login. To find jobs owned by personal accounts:

SELECT j.name AS job_name,
       SUSER_SNAME(j.owner_sid) AS owner_name,
       j.enabled
FROM msdb.dbo.sysjobs AS j
WHERE SUSER_SNAME(j.owner_sid) IS NULL          -- jobs whose owner no longer exists
   OR SUSER_SNAME(j.owner_sid) <> N'sa'
ORDER BY owner_name, job_name;

-- Change the owner
EXEC msdb.dbo.sp_update_job @job_name = N'Nightly Backup', @owner_login_name = N'sa';

The same setting can be changed in the Owner field on the General page of the job properties:

Owner field in SQL Server Agent job properties

2. Notifications: Don’t Let a Failed Job Stay Silent

Users shouldn’t be the first to notice that a job failed. Three pieces have to work together: a working Database Mail profile, that profile assigned to Agent, and an operator defined on the jobs.

  1. Configure Database Mail and send a test email.
  2. Enable the mail profile on the Alert System page of the Agent properties. The change takes effect when the Agent service restarts.
  3. Create an operator that goes to a team address rather than a person (for example DBA Team).
  4. On every job’s Notifications page, enable an email notification for “When the job fails”.

Notifications page in SQL Server Agent job properties with email on job failure

To find enabled jobs without a failure notification:

SELECT j.name AS job_name
FROM msdb.dbo.sysjobs AS j
WHERE j.enabled = 1
  AND (j.notify_level_email = 0 OR j.notify_email_operator_id = 0)
ORDER BY j.name;

The fail-safe operator

The fail-safe operator is the backup recipient that gets notifications when the regular operator can’t be reached, or when Agent can’t access the operator information in msdb. It also steps in for hours not covered by operators that are set to receive notifications only at certain times. It’s defined on the Alert System page of the Agent properties:

Fail-safe operator setting on the SQL Server Agent Alert System page

Our SQL Server alerts guide covers how to set up Agent alerts for serious errors and disk corruption.

Warning

If no Database Mail profile is assigned to Agent, the notifications defined on jobs are silently not sent, without any error. After setup, deliberately fail a test job and confirm the email actually arrives; repeat this at least once a year.

3. Job History: The Defaults Are Too Low

By default, Agent limits job history to 1,000 rows in total and 100 rows per job. A few frequent jobs (for example a log backup every 15 minutes) fill that limit within days and push out the history of every other job. When you need to investigate a problem, the record you need is already gone.

Raise the limits on the History page of the Agent properties; for most environments, tens of thousands of rows in total and a few thousand per job is a reasonable start:

Job history row limits on the SQL Server Agent History page

A more predictable approach is to remove the row limit and purge history by age. A weekly job with this command is enough:

DECLARE @oldest datetime = DATEADD(DAY, -60, GETDATE());
EXEC msdb.dbo.sp_purge_jobhistory @oldest_date = @oldest;

Keep an eye on msdb size in that case; msdb databases whose backup and job history is never purged can grow to gigabytes over time.

4. Overlapping Schedules

Backup, index maintenance, CHECKDB and ETL jobs starting at the same time create short but intense CPU and disk pressure, and slow each other down. To find jobs with the same start time:

SELECT STUFF(STUFF(RIGHT(N'000000' + CAST(s.active_start_time AS nvarchar(6)), 6), 5, 0, N':'), 3, 0, N':') AS start_time,
       COUNT(*) AS job_count,
       STRING_AGG(j.name, N', ') AS jobs
FROM msdb.dbo.sysjobs AS j
JOIN msdb.dbo.sysjobschedules AS js ON js.job_id = j.job_id
JOIN msdb.dbo.sysschedules AS s ON s.schedule_id = js.schedule_id
WHERE j.enabled = 1 AND s.enabled = 1
GROUP BY s.active_start_time
HAVING COUNT(*) > 1
ORDER BY job_count DESC;

Spread the jobs in the result apart, especially the heavy ones, either to different times or as consecutive steps in a single sequential job. Check from the job duration trend that maintenance finishes before business hours.

5. Permissions: Use Proxies and Credentials

CmdExec and PowerShell steps run with the permissions of the Agent service account by default. Instead of giving the service account broad rights on file servers or other systems, define a Windows account with the permissions the step needs as a credential, and use it through a proxy. Each step then runs only with the permissions it needs, and the service account stays least-privileged.

6. Make Job Steps Resilient

  • For steps that can be hit by network issues or transient blocking, set Retry attempts and Retry interval.
  • For steps that produce long output, write it to a file from the Advanced page; the job history message is limited to 4,000 characters.
  • Choose deliberately what the next steps do when a step fails (On failure action).
  • Keep jobs as scripts in source control, so no job is forgotten when a server is rebuilt or migrated.

7. Jobs in Always On Environments

Agent jobs live in msdb and aren’t synchronized with the availability group. So that jobs can run on the new primary after a failover:

  • Create the jobs on every replica and apply changes to all of them.
  • In the job’s first step, check whether the replica is primary; exit quietly on a secondary.
  • In SQL Server 2022 and later Enterprise edition, a contained availability group can remove this problem by keeping jobs in the group’s own msdb.
-- First job step: exit if this replica isn't primary
IF sys.fn_hadr_is_primary_replica(N'SalesDB') <> 1
BEGIN
    PRINT N'This replica is not primary; job step skipped.';
    RETURN;
END

Tip

Jobs and logins drifting apart between replicas is one of the most common surprises after a failover. A check that regularly compares jobs across replicas exposes the problem before the first real failover does.

Agent Checklist

  • Every job is owned by sa or a non-personal login.
  • Database Mail works; a mail profile is assigned to Agent and tested.
  • Every enabled job sends failure notifications to a team operator; a fail-safe operator is defined.
  • Job history limits are raised or history is purged by age; msdb size is monitored.
  • Heavy jobs are spread across different times.
  • CmdExec and PowerShell steps run through proxies.
  • Jobs are identical across Always On replicas and check for the primary.
  • The Agent service starts automatically; jobs are in source control.
sa or a dedicated, non-personal login. Jobs owned by a personal account fail when that account is disabled or the domain can’t be reached.
The default limit is 1,000 rows in total and 100 per job. Frequent jobs fill it quickly and purge older records. Raise the limits or purge history by age with sp_purge_jobhistory.
The backup recipient that gets alerts when the regular operator can’t be reached or Agent can’t access operator information. It’s set on the Alert System page of the Agent properties.
No. Jobs live in msdb; they must be created on every replica and check for the primary. Contained availability groups in SQL Server 2022 Enterprise are the exception.

Managed DBA Service

Make your Agent jobs reliable

Aryasoft’s senior DBA team sorts out job owners, notifications, history settings and schedules, responds to failed jobs within SLA times and reports every month.

4.7/5