{"id":112534,"date":"2026-09-02T12:00:00","date_gmt":"2026-09-02T12:00:00","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=112534"},"modified":"2026-08-25T08:45:19","modified_gmt":"2026-08-25T08:45:19","slug":"sql-server-login-failures-how-to-monitor-report-and-alert-on-them-complete-guide","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-login-failures-how-to-monitor-report-and-alert-on-them-complete-guide\/","title":{"rendered":"SQL Server login failures: how to monitor, report, and alert on them (complete guide)"},"content":{"rendered":"\n<p><strong>Failed logins are one of the clearest early-warning signs of trouble on a SQL Server &#8211; whether that&#8217;s a misconfigured connection string, an expired password, or an actual unauthorized access attempt. Yet, by default, SQL Server won&#8217;t proactively <em>tell you<\/em> when they happen; you have to go looking.<\/strong><\/p>\n\n\n\n<p><strong>This guide walks through how to pull login failure data using <code>sys.xp_readerrorlog<\/code>, filter it by time and error type, parse it into readable columns, aggregate repeat offenders, and automatically email a summary report \u2014 turning a passive log file into an active security and troubleshooting tool.<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/blog\/inside-perspectives-the-growing-importance-of-security-and-compliance\/\" target=\"_blank\" rel=\"noreferrer noopener\">Data security<\/a> is an ever-present concern in the world of databases, keeping us up late at night far more often than we would like! Of the many types of <a href=\"https:\/\/www.red-gate.com\/blog\/database-monitoring-and-security-go-hand-in-hand\/\" target=\"_blank\" rel=\"noreferrer noopener\">monitoring and alerting<\/a> that can be implemented, the simplest and most effective are those around login failures.<\/p>\n\n\n\n<p>If users, apps, or services are trying to access data and are failing the login process, that is immediate evidence of a problem. It may be anything from be a legitimate security threat, to an app bug, a configuration error &#8211; or something else entirely.<\/p>\n\n\n\n<p>In this article, I&#8217;ll explain the many ways to alert and report on login failures, and how to use that information to improve security and application quality. I&#8217;ll also describe real failure scenarios, including what causes them and the implications they may have.<\/p>\n\n\n\n<div id=\"callout-block_bcbc0c3312584beb751e0656768feb05\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>You may also be interested in&#8230;<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/\" target=\"_blank\" rel=\"noreferrer noopener\">Simple Talk&#8217;s full archive of data security, privacy and compliance articles and guides<\/a><\/p>\n\n<\/div>\n<\/div> \n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-is-a-sql-server-login-failure\">What is a SQL Server login failure?<\/h2>\n\n\n\n<p>When we think of failed logins, a simple vision appears: we&#8217;re trying to log in to a server, get an error message, and then the head-scratching begins. Most of us have received messages like this before:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"877\" height=\"622\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-25.png\" alt=\"\" class=\"wp-image-112535\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-25.png 877w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-25-300x213.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-25-768x545.png 768w\" sizes=\"auto, (max-width: 877px) 100vw, 877px\" \/><\/figure>\n\n\n\n<p>In reality, failed logins can happen any time, for a wide variety of reasons. More importantly, if we\u2019re not <em>looking<\/em> for these failures, it\u2019s likely that nobody is even seeing them! <\/p>\n\n\n\n<p><strong>In SQL Server, login failures are logged by default to the SQL Server error log &#8211; where they can be reviewed, alerted on, and reported on, at any time.<\/strong><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-what-causes-a-sql-server-login-failure\">What causes a SQL Server login failure?<\/h2>\n\n\n\n<p><strong>A failed SQL Server login typically indicates a problem that is worth checking. These problems can include:<\/strong><\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>An application not logging in correctly (or failing).<br><br><\/li>\n\n\n\n<li>Someone trying to gain unauthorized <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/sql-server-access-control-basics\/\" target=\"_blank\" rel=\"noreferrer noopener\">access<\/a> to a SQL Server.<br><br><\/li>\n\n\n\n<li>A user with unresolved password\/login problems.<br><br><\/li>\n\n\n\n<li>Incorrect login setting(s), such as encryption, or using <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/choose-an-authentication-mode?view=sql-server-ver17\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Auth vs. Windows Auth<\/a>.<br><br><\/li>\n\n\n\n<li>A <a href=\"https:\/\/www.red-gate.com\/simple-talk\/development\/dotnet-development\/connection-strings-an-overview\/\" target=\"_blank\" rel=\"noreferrer noopener\">connection string<\/a> pointing to a database that no longer exists (or is inaccessible).<br><br><\/li>\n\n\n\n<li>A password was changed, but not all connection strings were updated.<br><br><\/li>\n\n\n\n<li>Domain authentication problems.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Ultimately, there are many more potential login failure reasons &#8211; these are just a few of them &#8211; but it&#8217;s already easy to see how much they vary. <\/p>\n\n\n\n<p>In terms of severity alone, it&#8217;s everything from <em>\u201can application upgrade broke a connection string\u201d<\/em>, to <em>\u201coops, I goofed entering my password!\u201d<\/em>, to <em>\u201csomeone is trying to hack an admin account!\u201d<\/em>&#8230;and everything in-between.<\/p>\n\n\n\n<p><strong>By default, SQL Server will <em>not<\/em> tell you about any of this. So, without creating a process to review the error logs, you&#8217;ll be in the dark when it comes to any SQL Server login failures &#8211; regardless of their severity.<\/strong><\/p>\n\n\n\n<p><strong>Having readily accessible information on login failures will therefore <em>not only<\/em> pre-emptively catch a wide variety of SQL Server login issues, it can also assist in diagnosing application problems to gather the details needed to solve them.  <\/strong><\/p>\n\n\n\n<p><strong>Proactive monitoring is a staple of a solid security foundation, and ensures that computing resources are not being wasted on broken or unneeded processes.<\/strong><\/p>\n\n\n\n<section id=\"my-first-block-block_75bd55dbe49fc0aefc6a78b240285487\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Future-proof database monitoring with Redgate Monitor<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Multi-platform database observability for your entire estate. Optimize performance, ensure security, and mitigate potential risks with fast deep-dive analysis, intelligent alerting, and AI-powered insights.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/products\/redgate-monitor\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Learn more &amp; try for free: Future-proof database monitoring with Redgate Monitor\">Learn more &amp; try for free<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-find-sql-server-login-failure-information\">How to find SQL Server login failure information<\/h2>\n\n\n\n<p><strong>Failed logins to SQL Server are logged in the SQL Server error log. They can be retrieved through either the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/opinion\/editorials\/sql-server-management-studio-is-as-relevant-as-ever\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server Management Studio (SSMS)<\/a> user interface (UI), <em>or<\/em> with a call to the <code>sys.xp_readerrorlog<\/code> system <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/40-problems-sql-server-stored-procedure\/\" target=\"_blank\" rel=\"noreferrer noopener\">stored procedure<\/a>. <\/strong><\/p>\n\n\n\n<p>The files themselves are stored by default within the SQL Server installation folder in a location similar to this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"939\" height=\"497\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-26.png\" alt=\"\" class=\"wp-image-112536\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-26.png 939w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-26-300x159.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-26-768x406.png 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/figure>\n\n\n\n<p><em>ERRORLOG<\/em> is the current log being populated by SQL Server as events occur. Each subsequent log is a previous archived error log that is no longer updated, but is still available for review as needed. Older error logs have higher number suffixes.<\/p>\n\n\n\n<p>The SQL Server error log can be cycled using the <code>msdb.dbo.sp_cycle_errorlog<\/code> system stored procedure. Similarly, the <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/database-administration-sql-server\/setting-up-your-sql-server-agent-correctly\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server Agent<\/a> log can be cycled with the <code>msdb.dbo.sp_cycle_agent_errorlog<\/code> stored procedure. <\/p>\n\n\n\n<p>When executed, the current log becomes the first archive log. All other logs then have their numeric suffixes incremented by one.<\/p>\n\n\n\n<p>While viewing this data via the UI is convenient, it&#8217;s more useful for a one-off &#8216;take-a-look&#8217; scenario. However, if there <em>is<\/em> any reason to store, alert on, or report on this data, <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/\" target=\"_blank\" rel=\"noreferrer noopener\">T-SQL<\/a> is the best way to do so. <\/p>\n\n\n\n<p>Therefore, this article will focus on reusable queries to get this data, rather than <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/logs\/open-log-file-viewer\" target=\"_blank\" rel=\"noreferrer noopener\">clicking around in SSMS<\/a>.<\/p>\n\n\n\n<div id=\"callout-block_bcbc0c3312584beb751e0656768feb05\" class=\"callout alignnone\">\n    <div class=\"child-last:mb-0 child-first:mt-0 bg-gray-50 dark:bg-gray-950 p-4xl my-3xl\">\n\n<p><strong>Essential reading for all things SQL Server security on Simple Talk:<\/strong><\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/collections\/fabiano-amorims-complete-guide-to-sql-server-security\/\" target=\"_blank\" rel=\"noreferrer noopener\">Fabiano Amorim\u2019s complete guide to SQL Server security<\/a> (2026)<\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/sql-server-security-primer\/\" target=\"_blank\" rel=\"noreferrer noopener\">SQL Server Security Features: Complete Guide<\/a> (2023)<\/p>\n\n\n\n<p><a href=\"https:\/\/www.red-gate.com\/simple-talk\/data-security-privacy-compliance\/data-privacy-and-protection\/introduction-to-sql-server-security-part-1\/\" target=\"_blank\" rel=\"noreferrer noopener\">Introduction to SQL Server Security<\/a> (2018-19)<\/p>\n\n<\/div>\n<\/div> \n\n\n<p>Since the error log can be quite large on a busy (or error-filled) server, filtering is important when using this stored procedure:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">EXEC sys.xp_readerrorlog\n    0, \/* 0 (Current Log) *\/\n    1, \/* 1 (SQL Server) *\/\n    N'Login', \/* Text search #1 (Login) *\/\n    N'fail'; \/* Text search #2 (fail) *\/<\/pre><\/div>\n\n\n\n<p>The results are as follows:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"941\" height=\"141\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-27.png\" alt=\"\" class=\"wp-image-112537\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-27.png 941w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-27-300x45.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-27-768x115.png 768w\" sizes=\"auto, (max-width: 941px) 100vw, 941px\" \/><\/figure>\n\n\n\n<p>This shows 5 failed logins in the current error log, each for a different reason. <\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-some-notes-on-the-parameters-used\">Some notes on the parameters used<\/h3>\n\n\n\n<p>The first parameter determines which error log to read. <code>Xp_readerrorlog<\/code> can read the current error log or older\/archived ones. For most common uses, entering 0 for this parameter will only check the current log. This is adequate.<\/p>\n\n\n\n<p>Meanwhile, if you often cycle error logs on a server, there may be value in checking additional historical logs. To do that, simply go back in values (starting at 1) and continue to work backwards as needed.<\/p>\n\n\n\n<p>To check what error logs are available and when they were initialized, run the following query:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">EXEC sys.sp_enumerrorlogs;<\/pre><\/div>\n\n\n\n<p>The results are displayed in a basic list:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"489\" height=\"273\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-28.png\" alt=\"\" class=\"wp-image-112538\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-28.png 489w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-28-300x167.png 300w\" sizes=\"auto, (max-width: 489px) 100vw, 489px\" \/><\/figure>\n\n\n\n<p>The second parameter specified which error log to view. 1 indicates the SQL Server error log and 2 indicates the SQL Server Agent error log. The third and fourth parameters are text to search for &#8211; important for limiting search results to only what we&#8217;re interested in. <\/p>\n\n\n\n<p>For this example, <em>\u201clogin\u201d<\/em> and <em>\u201cfail\u201d<\/em> were searched for &#8211; effectively filtering out anything aside from failed logins. Just bear in mind that if you customize error log messages, or regularly insert additional errors into the logs, you may need to adjust the search text to be more restrictive.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-most-efficiently-find-error-log-data\">How to most efficiently find error log data<\/h3>\n\n\n\n<p>Failed logins are quite rare on my test server (outside of fun demos), and the logs are small. A busy production server, on the other hand, may generate a <em>huge<\/em> amount of log data. To help speed up searching, you can apply a date\/time filter using two additional parameters:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @CurrentTime DATETIME2(3) = GETUTCDATE();\nDECLARE @TwelveHoursAgo DATETIME2(3) = DATEADD(HOUR, -12, @CurrentTime);\n    EXEC sys.xp_readerrorlog\n        0, \/* 0 (Current Log) *\/\n        1, \/* 1 (SQL Server) *\/\n        N'Login', \/* Text search #1 (Login) *\/\n        N'fail', \/* Text search #2 (fail) *\/\n        @TwelveHoursAgo, \/* Start Time *\/\n        @CurrentTime; \/* End Time *\/<\/pre><\/div>\n\n\n\n<p>The start and end times allow you to define a set time period to retrieve. In this example, a twelve-hour time period is checked. When frequent automated monitoring occurs on an important server, there&#8217;s no need to look back further than the last automated check.<\/p>\n\n\n\n<p>For example, if a server reports on failed logins each hour, then the <em>Start<\/em> and <em>End<\/em> times can be adjusted to reflect returning just one hour of data and nothing more. This will provide faster searches and avoid reporting on the same failed login repeatedly.<\/p>\n\n\n\n<p>There&#8217;s one final parameter available for <code>xp_readerrorlog<\/code>: the sort order. This can be set as ASC (ascend) or DESC (descend), and will sort based on the time of the log entry:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @CurrentTime DATETIME2(3) = GETUTCDATE();\nDECLARE @TwelveHoursAgo DATETIME2(3) = DATEADD(HOUR, -12, @CurrentTime);\n    EXEC sys.xp_readerrorlog\n        0, \/* 0 (Current Log) *\/\n        1, \/* 1 (SQL Server) *\/\n        N'Login', \/* Text search #1 (Login) *\/\n        N'fail', \/* Text search #2 (fail) *\/\n        @TwelveHoursAgo, \/* Start Time *\/\n        @CurrentTime, \/* End Time *\/\n        'DESC';<\/pre><\/div>\n\n\n\n<p>The results are the same as above, but are sorted by error log time <em>descending<\/em>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"814\" height=\"228\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-29.png\" alt=\"\" class=\"wp-image-112539\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-29.png 814w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-29-300x84.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-29-768x215.png 768w\" sizes=\"auto, (max-width: 814px) 100vw, 814px\" \/><\/figure>\n\n\n\n<p>Now that we can retrieve error log data reliably and with a wide variety of filters for customization, a process can be built that parses this data and stages it in a table for further processing, storage, or alerting.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-report-on-a-sql-server-login-failure\">How to report on a SQL Server login failure<\/h2>\n\n\n\n<p>The next step is to take the data from above, store it somewhere, parse out some more useful information, and then send a report to wherever you\u2019d like it to go. The following code creates a <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/theory-and-design\/temporary-tables\/\" target=\"_blank\" rel=\"noreferrer noopener\">temporary table<\/a> and then inserts the results from above into it:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">CREATE TABLE #ErrorLogInfo\n(   ErrorLogTime DATETIME2(3),\n    ProcessInfo NVARCHAR(50),\n    ErrorText NVARCHAR(MAX));\n\nDECLARE @CurrentTime DATETIME2(3) = GETUTCDATE();\nDECLARE @TwelveHoursAgo DATETIME2(3) = DATEADD(HOUR, -12, @CurrentTime);\n\nINSERT INTO #ErrorLogInfo\n    (ErrorLogTime, ProcessInfo, ErrorText)\nEXEC sys.xp_readerrorlog\n    0, \/* 0 (Current Log) *\/\n    1, \/* 1 (SQL Server) *\/\n    N'Login', \/* Text search #1 (Login) *\/\n    N'fail', \/* Text search #2 (fail) *\/\n    @TwelveHoursAgo, \/* Start Time *\/\n    @CurrentTime, \/* End Time *\/\n    'DESC';\n\nSELECT\n    *\nFROM #ErrorLogInfo;<\/pre><\/div>\n\n\n\n<p>The logic from earlier is unchanged. The only added step here is to place the data into a temp table so that further processing can take place. Running a <code>SELECT *<\/code> against the table returns the following results:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"802\" height=\"206\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-30.png\" alt=\"\" class=\"wp-image-112540\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-30.png 802w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-30-300x77.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-30-768x197.png 768w\" sizes=\"auto, (max-width: 802px) 100vw, 802px\" \/><\/figure>\n\n\n\n<p>While useful, the <code>ErrorText<\/code> column is a big blob of error information. What I would prefer is to parse out the login name, IP address, and error summary into their own columns. This would be hugely convenient for any person or agent consuming this data. <\/p>\n\n\n\n<p>The following T-SQL adds those columns to the temp table and populates them with a variety of string manipulation:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">UPDATE ErrorLogInfo\n    SET LoginName = ISNULL(CASE\n                WHEN CHARINDEX('''', ErrorText) IS NOT NULL AND CHARINDEX('''', ErrorText) &lt;&gt; 0\n                    THEN SUBSTRING(ErrorText, CHARINDEX('''', ErrorText) + 1,\n                        CHARINDEX('''', ErrorText, CHARINDEX('''', ErrorText) + 1) -\n                        CHARINDEX('''', ErrorText) - 1)\n                ELSE NULL\n            END, ''),\n        IPAddress = ISNULL(REPLACE(CASE\n                WHEN PATINDEX('%CLIENT:%', ErrorText) IS NOT NULL AND PATINDEX('%CLIENT: %', ErrorText) &lt;&gt; 0\n                    THEN SUBSTRING(ErrorText, PATINDEX('%CLIENT:%', ErrorText) + 8,\n                        LEN(ErrorText) - (PATINDEX('%CLIENT:%', ErrorText) + 8))\n                ELSE NULL\n            END, ']', ''), ''),\n        ErrorSummary = ISNULL(CASE\n                WHEN ErrorText LIKE '%Failed to open the explicitly specified database%'\n                    THEN 'Unable to open the database specified in the connection string'\n                WHEN ErrorText LIKE '%Login lacks Connect SQL permission%'\n                    THEN 'This login does not have connection permissions to this SQL Server'\n                WHEN ErrorText LIKE '%The account is disabled%'\n                    THEN 'This login is disabled'\n                WHEN ErrorText LIKE '%Could not find a login matching the name provided%'\n                    THEN 'The login provided does not exist on this SQL Server'\n                WHEN ErrorText LIKE '%The login is from an untrusted domain and cannot be used with Integrated authentication%'\n                    THEN 'Windows authentication was used on a SQL Server that is not configured for use by that domain'\n                WHEN ErrorText LIKE '%Password did not match that for the login provided%'\n                    THEN 'Incorrect password entered for this login'\n                ELSE NULL\n            END, '')\nFROM #ErrorLogInfo ErrorLogInfo;<\/pre><\/div>\n\n\n\n<p>While this code doesn&#8217;t account for <em>every possible login failure message<\/em>, the most common ones are there, and it&#8217;s easy to add more &#8211; simply add more <code>WHEN<\/code> clauses to the <code>CASE<\/code> statement. <\/p>\n\n\n\n<p>Selecting <code>*<\/code> from the temp table now shows some nicely normalized columns that we can use:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"939\" height=\"134\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-31.png\" alt=\"\" class=\"wp-image-112541\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-31.png 939w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-31-300x43.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-31-768x110.png 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/figure>\n\n\n\n<p><em>As an aside: yes, you can easily spam the error logs by logging in with SQL authentication using a made-up login name. I have been known to periodically prank database administrators with funny login names to see how long before they notice and exact their revenge against me \ud83d\ude0a<\/em><\/p>\n\n\n\n<p>Separating out those columns allows for easier filtering, if needed, as well as cleaner reporting. More importantly, if login failures are common, this data can be aggregated so that subsequent communications are not overly large.<\/p>\n\n\n\n<section id=\"my-first-block-block_bb4f0bbc0b73140c0df94799e327b28f\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Subscribe to the Simple Talk newsletter<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Get selected articles, event information, podcasts and other industry content delivered straight to your inbox.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/simple-talk\/subscribe\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Subscribe now: Subscribe to the Simple Talk newsletter\">Subscribe now<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-find-more-detailed-error-information\">How to find more detailed error information<\/h3>\n\n\n\n<p>If the detailed error information is what you want, then reporting can continue using the collected data above. However, if you&#8217;re on a server with a very active error log, the table above might be quite populous; if so, aggregating the data is a good next step to reduce its size while retaining the most important information.<\/p>\n\n\n\n<p>The following query takes the detail data and places the aggregated information into a new temporary table:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @MinimumFailureCountToAlertOn INT = 2;\nSELECT\n    COUNT(*) AS NumberOfAttempts,\n    ErrorText AS ErrorText,\n    LoginName,\n    IPAddress,\n    ErrorSummary,\n    MIN(ErrorLogTime) AS MinLogDate,\n    MAX(ErrorLogTime) AS MaxLogDate\nINTO #SummarizedErrorInfo\nFROM #ErrorLogInfo\nWHERE ProcessInfo = 'Logon'\nGROUP BY ErrorText, LoginName, IPAddress, ErrorSummary\nHAVING COUNT(*) &gt;= @MinimumFailureCountToAlertOn\nORDER BY NumberOfAttempts DESC;<\/pre><\/div>\n\n\n\n<p>The variable <code>@MinimumFailureCountToAlertOn<\/code> is a way to tune if one-off login failures should be reported on or not. For this example, setting it to 2 filters out failures that occur only once. These are often manual login failures or one-offs that may not be of interest to you, but if you <em>do<\/em> want all detail &#8211; regardless of failed login count &#8211; set the variable equal to 1, or remove it altogether. <\/p>\n\n\n\n<p>After failing some more logins for fun (my idea of <em>\u201cfun\u201d<\/em> may not be the same as yours!), the updated results look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"939\" height=\"119\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-32.png\" alt=\"\" class=\"wp-image-112542\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-32.png 939w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-32-300x38.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-32-768x97.png 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/figure>\n\n\n\n<p>The results provide a clear idea of what&#8217;s happening most often and puts them top of the list. The aggregated details let us know the first and last failed login time, as well as the other details from earlier.<\/p>\n\n\n\n<p>The error summary, meanwhile, allows for a speedy review of the error types that occur most often. For example, if an application is trying to connect to 100 databases that were recently migrated to another server, it would be very useful to see 100 of the same error type on the same server!<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-create-a-report-or-generate-data-for-further-error-analysis\">How to create a report (or generate data) for further error analysis<\/h3>\n\n\n\n<p>With this information in hand, the last step is to generate some sort of report, alert, or data. This can then be analyzed or sent to the appropriate operations personnel for further action. <\/p>\n\n\n\n<p>To do this, there are many options available, including:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Send a summary email to operators interested in failed logins.<br><br><\/li>\n\n\n\n<li>Store the data in a permanent table for future analysis\/reporting.<br><br><\/li>\n\n\n\n<li>Push the data to an application for further processing <a href=\"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/bi-sql-server\/power-bi-part-1-introduction\/\" target=\"_blank\" rel=\"noreferrer noopener\">(PowerBI<\/a>, Microsoft Teams, etc&#8230;)<br><br><\/li>\n\n\n\n<li>If you maintain multiple SQL Servers, centralize the data to a single reporting\/operations server <em>before<\/em> analyzing it further.<\/li>\n<\/ul>\n<\/div>\n\n\n<p>The key is to do <em>something<\/em>. Collecting failures like this and never doing anything with them is a waste of resources. It also leaves you vulnerable to the impact of these problems.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"h-how-to-use-database-mail-in-sql-server-to-send-the-summary-information-to-specific-people\">How to use Database Mail in SQL Server to send the summary information to specific people<\/h3>\n\n\n\n<p>If <a href=\"https:\/\/www.sqlservercentral.com\/articles\/database-mail-in-sql-server-2022\" target=\"_blank\" rel=\"noreferrer noopener\">Database Mail<\/a> is enabled, the simplest way to proceed is to email the summary information to a target email address. Many applications &#8211; including Teams and a variety of on-call tools &#8211; accept emails as triggers for the creation of messages\/tickets\/alerts, so this is a solid place to start. <\/p>\n\n\n\n<p>No Database Mail? Use whatever your most common communication method is instead. The main priority is to push the information to the most useful target possible.<\/p>\n\n\n\n<p>The following script takes the contents of the summarized table generated above, composes a simple email with them, and sends it out:<\/p>\n\n\n\n<div class=\"wp-block-urvanov-syntax-highlighter-code-block\"><pre class=\"lang:tsql decode:true \">DECLARE @ServerName VARCHAR(100) = CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS VARCHAR(100));\nDECLARE @InstanceName VARCHAR(100) = @@SERVERNAME;\nDECLARE @ProfileName VARCHAR(MAX) = 'Default';\nDECLARE @EmailAddressList VARCHAR(MAX) = 'TestEmail@MyDomain.com';\nDECLARE @EmailSubject VARCHAR(MAX) = 'Multiple failed logins reported on ' + ISNULL(@ServerName, @InstanceName);\nDECLARE @EmailBody VARCHAR(MAX) = '&lt;html&gt;&lt;body&gt;The following failed logins were identified:&lt;BR&gt;\nDatabase Server: ' + ISNULL(@InstanceName, @ServerName) + '&lt;P&gt;';\n\nSELECT @EmailBody = @EmailBody +\n'   Error Text: ' + ErrorText + '&lt;BR&gt;\nFirst Failed Login Time : ' + CAST(MinLogDate AS VARCHAR(MAX)) + '&lt;BR&gt;\nMost Recent Failed Login Time: ' + CAST(MaxLogDate AS VARCHAR(MAX)) + '&lt;BR&gt;\nFailed Login Attempt Count: ' + CAST(NumberOfAttempts AS VARCHAR(MAX)) + '&lt;BR&gt;\nLogin Name: ' + LoginName + '&lt;BR&gt;\nIP Address: ' + IPAddress + '&lt;BR&gt;\nError Summary: ' + ErrorSummary + '&lt;P&gt;'\nFROM #SummarizedErrorInfo SummarizedErrorInfo\nORDER BY SummarizedErrorInfo.MinLogDate ASC;\n\nSELECT @EmailBody = @EmailBody + '&lt;\/body&gt;&lt;\/html&gt;';\n\nEXEC msdb.dbo.sp_send_dbmail\n\t@profile_name = @ProfileName,\n\t@recipients = @EmailAddressList,\n\t@subject = @EmailSubject,\n\t@body_format = 'html',\n\t@body = @EmailBody;<\/pre><\/div>\n\n\n\n<p>And here&#8217;s the resulting email. It&#8217;s a starting point, albeit quite a basic one:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"939\" height=\"811\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-33.png\" alt=\"\" class=\"wp-image-112543\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-33.png 939w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-33-300x259.png 300w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2026\/08\/image-33-768x663.png 768w\" sizes=\"auto, (max-width: 939px) 100vw, 939px\" \/><\/figure>\n\n\n\n<p>You can easily adjust the email contents to display more or less details. You can also &#8216;fancy-up&#8217; the HTML to make the email a bit easier to read.<\/p>\n\n\n\n<p>And if tens or hundreds of errors are sent out at once, you&#8217;ll likely want to aggregate further, or send multiple reports out. One with the aggregated high-level overview, perhaps, and another with more detail. Often times though, you don&#8217;t need hundreds of examples to locate and resolve the root cause of a systemic problem.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-make-the-summary-data-presentable-easy-to-read\">How to make the summary data presentable (easy to read)<\/h2>\n\n\n\n<p>Every database environment is different. Some may see a single failed login every month. Others may get hundreds a day. Ultimately, the goal is the same: minimize these occurrences as much as possible.<\/p>\n\n\n\n<p>Your database infrastructure and frequency of login failures will determine how to proceed with this data, and how large a summary is needed. As always, though, any information that is sent for alerting or reporting purposes must be actionable and useful. If the data is too large or verbose, it&#8217;ll get partially or entirely ignored.<\/p>\n\n\n\n<p>Before the information is sent, then, it&#8217;s important to:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Remove unneeded columns.<br><br><\/li>\n\n\n\n<li>Summarize\/aggregate sufficiently so that data is easy to understand and act on.<br><br><\/li>\n\n\n\n<li>Adjust the format of the data\/email\/report to be more visually useful to you.<br><br><\/li>\n\n\n\n<li>If the data is large, an agent can analyze it for you, providing actionable results.<\/li>\n<\/ul>\n<\/div>\n\n\n<h2 class=\"wp-block-heading\" id=\"h-how-to-prevent-failed-sql-server-logins\">How to prevent failed SQL Server logins<\/h2>\n\n\n\n<p>Also worth considering are strategies to address failed logins <em>before<\/em> they happen. Frequent errors, or repeats of the <em>same<\/em> error, cannot simply be resolved one-at-a-time. They&#8217;ll just keep happening if you don&#8217;t address the root cause.<\/p>\n\n\n\n<p>So, consider bigger solutions such as:<\/p>\n\n\n<div class=\"block-core-list\">\n<ul class=\"wp-block-list\">\n<li>Network rules to restrict unauthorized traffic to database servers.<br><br><\/li>\n\n\n\n<li>Set up automatic alerts to internal employees to let them know they have a login problem.<br><br><\/li>\n\n\n\n<li>Automatically block IP addresses once a systemic problem is identified.<br><br><\/li>\n\n\n\n<li>Disallow SQL authentication on database servers that do not use it.<br><br><\/li>\n\n\n\n<li>Audit server logins to assist in correlating failed logins to successful logins.<br><br><\/li>\n\n\n\n<li>Automatically disable a login if there are too many failed logins for it.<br><br><\/li>\n\n\n\n<li>Ensure strong passwords are enforced for all SQL Auth logins. This ensures that brute-force attacks are unlikely to succeed.<br><br><\/li>\n\n\n\n<li>Set up automatic notifications for the developers, operators, or administrators who regularly solve issues like these. Hitting someone with logged alerts when things break will nudge them to <em>permanently<\/em> solve an issue &#8211; reducing both interruption <em>and<\/em> embarrassment!<\/li>\n<\/ul>\n<\/div>\n\n\n<p>Depending on your SQL Server(s), there could be a wide variety of other appropriate responses as well.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"h-in-summary-see-the-big-picture-to-prevent-future-issues\">In summary: see the big picture to prevent future issues<\/h2>\n\n\n\n<p><strong>Failed SQL Server logins represent a wide variety of bad scenarios. Some may be innocent and benign, whereas others could represent critical infrastructure flaws. Having a simple process to effectively communicate these failures is key to resolving them in a timely manner.<\/strong><\/p>\n\n\n\n<p><strong>Seeing the big picture and solving these issues efficiently will improve your application development, software release processes, user data access<\/strong>, <strong>and your SQL Server&#8217;s security as a whole.<\/strong><\/p>\n\n\n\n<p><em>How do you manage failed logins? If you have other processes, challenges, or solutions, feel free to share down in the comments below!<\/em><\/p>\n\n\n\n<section id=\"my-first-block-block_8800db0c054f61cea5b433f81ca30e01\" class=\"my-first-block alignwide\">\n    <div class=\"bg-brand-600 text-base-white py-5xl px-4xl rounded-sm bg-gradient-to-r from-brand-600 to-brand-500 red\">\n        <div class=\"gap-4xl items-start md:items-center flex flex-col md:flex-row justify-between\">\n            <div class=\"flex-1 col-span-10 lg:col-span-7\">\n                <h3 class=\"mt-0 font-display mb-2 text-display-sm\">Simple Talk is brought to you by Redgate Software<\/h3>\n                <div class=\"child:last-of-type:mb-0\">\n                                            Take control of your databases with the trusted Database DevOps solutions provider. Automate with confidence, scale securely, and unlock growth through AI.                                    <\/div>\n            <\/div>\n                                            <a href=\"https:\/\/www.red-gate.com\/solutions\/overview\/\" class=\"btn btn--secondary btn--lg\" aria-label=\"Discover how Redgate can help you: Simple Talk is brought to you by Redgate Software\">Discover how Redgate can help you<\/a>\n                    <\/div>\n    <\/div>\n<\/section>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What causes a SQL Server login failure?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Login failures can stem from application bugs, incorrect passwords, disabled accounts, outdated connection strings, mismatched authentication modes (SQL vs. Windows), domain authentication issues, or unauthorized access attempts.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. Where does SQL Server store login failure information?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">Login failures are recorded in the SQL Server error log by default. They can be viewed in SQL Server Management Studio (SSMS) or retrieved programmatically with the <code>sys.xp_readerrorlog<\/code> system stored procedure.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. How can I filter login failures by date and time?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\"><code>xp_readerrorlog<\/code> accepts optional start and end time parameters, letting you retrieve only the failures within a specific window \u2014 useful for scheduled monitoring jobs that check, for example, the last hour of activity.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I get alerted automatically about failed logins?<\/h3>\n            <div class=\"faq-answer\">\n                <p dir=\"ltr\">You can insert the results of <code>xp_readerrorlog<\/code> into a table, aggregate them by login name, IP address, and error type, then use <code>msdb.dbo.sp_send_dbmail<\/code> to email a summary report to your operations team or on-call tool.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">5. Should every failed login be reported?<\/h3>\n            <div class=\"faq-answer\">\n                <div>\n<div role=\"article\" aria-label=\"Message 2 of 4\">\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<div>\n<p dir=\"ltr\">Not necessarily. Using a minimum failure count threshold (e.g., only reporting logins that failed 2+ times) helps filter out one-off mistakes and keeps alerts focused on recurring or systemic issues worth investigating.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to detect and report SQL Server login failures using T-SQL and xp_readerrorlog. Includes ready-to-use scripts for filtering, aggregating, and emailing alerts.&hellip;<\/p>\n","protected":false},"author":329827,"featured_media":106674,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143523,53,143524,143531],"tags":[4168,4170,4150,4151,4252],"coauthors":[101655],"class_list":["post-112534","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-featured","category-sql-server","category-t-sql-programming-sql-server","tag-database","tag-database-administration","tag-sql","tag-sql-server","tag-t-sql-programming"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112534","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/329827"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=112534"}],"version-history":[{"count":7,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112534\/revisions"}],"predecessor-version":[{"id":112589,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/112534\/revisions\/112589"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/106674"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=112534"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=112534"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=112534"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=112534"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}