Коротко о проблеме
Клиент RDP выдаёт «Произошла внутренняя ошибка». Сервер удалённый, физического доступа нет, связь только через VPN. Порт 3389 при этом отвечает.
Ситуация неприятна тем, что «внутренняя ошибка» — это заглушка, за которой скрывается десяток разных причин, а привычный канал администрирования как раз и лежит. Ниже — методика, которая доводит до диагноза по шагам, а не перебором наугад.
Шаг 0. Отсечь клиентскую сторону
Начинать нужно с себя — это бесплатно и не требует доступа к серверу. RDP 8 и новее пытается установить дополнительный UDP-транспорт, и поверх VPN он часто не работает, давая ровно эту ошибку.
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client" /v fClientDisableUDP /t REG_DWORD /d 1 /f
Заодно чистим кэш клиента — битые записи в нём дают тот же симптом:
del /f /q "%LOCALAPPDATA%\Microsoft\Terminal Server Client\Cache\*"
Если после этого подключение прошло — дальше можно не читать.
Шаг 1. Понять, где именно рвётся
Test-NetConnection 10.0.0.10 -Port 3389 -InformationLevel Detailed
Развилка здесь принципиальная:
TcpTestSucceeded : False— проблема не в RDP вообще. Это маршрутизация, файрвол или туннель. Копать надо там, всё остальное бессмысленно.TcpTestSucceeded : True— служба жива и слушает, соединение принимается. Значит рвётся после установки TCP-сессии: на рукопожатии TLS или на аутентификации.
Второй вариант и есть наш случай. Он сразу отсекает половину советов из интернета: если порт слушается, то «служба не запущена» и «RDP отключён в системе» — не про нас.
Шаг 2. Найти запасной канал управления
Чтобы чинить, нужно попасть на сервер хоть как-то. Проверяем, что доступно через туннель:
Test-NetConnection 10.0.0.10 -Port 5985 # WinRM
Test-NetConnection 10.0.0.10 -Port 445 # SMB / RPC
Если WinRM отвечает, но Enter-PSSession ругается на TrustedHosts — это не «порт закрыт», а недоверие клиента к хосту при NTLM-аутентификации. Лечится на своей машине:
Set-Item WSMan:\localhost\Client\TrustedHosts -Value 10.0.0.10 -Force
Учётку при подключении указывать явно, с префиксом машины: 10.0.0.10\Administrator. Иначе NTLM снова споткнётся.
Отдельно стоит помнить: правило файрвола для WinRM в публичном профиле по умолчанию разрешает доступ только из той же локальной подсети. Клиент за VPN почти всегда в другой подсети — и режется этим правилом, даже когда служба работает.
Шаг 3. Ловушка, на которой всё встаёт: UAC Remote Restrictions
Это ключевой момент статьи, и именно на нём теряется больше всего времени.
Картина выглядит так: SMB (445) открыт, пароль локального администратора верный, но всё подряд отбивается отказом в доступе.
psexec \\10.0.0.10 -u 10.0.0.10\admin -p *** powershell -c "Restart-Service TermService -Force"
# Couldn't access 10.0.0.10: не найден сетевой путь
# Make sure that the default admin$ share is enabled
sc.exe \\10.0.0.10 stop TermService
# [SC] OpenService: ошибка 5: Отказано в доступе
Get-WmiObject -ComputerName 10.0.0.10 -Credential $cred -Class Win32_Service
# Отказано в доступе. (0x80070005 E_ACCESSDENIED)
Три разных инструмента, три разных ошибки — и одна общая причина. Начиная с Windows Vista действует механизм UAC Remote Restrictions: при удалённом входе локальная учётная запись администратора получает урезанный токен. Аутентификация проходит, а административных прав у сессии нет. Поэтому отваливаются и административные шары, и SCM, и WMI одновременно.
Важно: на доменные учётные записи это не распространяется. Если сервер в домене и есть доменный админ — проблемы нет. Ловушка бьёт именно по standalone-серверам с локальными учётками.
Снимается ограничение одним ключом реестра:
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
LocalAccountTokenFilterPolicy = 1 (DWORD)
И тут возникает то, ради чего стоит дочитать до конца: чтобы записать этот ключ удалённо, нужны ровно те права, которые он и разблокирует. Замкнутый круг.
Единственный шанс разорвать его снаружи — служба удалённого реестра. Она работает под системным токеном, и UAC-фильтрация её иногда не задевает:
reg query "\\10.0.0.10\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v LocalAccountTokenFilterPolicy
Если куст читается — пишем ключ, и все остальные каналы оживают без перезагрузки:
reg add "\\10.0.0.10\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f
Если и удалённый реестр закрыт — программных рычагов не остаётся. Дальше только уровень ниже операционной системы: IPMI, iLO, iDRAC, KVM-консоль хостера или гипервизор. А если сервер стоит на своём железе без контроллера управления — увы, нужны чьи-то руки на площадке.
Маленькая, но обидная деталь для тех, кто работает в PowerShell: sc там является алиасом для Set-Content. Команда до сервера просто не долетает, выдавая невнятную ошибку про позиционный параметр. Писать нужно sc.exe.
Шаг 4. Диагностика с консоли
Когда доступ получен, первым делом снимаем общую картину:
Get-Service TermService,UmRdpService | Select Name,Status,StartType
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -Name fDenyTSConnections
netstat -ano | findstr ":3389"
qwinsta
В нашем случае всё выглядело здоровым: службы Running, fDenyTSConnections = 0, порт слушается, слушатель RDP-Tcp в состоянии приёма. То есть типовые советы уже исчерпаны, а проблема осталась.
Дальше — журнал, и читать его нужно осмысленно:
Get-WinEvent -LogName "Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational" -MaxEvents 20 |
Select TimeCreated,Id,Message | Format-List
Ключевые события:
- 258 — слушатель начал прослушивание. Служба поднялась нормально.
- 261 — слушатель принял соединение. Ваши попытки долетают до сервера.
- 1149 — аутентификация пользователя выполнена успешно.
- 1057 / 1058 — не удалось создать или прочитать самоподписанный сертификат.
Самое информативное здесь — то, чего нет. Если события 261 идут, а 1149 не появляется ни разу, соединение рвётся между приёмом и аутентификацией. Это рукопожатие TLS, и круг подозреваемых резко сужается. Если при этом нет 1057 и 1058 — генерация сертификата исправна, и весь блок советов про повреждённый каталог MachineKeys можно смело пропустить.
Шаг 5. Изолировать переменную
Главная методическая ошибка на этом этапе — менять несколько настроек разом. Классический совет из интернета звучит как «отключите NLA и снизьте уровень безопасности», и он действительно чаще всего возвращает подключение. Только вот после этого вы не знаете, что именно было сломано, и обратно уже не соберёте.
Уровень безопасности слушателя и NLA — две независимые вещи, и проверять их надо по очереди:
$ts = Get-WmiObject -Namespace root\cimv2\TerminalServices -Class Win32_TSGeneralSetting -Filter "TerminalName='RDP-Tcp'"
$ts.SetSecurityLayer(0) # 0 = RDP, 1 = Negotiate, 2 = SSL
$ts.SetUserAuthenticationRequired(0) # NLA
Restart-Service TermService -Force
Если подключение прошло — возвращаем только уровень безопасности, оставив NLA выключенным:
$ts.SetSecurityLayer(1)
Restart-Service TermService -Force
Заходит — значит виноват NLA и CredSSP. Не заходит — виноват TLS. Дальше вы чините конкретную вещь, а не гадаете.
Понятно, что режим без TLS и без NLA — это дыра, и оставлять сервер в нём нельзя. Это диагностический шаг на несколько минут, не решение.
Шаг 6. Разбираемся с TLS
Если виноват TLS, проверяем конфигурацию Schannel. Чаще всего её ломает хардненинг — вручную, групповой политикой или утилитами вроде IIS Crypto:
Get-ChildItem "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols" -Recurse |
ForEach-Object { "$($_.Name -replace '.*Protocols\\','') Enabled=$((Get-ItemProperty $_.PSPath -EA SilentlyContinue).Enabled)" }
(Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\FipsAlgorithmPolicy" -EA SilentlyContinue).Enabled
(Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002" -EA SilentlyContinue).Functions
Последняя команда — недооценённая. Групповая политика умеет навязывать серверу собственный список наборов шифров, и если в нём не осталось ни одного, пригодного для RDP, вы получите ровно «внутреннюю ошибку» при полностью корректных настройках протоколов.
Если Schannel чист, а TLS не работает — смотрим сертификат и, что важнее, доступ к его закрытому ключу:
Get-ChildItem "Cert:\LocalMachine\Remote Desktop" | Select Thumbprint,Subject,NotAfter,NotBefore
Сертификат может быть валиден и виден, но если у службы нет прав на чтение закрытого ключа, рукопожатие рвётся. При этом нативное RDP-шифрование продолжает работать, потому что ключ ему не нужен — отсюда и обманчивая картина «без TLS всё хорошо».
$c = Get-ChildItem "Cert:\LocalMachine\Remote Desktop"
$kf = $c.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$kf"
В списке должен присутствовать NT AUTHORITY\NETWORK SERVICE:(R). Если его нет — выдаём:
icacls "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\$kf" /grant "NT AUTHORITY\NETWORK SERVICE:(R)"
Restart-Service TermService -Force
Радикальный вариант — удалить сертификат и дать службе создать новый, права на ключ при генерации выставляются корректно:
Get-ChildItem "Cert:\LocalMachine\Remote Desktop" | Remove-Item -Force
Restart-Service TermService -Force
Здесь стоит быть готовым к тому, что Restart-Service зависнет в ожидании остановки: TermService часто не отпускает активные сеансы. Ожидание в консоли безобидно, Ctrl+C прерывает только цикл ожидания, а не саму операцию. Проверять результат нужно через Get-Service, а не по факту возврата приглашения — иначе легко решить, что сертификат не сгенерировался, хотя служба просто ещё не поднялась.
Шаг 7. Если роль RDSH
Отдельный случай, дающий тот же симптом, — истёкший льготный период лицензирования на сервере узла сеансов:
Get-WindowsFeature RDS-RD-Server | Select Name,InstallState
Если роль установлена, а сервер лицензирования не настроен, через 120 дней подключения прекращаются с невнятной ошибкой. Сброс счётчика требует перезагрузки и является временной мерой, а не решением — лицензии всё равно придётся купить.
Чем закончилось в этом кейсе
Конечной причиной оказалась нерабочая пара «сертификат — закрытый ключ» у слушателя RDP-Tcp.
Коварство ситуации в том, что по всем внешним признакам сертификат был исправен: он присутствовал в хранилище, срок действия не истёк, привязка в реестре отсутствовала (то есть служба использовала свой самоподписанный), событий 1057 и 1058 в журнале не было — генерация не падала. Конфигурация Schannel тоже оказалась абсолютно чистой: TLS 1.2 для сервера включён, переопределений шифров, хешей и алгоритмов обмена ключами нет, FIPS выключен, список наборов шифров групповой политикой не навязан.
При этом рукопожатие TLS не проходило, а подключение с нативным RDP-шифрованием работало без нареканий. Объяснение простое: нативному шифрованию закрытый ключ не нужен, а Schannel без него сделать ничего не может. Отсюда и обманчивая картина «отключил TLS — всё заработало», которая уводит в сторону хардненинга и настроек протоколов, где искать нечего.
Лечение свелось к пересозданию сертификата — при генерации служба выставляет права на закрытый ключ корректно:
Get-ChildItem "Cert:\LocalMachine\Remote Desktop" | Remove-Item -Force
Restart-Service TermService -Force
После появления нового сертификата уровень безопасности возвращается на согласование, и подключение по TLS проходит штатно:
$ts = Get-WmiObject -Namespace root\cimv2\TerminalServices -Class Win32_TSGeneralSetting -Filter "TerminalName='RDP-Tcp'"
$ts.SetSecurityLayer(1)
$ts.SetUserAuthenticationRequired(1)
Restart-Service TermService -Force
Практический вывод: валидный по датам сертификат в хранилище ещё не означает, что им можно пользоваться. Проверять надо не только сам сертификат, но и доступность его закрытого ключа — а самый быстрый способ это выяснить занимает две команды и не требует разбора всей криптографической подсистемы.
И главное — на всё это ушло меньше времени, чем на попытки попасть на сервер. Что возвращает нас к разделу о профилактике.
Профилактика: главный вывод
Реальная проблема в этом кейсе была не в RDP. Проблема была в том, что при отказе RDP не осталось ни одного запасного канала управления. Несколько часов ушло не на починку, а на попытки хоть как-то попасть на сервер.
Пока доступ есть, стоит потратить пять минут и подготовиться к тому дню, когда его не будет:
# снять UAC-ограничение для локальных администраторов
New-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" `
-Name LocalAccountTokenFilterPolicy -Value 1 -PropertyType DWord -Force
# включить WinRM и разрешить доступ не только из локальной подсети
Enable-PSRemoting -Force
Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress Any
# удалённый реестр
Set-Service RemoteRegistry -StartupType Automatic
Start-Service RemoteRegistry
Разумеется, всё это открывается не в интернет, а внутрь доверенного периметра — VPN, управляющая подсеть, ограничение по адресам источника. Смысл не в том, чтобы ослабить сервер, а в том, чтобы иметь второй независимый путь.
И отдельно: если сервер стоит на своём железе, IPMI / iLO / iDRAC надо настраивать и проверять заранее. Контроллер управления, о котором вспомнили в момент аварии, обычно оказывается либо не подключённым к сети, либо с неизвестным паролем.
Итог
Порядок действий, который экономит время:
- Отсечь клиентскую сторону — UDP и кэш.
- Проверить порт: жив ли слушатель вообще.
- Найти запасной канал управления, помня про UAC Remote Restrictions на standalone-серверах.
- С консоли снять картину служб и прочитать журнал, обращая внимание на отсутствующие события, а не только на присутствующие.
- Изолировать одну переменную: TLS и NLA проверять по очереди.
- Если виноват TLS — сначала Schannel, затем сертификат и доступность его закрытого ключа.
- Чинить конкретную причину, а не оставлять сервер с отключённой защитой.

Комментариев пока нет
Будьте первым — оставьте свой.