Утро понедельника, 8 пользователей не могут подключиться к терминальному серверу. Клиент RDP выдаёт лаконичное «Произошла внутренняя ошибка» — и ни слова о том, что именно пошло не так. Знакомая ситуация? В этой статье разберём реальный кейс диагностики и ремонта RDS-сервера на Windows Server 2019, который мы решили удалённо через WinRM за 15 минут — без перезагрузки и без потери активных сессий.
Симптомы и исходная ситуация
Терминальный сервер 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 ...
Исправление: пошаговый процесс
Без этого шага ни один последующий не сработает — сертификаты не могут хранить ключи.
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys" `
/grant "SYSTEM:(OI)(CI)(F)" `
/grant "NETWORK SERVICE:(OI)(CI)(R,W)"
После выполнения в папке появились 428 файлов ключей — они сгенерировались из очереди запросов, ожидавших доступа.
Этот ключ реестра защищён специальными 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
Важный нюанс: нельзя указывать -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"
# Копируем в хранилище 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()
Устанавливаем 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 = 0 | RDP Security | Без SSL. Логин на экране сервера. Для диагностики. |
| SecurityLayer = 1 | Negotiate | Пробует SSL, откатывается на RDP. Рекомендуется. |
| SecurityLayer = 2 | SSL/TLS | Строго SSL. Требует рабочий сертификат. |
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 выдаёт «внутреннюю ошибку»
- Проверить EventID 1057 в журнале System — проблема с SSL-сертификатом
- Проверить EventID 36870 (Schannel) — нет доступа к закрытому ключу
- Проверить права на MachineKeys —
SYSTEMиNETWORK SERVICEдолжны иметь доступ - Проверить Grace Period — если 0 дней, удалить ключ через SYSTEM
- Проверить SecurityLayer — при проблемах с сертификатом временно поставить 0 или 1
- Пересоздать сертификат — без указания
-ProviderвNew-SelfSignedCertificate - Перезапустить TermService —
net stop TermService /y && net start TermService
Частые вопросы
HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server\RCM\GracePeriod защищён от удаления даже для администраторов. Нужно создать запланированную задачу от имени NT AUTHORITY\SYSTEM с командой Remove-Item. После удаления перезапустите TermService. Убедитесь, что лицензионный сервер настроен и содержит активные CAL.-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 как баланс безопасности и отказоустойчивости.0xD0 (Connection Confirm) — RDP-сервер работает. Через PowerShell: New-Object System.Net.Sockets.TcpClient('server', 3389) — проверяет открытость порта, но не протокол. Для полной проверки нужен протокольный тест.