RDP «Произошла внутренняя ошибка»: диагностика и исправление за 15 минут

Утро понедельника, 8 пользователей не могут подключиться к терминальному серверу. Клиент RDP выдаёт лаконичное «Произошла внутренняя ошибка» — и ни слова о том, что именно пошло не так. Знакомая ситуация? В этой статье разберём реальный кейс диагностики и ремонта RDS-сервера на Windows Server 2019, который мы решили удалённо через WinRM за 15 минут — без перезагрузки и без потери активных сессий.

Важно: ошибка «Произошла внутренняя ошибка» — это зонтичная ошибка клиента RDP. За ней могут скрываться минимум 5 разных проблем на стороне сервера. Не угадывайте — диагностируйте.

Симптомы и исходная ситуация

Терминальный сервер RD-03 (Windows Server 2019, роль RDS-RD-Server) обслуживает около 25 пользователей. Подключение снаружи идёт через NAT с пробросом порта. В какой-то момент новые RDP-подключения стали падать с одной и той же ошибкой.

Что мы наблюдали:

  • Клиент RDP (mstsc.exe) выдаёт «Произошла внутренняя ошибка» сразу после ввода учётных данных
  • Некоторые пользователи, подключившиеся ранее, продолжали работать — их сессии были активны
  • Сервер отвечал по WinRM (порт 5985/5986) — значит, сам сервер жив
  • Порт 3389 слушал (LISTENING), TCP-подключение устанавливалось

Диагностика: три корневые причины

Подключившись к серверу через WinRM (pywinrm + NTLM-аутентификация), мы начали собирать информацию. Вот что обнаружилось.

Причина 1: повреждённый SSL-сертификат (EventID 1057)

В журнале событий System обнаружились повторяющиеся записи:

EventID: 1057
Source: TerminalServices-RemoteConnectionManager

Серверу узла сеансов удалённых рабочих столов не удалось создать
новый самозаверяющий сертификат для использования при проверке
подлинности при подключениях SSL.
Код состояния: Указан неправильный алгоритм.

При SecurityLayer=2 (SSL/TLS) сервер обязан иметь рабочий SSL-сертификат. Служба TermService пыталась сгенерировать самоподписанный сертификат при каждом запуске — и каждый раз падала с ошибкой NTE_BAD_ALGID (0x80090008).

Причина 2: пустая папка MachineKeys

Копнув глубже, мы нашли первопричину проблемы с сертификатами:

C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys — 0 файлов!

Это папка, где Windows хранит закрытые ключи машинных сертификатов. Без неё ни один сертификат не может быть создан. Но мало того — у этой папки отсутствовали права для SYSTEM и NETWORK SERVICE:

# Было (сломано):
MachineKeys   Все:(R,W)
              BUILTIN\Администраторы:(F)

# Должно быть:
MachineKeys   Все:(R,W)
              BUILTIN\Администраторы:(F)
              NT AUTHORITY\СИСТЕМА:(OI)(CI)(F)          <-- отсутствовало!
              NT AUTHORITY\NETWORK SERVICE:(OI)(CI)(R,W) <-- отсутствовало!

Служба TermService работает под NETWORK SERVICE. Без доступа к MachineKeys она физически не могла ни создать, ни использовать закрытый ключ сертификата.

Дополнительно в журнале Schannel мы видели подтверждение:

EventID: 36870
Source: Schannel

Произошла неустранимая ошибка при попытке обращения к закрытому ключу
учётных данных TLS server.
Код ошибки: 0x8009030D (NTE_INTERNAL_ERROR)

Причина 3: истекший Grace Period лицензирования RDS

На сервере была установлена роль RDS-Licensing, но Grace Period (пробный период 120 дней) истёк:

Grace days remaining: 0

При этом лицензии были — более 9 900 доступных CAL в пуле. Проблема была в том, что ключ реестра GracePeriod содержал истёкшую метку, и служба отказывалась принимать новые подключения.

Ключ реестра:

HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\GracePeriod
  L$RTMTIMEBOMB_1320153D-8DA3-4e8e-B27B-0D888223A588  REG_BINARY  ...
Совет: даже если у вас настроен лицензионный сервер с достаточным количеством CAL, наличие истёкшего GracePeriod может блокировать подключения. Этот ключ имеет приоритет.

Исправление: пошаговый процесс

Шаг 1 — Восстановление прав MachineKeys

Без этого шага ни один последующий не сработает — сертификаты не могут хранить ключи.

icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" `
    /grant "SYSTEM:(OI)(CI)(F)" `
    /grant "NETWORK SERVICE:(OI)(CI)(R,W)"

После выполнения в папке появились 428 файлов ключей — они сгенерировались из очереди запросов, ожидавших доступа.

Шаг 2 — Удаление истекшего Grace Period

Этот ключ реестра защищён специальными ACL — даже локальный администратор не может его удалить напрямую. Решение — запланированная задача от имени NT AUTHORITY\SYSTEM:

# PowerShell — создаём и выполняем задачу от SYSTEM
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
    -Argument "-Command Remove-Item -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\GracePeriod' -Force -Recurse"

$principal = New-ScheduledTaskPrincipal `
    -UserId "NT AUTHORITY\SYSTEM" `
    -LogonType ServiceAccount -RunLevel Highest

Register-ScheduledTask -TaskName "DeleteGracePeriod" `
    -Action $action -Principal $principal -Force

Start-ScheduledTask -TaskName "DeleteGracePeriod"
Start-Sleep 5

# Проверяем
Test-Path "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\GracePeriod"
# False — удалено!

Unregister-ScheduledTask -TaskName "DeleteGracePeriod" -Confirm:$false
Шаг 3 — Создание нового SSL-сертификата

Важный нюанс: нельзя указывать -Provider "Microsoft RSA SChannel Cryptographic Provider" — именно это вызывало ошибку NTE_BAD_ALGID. Нужно позволить системе выбрать провайдер автоматически:

# Правильно — без явного Provider:
$cert = New-SelfSignedCertificate `
    -DnsName "RD-03.MSSLEEP.local" `
    -CertStoreLocation "Cert:\LocalMachine\My" `
    -NotAfter (Get-Date).AddYears(10) `
    -KeyLength 2048 `
    -KeyExportPolicy Exportable `
    -KeyUsage DigitalSignature,KeyEncipherment `
    -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.1")

# Неправильно — вызывает NTE_BAD_ALGID:
# -Provider "Microsoft RSA SChannel Cryptographic Provider"
Шаг 4 — Привязка сертификата к RDP
# Копируем в хранилище Remote Desktop
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store(
    "Remote Desktop", "LocalMachine")
$store.Open("ReadWrite")
$store.Add($cert)
$store.Close()

# Привязываем к RDP через WMI
$wmi = Get-WmiObject -Class Win32_TSGeneralSetting `
    -Namespace root/cimv2/TerminalServices `
    -Filter "TerminalName='RDP-Tcp'"
$wmi.SSLCertificateSHA1Hash = $cert.Thumbprint
$wmi.Put()
Шаг 5 — Настройка SecurityLayer и NLA

Устанавливаем SecurityLayer=1 (Negotiate) — золотая середина между безопасностью и отказоустойчивостью:

Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
    -Name SecurityLayer -Value 1

Set-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" `
    -Name UserAuthentication -Value 1
ПараметрЗначениеОписание
SecurityLayer = 0RDP SecurityБез SSL. Логин на экране сервера. Для диагностики.
SecurityLayer = 1NegotiateПробует SSL, откатывается на RDP. Рекомендуется.
SecurityLayer = 2SSL/TLSСтрого SSL. Требует рабочий сертификат.
Шаг 6 — Перезапуск TermService
Restart-Service TermService -Force

Существующие сессии отключатся, но пользователи смогут переподключиться.

Проверка результата

После исправления проверяем все ключевые параметры:

# Служба работает
(Get-Service TermService).Status  # Running

# Порт слушает
netstat -an | findstr ":3389.*LISTEN"
# TCP  0.0.0.0:3389  0.0.0.0:0  LISTENING

# Listener активен
qwinsta
# rdp-tcp  65536  Listen

# GracePeriod удалён
Test-Path "HKLM:\...\GracePeriod"  # False

# Сертификат привязан
(Get-WmiObject -Class Win32_TSGeneralSetting `
    -Namespace root/cimv2/TerminalServices).SSLCertificateSHA1Hash
# FE7BD91C7C600C5C13BC23C16176656D89615AE5

Финальная проверка — тест RDP-хендшейка без клиента (Python):

import socket, struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(10)
s.connect(('server-ip', 3389))

# X.224 Connection Request
tpkt = struct.pack('>BBH', 3, 0, 11)
x224 = bytes([6, 0xE0, 0, 0, 0, 0, 0])
s.send(tpkt + x224)

resp = s.recv(1024)
if resp[5] == 0xD0:
    print("RDP CONNECTION CONFIRMED")
s.close()

Результат: RDP CONNECTION CONFIRMED. Пользователи подключились, пароль запрашивается на стороне клиента (NLA работает).

Бонус: удалённая диагностика через WinRM

Если RDP не работает, единственный способ попасть на сервер — WinRM. Вот минимальный скрипт на Python для диагностики:

import winrm

s = winrm.Session(
    'http://server-ip:5985/wsman',
    auth=('DOMAIN\\admin', 'password'),
    transport='ntlm'
)

# Проверить службу RDP
r = s.run_cmd('sc query TermService')
print(r.std_out.decode('cp866'))

# Проверить журнал событий
r = s.run_cmd('wevtutil qe System /c:5 /f:text /rd:true '
              '/q:"*[System[EventID=1057 or EventID=36870]]"')
print(r.std_out.decode('utf-8', errors='replace'))

# Проверить сертификат
r = s.run_cmd('certutil -store "Remote Desktop"')
print(r.std_out.decode('cp866'))

# Проверить SecurityLayer
r = s.run_cmd('reg query "HKLM\\SYSTEM\\CurrentControlSet'
              '\\Control\\Terminal Server\\WinStations'
              '\\RDP-Tcp" /v SecurityLayer')
print(r.std_out.decode('cp866'))

Чек-лист: если RDP выдаёт «внутреннюю ошибку»

  1. Проверить EventID 1057 в журнале System — проблема с SSL-сертификатом
  2. Проверить EventID 36870 (Schannel) — нет доступа к закрытому ключу
  3. Проверить права на MachineKeysSYSTEM и NETWORK SERVICE должны иметь доступ
  4. Проверить Grace Period — если 0 дней, удалить ключ через SYSTEM
  5. Проверить SecurityLayer — при проблемах с сертификатом временно поставить 0 или 1
  6. Пересоздать сертификат — без указания -Provider в New-SelfSignedCertificate
  7. Перезапустить TermServicenet stop TermService /y && net start TermService

Частые вопросы

Это общая ошибка клиента RDP, которая может означать несколько разных проблем на сервере: повреждённый SSL-сертификат (EventID 1057), истекший Grace Period лицензирования RDS, ошибку Schannel при доступе к закрытому ключу (EventID 36870), или повреждённые права на папку MachineKeys. Для точной диагностики необходимо подключиться к серверу по WinRM и проверить журнал событий System.
Ключ реестра HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\GracePeriod защищён от удаления даже для администраторов. Нужно создать запланированную задачу от имени NT AUTHORITY\SYSTEM с командой Remove-Item. После удаления перезапустите TermService. Убедитесь, что лицензионный сервер настроен и содержит активные CAL.
Ошибка 0x80090008 возникает при указании -Provider "Microsoft RSA SChannel Cryptographic Provider" в сочетании с -HashAlgorithm SHA256. Этот legacy-провайдер не поддерживает некоторые комбинации параметров. Решение — не указывать -Provider и позволить системе выбрать CNG-провайдер автоматически. Также проверьте права на папку MachineKeys.
SecurityLayer=0 (RDP Security) — классический протокол без SSL, логин на экране сервера. SecurityLayer=1 (Negotiate) — пробует SSL, при неудаче откатывается на RDP Security. SecurityLayer=2 (SSL/TLS) — строго требует рабочий SSL-сертификат. Рекомендуется SecurityLayer=1 как баланс безопасности и отказоустойчивости.
Отправьте X.224 Connection Request на порт 3389 через TCP-сокет. Если в ответ придёт пакет с байтом 0xD0 (Connection Confirm) — RDP-сервер работает. Через PowerShell: New-Object System.Net.Sockets.TcpClient('server', 3389) — проверяет открытость порта, но не протокол. Для полной проверки нужен протокольный тест.

Терминальный сервер не пускает пользователей?

Мы диагностируем и чиним RDS удалённо через WinRM — без перезагрузки и без потери активных сессий. 15+ лет опыта, собственный дата-центр.

Написать в Telegram