Grant VIEW SERVER STATE in SQL Server

VIEW SERVER STATE is the permission that unlocks most server-level DMVs and DMFs, and it’s the one people reach for first when a monitoring account starts throwing permission errors.

It is rarely the only one needed. A monitoring or read-only DBA login usually needs a small set of permissions working together, and on SQL Server 2022 and later that set got wider, not narrower. This post covers VIEW SERVER STATE properly, then the other permissions that go with it, and finishes with a single script that builds a read-only monitoring login end to end.

Common DMVs VIEW SERVER STATE enables include:

  • Session and request state: sys.dm_exec_sessions, sys.dm_exec_requests
  • Connection details: sys.dm_exec_connections
  • Waits and pressure signals: sys.dm_os_wait_stats
  • Performance counters: sys.dm_os_performance_counters
  • Plan cache stats: sys.dm_exec_query_stats (plus sys.dm_exec_sql_text / sys.dm_exec_query_plan)

Grant it deliberately. It exposes operational metadata about the instance.


What It Allows

VIEW SERVER STATE allows a login to read server-scoped state from DMVs and DMFs.

That usually means visibility into:

  • sessions, requests, and connection details
  • waits and workload pressure signals
  • cached query stats and plan cache metadata
  • server-wide performance counters

It does not grant access to user data by itself, but it can expose query text, object names, and other internal operational details.


Check Who Has VIEW SERVER STATE

The straightforward version shows explicit grants:

-- List logins explicitly granted VIEW SERVER STATE
SELECT
    p.name         AS login_name,
    sp.permission_name,
    sp.state_desc
FROM sys.server_permissions sp
JOIN sys.server_principals p
    ON sp.grantee_principal_id = p.principal_id
WHERE sp.permission_name = 'VIEW SERVER STATE'
ORDER BY p.name;

That only tells you part of the story. It shows explicit GRANTs and nothing else, so it misses sysadmin members (sysadmin implies the permission), it misses anyone who inherits it through a server role, and on SQL Server 2022+ it ignores the newer performance and security variants entirely.

This version covers all of it: every server-level “view state” style permission, whether it was granted or denied, and who inherits it through a role rather than directly.

-- Who can see server state: direct grants, role-inherited grants, and sysadmin
SET NOCOUNT ON;

-- 1. Explicit grants and denies, including the SQL 2022+ granular permissions
SELECT
    'DIRECT'                AS source,
    p.name                  AS principal_name,
    p.type_desc,
    sp.permission_name,
    sp.state_desc
FROM sys.server_permissions AS sp
JOIN sys.server_principals  AS p
    ON sp.grantee_principal_id = p.principal_id
WHERE sp.permission_name LIKE 'VIEW%SERVER%STATE'
   OR sp.permission_name IN ('VIEW ANY DEFINITION',
                             'VIEW ANY DATABASE',
                             'CONNECT ANY DATABASE')

UNION ALL

-- 2. Members of fixed/user server roles that hold one of those permissions
SELECT
    'VIA ROLE: ' + r.name   AS source,
    m.name                  AS principal_name,
    m.type_desc,
    sp.permission_name,
    sp.state_desc
FROM sys.server_permissions   AS sp
JOIN sys.server_principals    AS r  ON sp.grantee_principal_id = r.principal_id
JOIN sys.server_role_members  AS rm ON rm.role_principal_id    = r.principal_id
JOIN sys.server_principals    AS m  ON m.principal_id          = rm.member_principal_id
WHERE r.type_desc = 'SERVER_ROLE'
  AND (sp.permission_name LIKE 'VIEW%SERVER%STATE'
       OR sp.permission_name IN ('VIEW ANY DEFINITION',
                                 'VIEW ANY DATABASE',
                                 'CONNECT ANY DATABASE'))

UNION ALL

-- 3. sysadmin members: they have it implicitly and will never show above
SELECT
    'SYSADMIN (implicit)'   AS source,
    m.name                  AS principal_name,
    m.type_desc,
    'VIEW SERVER STATE'     AS permission_name,
    'GRANT'                 AS state_desc
FROM sys.server_role_members AS rm
JOIN sys.server_principals   AS r ON r.principal_id = rm.role_principal_id
JOIN sys.server_principals   AS m ON m.principal_id = rm.member_principal_id
WHERE r.name = 'sysadmin'
ORDER BY permission_name, source, principal_name;

Read the state_desc column carefully: a DENY beats a GRANT from any other source, so a login that appears twice with both is denied.

To review full instance visibility rather than just this permission, use Get Sysadmin Members, and to see everything one specific account can reach, Get User Permissions Audit resolves it in a single pass, including access inherited through nested AD groups.


Grant VIEW SERVER STATE

Run this as sysadmin (or a login that can grant server permissions):

GRANT VIEW SERVER STATE TO [DOMAIN\SomeUser];
-- or
GRANT VIEW SERVER STATE TO [SomeSqlLogin];
SSMS query window running GRANT VIEW SERVER STATE for a Login

To remove it, run the following:

REVOKE VIEW SERVER STATE FROM [DOMAIN\SomeUser];

In most environments you either grant it to the right operational accounts, or you do not. Explicit DENY is possible, but it is uncommon and usually not needed in normal DBA workflows.


The Other Permissions Monitoring Usually Needs

VIEW SERVER STATE on its own covers the server-scoped DMVs and nothing else. These are the permissions that typically have to go with it, and the symptom you see when one is missing.

PermissionScopeWhat breaks without it
VIEW SERVER STATEServerMost server DMVs return nothing or error: sessions, requests, waits, plan cache
VIEW SERVER PERFORMANCE STATE
SQL 2022+
ServerPerformance-focused DMVs stay blocked even with VIEW SERVER STATE granted
VIEW SERVER SECURITY STATE
SQL 2022+
ServerSecurity-related DMVs and audit state stay blocked
VIEW ANY DEFINITIONServerObject names, definitions and query text come back NULL or hidden
VIEW ANY DATABASEServerDatabases the login has no user in disappear from sys.databases
CONNECT ANY DATABASE
SQL 2014+
ServerPer-database checks fail on any database without an explicit user
VIEW DATABASE STATEDatabaseDatabase-scoped DMVs (index usage, file stats) return nothing for that database
SQLAgentReaderRole in msdbDatabaseAgent job history and schedules aren’t readable
IMPERSONATE on a loginServerEffective-permission auditing (the EXECUTE AS approach) can’t run

On SQL Server 2019 and earlier, VIEW SERVER STATE is usually all you need at the server level. Starting in SQL Server 2022 Microsoft split some DMV access behind VIEW SERVER PERFORMANCE STATE and VIEW SERVER SECURITY STATE, so a monitoring login can hold VIEW SERVER STATE and still hit permission errors until the relevant one is granted too:

GRANT VIEW SERVER PERFORMANCE STATE TO [DOMAIN\SomeUser];   -- SQL Server 2022+
GRANT VIEW SERVER SECURITY STATE    TO [DOMAIN\SomeUser];   -- SQL Server 2022+

If you try to grant either on a version that doesn’t support it you’ll get an “invalid permission” style error. On that instance, VIEW SERVER STATE is the correct and complete permission.


Build a Read-Only Monitoring Login

Putting it together: this creates a login that can see everything a monitoring tool or a read-only DBA needs, and change nothing. Edit the name at the top, and drop the 2022+ block if you’re on an older version.

-- Read-only monitoring login. Review before running: this grants instance-wide visibility.
USE [master];

-- 1. Server-level visibility
GRANT VIEW SERVER STATE       TO [DOMAIN\SqlMonitor];   -- server DMVs
GRANT VIEW ANY DEFINITION     TO [DOMAIN\SqlMonitor];   -- object names and query text
GRANT VIEW ANY DATABASE       TO [DOMAIN\SqlMonitor];   -- see every database listed
GRANT CONNECT ANY DATABASE    TO [DOMAIN\SqlMonitor];   -- SQL 2014+, reach databases with no user

-- 2. SQL Server 2022 and later only, remove these two lines on older versions
GRANT VIEW SERVER PERFORMANCE STATE TO [DOMAIN\SqlMonitor];
GRANT VIEW SERVER SECURITY STATE    TO [DOMAIN\SqlMonitor];

-- 3. Agent job history
USE [msdb];
CREATE USER [DOMAIN\SqlMonitor] FOR LOGIN [DOMAIN\SqlMonitor];
ALTER ROLE [SQLAgentReaderRole] ADD MEMBER [DOMAIN\SqlMonitor];

Note what is deliberately absent: no sysadmin, no db_datareader on user databases, and no write permission anywhere. This login can read operational state, not business data. If a monitoring product’s documentation asks for sysadmin, this permission set is worth proposing instead, and it covers the overwhelming majority of what such tools actually query.


Operational Notes

  • This is a server-level permission, not database-level.
  • This does not cover database-scoped DMVs. Those are controlled by VIEW DATABASE STATE at the database level.
  • For Availability Groups, permissions are per replica. If the same monitoring login connects to multiple replicas, grant it on each instance.
  • Don’t grant this to generic app logins. Keep it to DBA, support, and monitoring accounts.
  • In Azure SQL Database, many server-scoped DMVs aren’t available in the same way, so this permission isn’t a like-for-like fix there.
  • Re-check after an upgrade to SQL Server 2022 or later. An account that worked for years can start failing on performance DMVs because of the permission split, not because anything was revoked.

Frequently Asked Questions

Is VIEW SERVER STATE a security risk?

It exposes operational metadata, not table data. Someone holding it can see running queries, including the query text and object names, which can leak schema design and occasionally literal values embedded in ad-hoc SQL. That’s a real consideration for a shared or hosted instance, but it is a very long way from read access to your data. Grant it to DBA, support and monitoring accounts, not to application logins.

Why does my monitoring login still get permission errors after I granted it?

Three usual causes. On SQL Server 2022 and later, the permission was split, so performance DMVs need VIEW SERVER PERFORMANCE STATE as well. If object names or query text come back empty rather than erroring, that’s VIEW ANY DEFINITION missing. And if the problem is per-database rather than server-wide, it’s VIEW DATABASE STATE or a missing database user, not this permission at all.

Do sysadmin members need it granted?

No. Sysadmin implies it, which is exactly why the simple “who has it” query above misses them. Any audit of who can see server state has to include sysadmin membership separately, which is what the fuller query in this post does.

Can I grant it to a Windows group instead of each user?

Yes, and it’s usually the better approach. Grant to the AD group, then manage membership in AD rather than in SQL Server. Be aware this makes access harder to see: a per-login permission check won’t reveal it. Auditing effective access through nested groups is what Get User Permissions Audit is for.

What’s the least privilege for someone who only needs to see if the server is busy?

VIEW SERVER STATE alone, plus VIEW SERVER PERFORMANCE STATE on 2022+. That covers sessions, requests and waits. Add VIEW ANY DEFINITION only when they need to know which query or object is involved, since that is the part that exposes the most.


Related


Summary

If a user needs to run real DMV-based troubleshooting queries, VIEW SERVER STATE is the right permission to start from, but treat it as one of a set rather than the whole answer. Object names need VIEW ANY DEFINITION, per-database checks need CONNECT ANY DATABASE and VIEW DATABASE STATE, Agent history needs a role in msdb, and SQL Server 2022 splits performance and security DMVs behind their own permissions.

Audit who has it including sysadmin and role inheritance, grant the set intentionally, and use the read-only monitoring login above as the default answer whenever someone asks for sysadmin “just for monitoring”.

Comments

Leave a Reply

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