The Short Answer
| Scenario | Logins | Agent jobs |
|---|---|---|
| Built-in HA within one instance (General Purpose or Business Critical, including zone redundancy) | Yes | Yes |
| Failover group between two instances | No | No |
| Managed Instance link (SQL Server to MI and back) | No | No |
| Restore or copy a database to another instance | No | No |
The rule behind the table is simple: logins live in master, jobs live in msdb, and system databases never leave their instance. Everything that replicates between instances replicates user databases only.
Within One Instance: Nothing to Synchronize
The built-in high availability of Managed Instance protects the whole instance, system databases included. In General Purpose, a failed compute node is replaced and the instance starts again on the same remote storage. In Business Critical, the instance runs on four replicas with local storage, kept in sync with Always On technology under the hood. In both tiers, a failover or a maintenance event brings back the same instance with the same master and msdb. Logins, jobs, linked servers and Database Mail are simply there.
Microsoft does not leave anything to sync here, and that is correct. It is also why the question rarely comes up during the first months on Managed Instance: local failovers happen regularly during maintenance, and nobody notices anything.
Failover Groups: The Databases Move, the Instance Does Not
A failover group replicates user databases from a primary instance to a secondary instance in another region. The documentation is explicit: system databases are not replicated to the secondary instance. Scenarios that depend on objects in the system databases, such as server logins and Agent jobs, require the objects to be created manually on the secondary and kept in sync with the primary.
There is exactly one exception: the service master key is copied once, when the failover group is created. Later changes to it on the primary are not replicated. Instance settings such as backup storage redundancy or long-term retention policies are not replicated either.
What exists only on the instance where you created it:
- SQL logins and Microsoft Entra logins, including server role memberships and the disabled state
- SQL Server Agent jobs, schedules, operators and alerts
- Linked servers and server-level credentials
- Database Mail profiles and accounts
- Server-level certificates, server audits and server configuration
What does replicate: everything inside the user databases. That includes database users, roles, permissions, schemas, procedures, and the data. This is the source of the classic symptom after a failover: the database users are there, but the logins they belong to are missing, or exist with a different SID.
What You See After an Unprepared Failover
- Error 18456, "Login failed" for every SQL login that was never created on the secondary.
- Orphaned users for logins that were created on the secondary by hand with the same name but a new SID. The login works, the database access does not.
- No maintenance, no ETL, no cleanup jobs on the new primary, because
msdbthere is empty or outdated. - Failing jobs on the old primary, which is now the geo-secondary: its jobs keep running on schedule against databases that are now read-only.
Synchronizing Logins
The requirement is not just "the same logins", but the same SID. Database users are mapped to logins by SID, not by name. A SQL login created on the secondary with CREATE LOGIN app WITH PASSWORD = ... gets a new SID, and every database user that belongs to app is orphaned after failover.
For SQL logins, create them on the secondary with the SID and the password hash from the primary. For Microsoft Entra logins the SID is derived from the Entra object ID, so CREATE LOGIN ... FROM EXTERNAL PROVIDER on both instances produces the same SID automatically. The following script runs on the primary and generates the statements for the secondary, including server role memberships and disabled logins. Every CREATE is guarded, so the output can be applied repeatedly:
-- Run on the PRIMARY. Output: script to run on the SECONDARY.
SET NOCOUNT ON;
SELECT N'IF SUSER_SID(N' + QUOTENAME(sl.name, '''') + N') IS NULL CREATE LOGIN ' + QUOTENAME(sl.name)
+ N' WITH PASSWORD = ' + CONVERT(nvarchar(max), CAST(LOGINPROPERTY(sl.name, 'PasswordHash') AS varbinary(256)), 1)
+ N' HASHED, SID = ' + CONVERT(nvarchar(max), sl.sid, 1)
+ N', DEFAULT_DATABASE = ' + QUOTENAME(sl.default_database_name)
+ N', CHECK_POLICY = ' + CASE sl.is_policy_checked WHEN 1 THEN N'ON' ELSE N'OFF' END
+ N', CHECK_EXPIRATION = ' + CASE sl.is_expiration_checked WHEN 1 THEN N'ON' ELSE N'OFF' END + N';' AS cmd
FROM sys.sql_logins sl
WHERE sl.name NOT LIKE N'##%'
AND sl.principal_id > 1 -- skip the instance admin login, each instance has its own
UNION ALL
SELECT N'IF SUSER_SID(N' + QUOTENAME(sp.name, '''') + N') IS NULL CREATE LOGIN ' + QUOTENAME(sp.name) + N' FROM EXTERNAL PROVIDER;'
FROM sys.server_principals sp
WHERE sp.type IN ('E', 'X') -- Microsoft Entra user and group logins
UNION ALL
SELECT N'ALTER SERVER ROLE ' + QUOTENAME(r.name) + N' ADD MEMBER ' + QUOTENAME(m.name) + N';'
FROM sys.server_role_members rm
JOIN sys.server_principals r ON r.principal_id = rm.role_principal_id
JOIN sys.server_principals m ON m.principal_id = rm.member_principal_id
WHERE m.type IN ('S', 'E', 'X')
AND m.name NOT LIKE N'##%'
AND m.principal_id > 1
UNION ALL
SELECT N'ALTER LOGIN ' + QUOTENAME(sp.name) + N' DISABLE;'
FROM sys.server_principals sp
WHERE sp.is_disabled = 1
AND sp.type IN ('S', 'E', 'X')
AND sp.name NOT LIKE N'##%'
AND sp.principal_id > 1;
In the test, three SQL logins (one with a server role, one disabled) were scripted, dropped and recreated from the output. SID, password hash, role membership and disabled state were identical afterwards, the original password worked, and running the script a second time changed nothing.
Two things the script deliberately does not do: it does not drop logins on the secondary that no longer exist on the primary, and it does not change passwords of logins that already exist. Both are decisions with consequences, and an automated sync that removes logins should at least log what it removed. For password changes, a simple approach is to compare the password hash on both sides and recreate the login when it differs. The same idea for on-premises availability groups, as a PowerShell solution, is described in Synchronizing AlwaysOn Logins with PowerShell.
Synchronizing Agent Jobs
Jobs are harder than logins, because a job must exist on both instances but may only do its work on the current primary. Two parts are needed: getting the job definitions to the secondary, and making every job aware of its role.
Make Every Job Role-Aware
A job on the geo-secondary runs on schedule like any other job. The databases are readable but not writable, so anything that changes data fails. The robust pattern is to check the database itself in every step and do nothing if it is not writable:
IF DATABASEPROPERTYEX(N'SalesDb', 'Updateability') = N'READ_WRITE'
EXEC SalesDb.dbo.usp_NightlyAggregation;
Name the database explicitly. A job step runs in the context of its configured database, often master or msdb, and those are always writable. Databases on the geo-secondary are read-only, so the check does not return READ_WRITE there, and the job step succeeds without doing anything, so the secondary does not produce a stream of failure alerts. Verify the behavior once after your first planned failover.
This pattern has a second benefit. The job definition in msdb does not replicate, but the stored procedure in the user database does. Keep jobs thin: a job step that only calls a procedure in the user database is trivial to recreate, and the actual logic fails over with the data. Note that Agent on Managed Instance supports T-SQL steps, but not CmdExec or PowerShell steps, so the logic belongs in T-SQL anyway.
Getting the Definitions to the Secondary
There are three practical options:
- Jobs as code (recommended). Keep every job as a T-SQL deployment script in source control and deploy it to both instances in the same release. With the role check above, the job can be active on both sides. This is the only option where you know exactly which version of a job runs where.
- Scheduled copy from the primary. A PowerShell job scripts the jobs on the primary and creates or updates them on the secondary, for example with dbatools (
Get-DbaAgentJobandExport-DbaScript, orCopy-DbaAgentJob). Works well for environments where jobs are still created by hand, as long as somebody owns the sync job and its errors. - Microsoft's sample solution. Microsoft publishes scripts ("Azure SQL Managed Instance: Sync Agent Jobs and Logins in Failover Group", version 2.0, July 2024) that store job definitions in a separate database and recreate them on the new primary after a failover. Read the limitations before you use it: a job that is running during the failover loses its progress and starts again at step 1, job history is lost because jobs are deleted and recreated, and jobs that should run only on the primary or only on the secondary are not supported without changes.
The Rest of the Instance: A Checklist
Logins and jobs are the most visible gaps, but not the only ones. Before you call a failover group production-ready, check each of these on the secondary:
| Object | Replicated? | What to do |
|---|---|---|
| Database users, roles, permissions | Yes | Nothing, but they need matching login SIDs |
| SQL and Entra logins, server roles | No | Script above, same SID |
| Agent jobs, schedules, operators, alerts | No | Jobs as code, role check in every step |
| Linked servers, server credentials | No | Create on both; check whether the target is reachable from the secondary region |
| Database Mail | No | Create profile and account on both, same profile name |
| Server-level certificates and audits | No | Create on both |
| Service master key | Once, at creation | Later changes must be repeated manually |
| Instance settings (backup redundancy, LTR policies) | No | Configure on both |
For applications, connect only to the failover group listener (<fog-name>.<zone>.database.windows.net), never to an instance name. Otherwise logins and jobs may be perfect and the application still writes to the old primary. Connection string details are in SQL Server Connection Strings.
Test It Before You Need It
A failover group that has never failed over is a hypothesis. A planned failover takes minutes and is reversible:
# Make the secondary instance the primary (run in the secondary's resource group/region)
Switch-AzSqlDatabaseInstanceFailoverGroup -ResourceGroupName 'rg-sql-weu' `
-Location 'westeurope' -Name 'fog-sales'
After the switch, check three things: can every application log in through the listener, did the first scheduled run of each job succeed on the new primary, and did the jobs on the new secondary skip their work instead of failing. Then fail back and check the same in the other direction. Repeat the test after every larger change to logins or jobs, or at least twice a year.
The Bottom Line
Within one Managed Instance, logins and Agent jobs are part of the instance and survive every local failover. As soon as a second instance is involved, through a failover group, the Managed Instance link, or a restore, only user databases travel. Logins have to be created on the secondary with the same SID, jobs have to exist on both sides and check whether their database is writable, and linked servers, Database Mail and certificates need the same care. None of this is difficult. It just has to be done before the failover, because during a regional outage is the worst moment to find out what master and msdb contained.
Related reading: Logins, Users, Roles and Permissions, Distributed AG from on-premises to Azure and SQL Server Failover Cluster or Always On Availability Groups?