Skip to main content

Monitoring and Alerting

Monitor DuoKey SQL EKM encryption status and configure alerts

Applies to:
SQL Server 2016+OperationsSQL Server Agent

Overview

Effective monitoring ensures your encrypted databases remain healthy and compliant. This guide covers real-time status monitoring, automated health checks, SQL Server Agent alerts, and audit logging.

TDE Encryption Dashboard● All Systems Healthy
Encrypted Databases
12
Pending Encryption
0
Rotation Overdue
2
Provider Status
OK

Encryption Status Monitoring

Create Status View

Create a view for easy monitoring of all encrypted databases:

TDE Status ViewSQL
CREATE OR ALTER VIEW dbo.vw_TDE_Status
AS
SELECT
    DB_NAME(dek.database_id) AS DatabaseName,
    d.state_desc AS DatabaseState,
    CASE dek.encryption_state
        WHEN 0 THEN 'No DEK Present'
        WHEN 1 THEN 'Unencrypted'
        WHEN 2 THEN 'Encryption In Progress'
        WHEN 3 THEN 'Encrypted'
        WHEN 4 THEN 'Key Change In Progress'
        WHEN 5 THEN 'Decryption In Progress'
        WHEN 6 THEN 'Protection Change In Progress'
    END AS EncryptionStatus,
    dek.percent_complete AS ProgressPercent,
    dek.key_algorithm AS Algorithm,
    dek.key_length AS KeyLength,
    ak.name AS ProtectingKeyName,
    cp.name AS EKMProvider,
    dek.create_date AS DEKCreated,
    dek.regenerate_date AS LastKeyRotation,
    DATEDIFF(DAY, ISNULL(dek.regenerate_date, dek.create_date), GETDATE())
        AS DaysSinceRotation
FROM sys.dm_database_encryption_keys dek
INNER JOIN sys.databases d ON dek.database_id = d.database_id
LEFT JOIN sys.asymmetric_keys ak ON dek.encryptor_thumbprint = ak.thumbprint
LEFT JOIN sys.cryptographic_providers cp ON ak.provider_id = cp.provider_id;
GO
Tip
Query this view regularly with: SELECT * FROM dbo.vw_TDE_Status

Encryption State Reference

Understanding encryption states is critical for monitoring:

StateValueDescriptionAction Required
0No DEKNo database encryption key presentCreate DEK with CREATE DATABASE ENCRYPTION KEY
1UnencryptedDEK exists but encryption not enabledEnable with ALTER DATABASE SET ENCRYPTION ON
2EncryptingEncryption in progressWait for completion, monitor percent_complete
3EncryptedDatabase fully encryptedNone - normal operation
4Key ChangeKey rotation in progressWait for completion
5DecryptingDecryption in progressWait for completion
6Protection ChangeProtector change in progressWait for completion

Quick Status Queries

Quick Status CheckSQL
-- All encrypted databases with status
SELECT
    DB_NAME(database_id) AS [Database],
    encryption_state,
    percent_complete
FROM sys.dm_database_encryption_keys;
GO

-- Check EKM provider is healthy
SELECT name, is_enabled, version
FROM sys.cryptographic_providers
WHERE name = 'DuoKeyProvider';
GO

-- Databases needing attention
SELECT
    DB_NAME(database_id) AS [Database],
    'Key rotation overdue' AS Issue
FROM sys.dm_database_encryption_keys
WHERE DATEDIFF(DAY, ISNULL(regenerate_date, create_date), GETDATE()) > 365;
GO

Health Check Stored Procedure

This comprehensive stored procedure checks for common issues and can optionally send email alerts:

Health Check ProcedureSQL
CREATE OR ALTER PROCEDURE dbo.usp_TDE_HealthCheck
    @AlertThresholdDays INT = 365,
    @SendEmail BIT = 0,
    @EmailRecipients NVARCHAR(500) = NULL
AS
BEGIN
    SET NOCOUNT ON;

    DECLARE @Issues TABLE (
        Severity VARCHAR(20),
        Category VARCHAR(50),
        DatabaseName NVARCHAR(128),
        Issue NVARCHAR(500),
        Recommendation NVARCHAR(500)
    );

    -- Check for databases that should be encrypted but aren't
    INSERT INTO @Issues
    SELECT
        'WARNING',
        'Encryption Gap',
        name,
        'Database is not encrypted',
        'Consider enabling TDE for this database'
    FROM sys.databases
    WHERE database_id > 4  -- Exclude system databases
    AND is_encrypted = 0
    AND name NOT IN ('tempdb', 'ReportServer', 'ReportServerTempDB');

    -- Check for key rotation overdue
    INSERT INTO @Issues
    SELECT
        'WARNING',
        'Key Rotation',
        DB_NAME(database_id),
        'Key rotation overdue (' +
            CAST(DATEDIFF(DAY, ISNULL(regenerate_date, create_date), GETDATE()) AS VARCHAR) +
            ' days since last rotation)',
        'Rotate DEK per security policy'
    FROM sys.dm_database_encryption_keys
    WHERE DATEDIFF(DAY, ISNULL(regenerate_date, create_date), GETDATE()) > @AlertThresholdDays;

    -- Check for stuck encryption operations
    INSERT INTO @Issues
    SELECT
        'CRITICAL',
        'Encryption Stuck',
        DB_NAME(database_id),
        'Encryption operation stuck at ' + CAST(percent_complete AS VARCHAR) + '%',
        'Investigate I/O performance and DuoKey connectivity'
    FROM sys.dm_database_encryption_keys
    WHERE encryption_state IN (2, 4, 5, 6)
    AND percent_complete < 100
    AND DATEDIFF(HOUR, modify_date, GETDATE()) > 4;

    -- Check EKM provider status
    IF NOT EXISTS (
        SELECT 1 FROM sys.cryptographic_providers
        WHERE name = 'DuoKeyProvider' AND is_enabled = 1
    )
    BEGIN
        INSERT INTO @Issues VALUES (
            'CRITICAL',
            'EKM Provider',
            'N/A',
            'DuoKey EKM Provider is not enabled or missing',
            'Verify provider installation and enable if disabled'
        );
    END

    -- Return results
    SELECT * FROM @Issues
    ORDER BY
        CASE Severity WHEN 'CRITICAL' THEN 1 WHEN 'WARNING' THEN 2 ELSE 3 END,
        Category, DatabaseName;

    -- Optionally send email (requires Database Mail configured)
    IF @SendEmail = 1 AND EXISTS (SELECT 1 FROM @Issues)
    BEGIN
        DECLARE @body NVARCHAR(MAX);
        SELECT @body = STRING_AGG(
            Severity + ': ' + Category + ' - ' + DatabaseName + ': ' + Issue,
            CHAR(13) + CHAR(10)
        ) FROM @Issues;

        EXEC msdb.dbo.sp_send_dbmail
            @recipients = @EmailRecipients,
            @subject = 'SQL EKM Health Check Alert',
            @body = @body;
    END
END
GO

Usage Examples

Execute Health CheckSQL
-- Basic health check (returns issues only)
EXEC dbo.usp_TDE_HealthCheck;
GO

-- With custom threshold (90 days for key rotation)
EXEC dbo.usp_TDE_HealthCheck @AlertThresholdDays = 90;
GO

-- With email alerts (requires Database Mail)
EXEC dbo.usp_TDE_HealthCheck
    @AlertThresholdDays = 365,
    @SendEmail = 1,
    @EmailRecipients = '[email protected],[email protected]';
GO
Note
The health check procedure requires Database Mail to be configured for email alerts. See Microsoft Database Mail documentation.

SQL Server Agent Alerts

Create an automated SQL Server Agent job to run the health check on a schedule:

Create Agent JobSQL
-- Create health check job
USE msdb;
GO

EXEC dbo.sp_add_job
    @job_name = N'TDE_HealthCheck_Hourly',
    @enabled = 1,
    @description = N'Hourly TDE encryption health check';
GO

EXEC dbo.sp_add_jobstep
    @job_name = N'TDE_HealthCheck_Hourly',
    @step_name = N'RunHealthCheck',
    @subsystem = N'TSQL',
    @command = N'EXEC dbo.usp_TDE_HealthCheck
        @AlertThresholdDays = 365,
        @SendEmail = 1,
        @EmailRecipients = ''[email protected]''',
    @database_name = N'master';
GO

EXEC dbo.sp_add_schedule
    @schedule_name = N'Hourly',
    @freq_type = 4,
    @freq_interval = 1,
    @freq_subday_type = 8,
    @freq_subday_interval = 1;
GO

EXEC dbo.sp_attach_schedule
    @job_name = N'TDE_HealthCheck_Hourly',
    @schedule_name = N'Hourly';
GO

EXEC dbo.sp_add_jobserver
    @job_name = N'TDE_HealthCheck_Hourly';
GO

PRINT 'Health check job created successfully.';
GO

Schedule Options

Schedulefreq_typefreq_subday_typefreq_subday_interval
Every hour4 (Daily)8 (Hour)1
Every 15 minutes4 (Daily)4 (Minute)15
Every 6 hours4 (Daily)8 (Hour)6
Once daily at 6 AM4 (Daily)1 (Once)N/A
Important
Ensure SQL Server Agent is running and the service account has permissions to send email if using Database Mail.

Performance Monitoring

Monitor I/O performance to ensure TDE isn't causing excessive overhead:

Performance Baseline QuerySQL
-- TDE Performance Baseline Query
SELECT
    DB_NAME(fs.database_id) AS DatabaseName,
    mf.name AS FileName,
    mf.type_desc AS FileType,
    fs.num_of_reads AS Reads,
    fs.num_of_writes AS Writes,
    fs.io_stall_read_ms AS ReadStallMs,
    fs.io_stall_write_ms AS WriteStallMs,
    CAST(fs.size_on_disk_bytes / 1024.0 / 1024.0 AS DECIMAL(10,2)) AS SizeMB,
    d.is_encrypted AS Encrypted,
    CASE
        WHEN fs.num_of_reads > 0
        THEN CAST(fs.io_stall_read_ms / fs.num_of_reads AS DECIMAL(10,2))
        ELSE 0
    END AS AvgReadLatencyMs,
    CASE
        WHEN fs.num_of_writes > 0
        THEN CAST(fs.io_stall_write_ms / fs.num_of_writes AS DECIMAL(10,2))
        ELSE 0
    END AS AvgWriteLatencyMs
FROM sys.dm_io_virtual_file_stats(NULL, NULL) fs
INNER JOIN sys.master_files mf
    ON fs.database_id = mf.database_id AND fs.file_id = mf.file_id
INNER JOIN sys.databases d
    ON fs.database_id = d.database_id
WHERE d.is_encrypted = 1
ORDER BY fs.io_stall_read_ms + fs.io_stall_write_ms DESC;
GO

Key Metrics to Watch

MetricHealthy RangeAction if Exceeded
AvgReadLatencyMs< 20msCheck storage I/O, consider faster storage
AvgWriteLatencyMs< 20msCheck storage I/O, consider log optimization
CPU during encryption< 80%Schedule encryption during off-hours
TDE scan progressIncreasingCheck for locks, I/O bottlenecks
Tip
TDE typically adds 3-5% overhead for reads and 5-10% for writes. Modern CPUs with AES-NI instructions minimize this impact significantly.

Audit Configuration

Enable SQL Server Audit to track key operations for compliance:

Audit ConfigurationSQL
-- Create server audit
CREATE SERVER AUDIT TDE_Audit
TO FILE (FILEPATH = 'C:\SQLAudit\', MAXSIZE = 100 MB)
WITH (QUEUE_DELAY = 1000, ON_FAILURE = CONTINUE);
GO

ALTER SERVER AUDIT TDE_Audit WITH (STATE = ON);
GO

-- Create audit specification for crypto operations
CREATE SERVER AUDIT SPECIFICATION TDE_AuditSpec
FOR SERVER AUDIT TDE_Audit
ADD (DATABASE_CHANGE_GROUP),
ADD (SCHEMA_OBJECT_ACCESS_GROUP),
ADD (BACKUP_RESTORE_GROUP)
WITH (STATE = ON);
GO

PRINT 'Audit configuration created successfully.';
GO

Events Captured

Event GroupWhat It Captures
DATABASE_CHANGE_GROUPCREATE/ALTER/DROP DATABASE operations
SCHEMA_OBJECT_ACCESS_GROUPAccess to encryption keys and certificates
BACKUP_RESTORE_GROUPBackup and restore operations
Caution
Ensure the audit file path exists and SQL Server service account has write permissions. Audit files can grow large - implement rotation.