Шаг 1 — Подготовка и проверка окружения
Перед установкой SFTP-сервера проверим, не установлен ли OpenSSH ранее, свободен ли TCP-порт 22, а затем подготовим каталог и локальную группу для SFTP-пользователей.
Все команды выполняются в PowerShell от имени администратора.
Проверяем наличие службы OpenSSH
Get-Service sshd -ErrorAction SilentlyContinue | Format-List Name, Status, StartType
Если команда ничего не выводит, служба sshd в системе отсутствует.
Проверяем порт 22
Get-NetTCPConnection -State Listen -LocalPort 22 -ErrorAction SilentlyContinue
Если команда не возвращает результат, в данный момент TCP-порт 22 не прослушивается другим процессом.
Создаём каталог для SFTP
New-Item -ItemType Directory -Path 'D:\SFTP' -Force | Out-Null
В каталоге D:\SFTP будут размещаться персональные директории SFTP-пользователей.
Создаём локальную группу SFTP-пользователей
New-LocalGroup -Name 'sftpusers' -Description 'Пользователи SFTP сервера' -ErrorAction SilentlyContinue
После выполнения команды будет создана локальная группа sftpusers:
Name Description
---- -----------
sftpusers Пользователи SFTP сервера
В дальнейшем именно к участникам этой группы мы применим ограничения OpenSSH.

Шаг 2 — Установка Win32-OpenSSH
Теперь установим OpenSSH-сервер. В нашем примере используется сборка Win32-OpenSSH из официального репозитория PowerShell на GitHub.
Скачиваем OpenSSH
Скачайте актуальный 64-битный архив OpenSSH-Win64.zip с GitHub.
Создаём временную папку
New-Item -ItemType Directory -Path 'C:\Temp' -Force | Out-Null
Скопируйте скачанный файл OpenSSH-Win64.zip в каталог C:\Temp.
Распаковываем OpenSSH
$zip = 'C:\Temp\OpenSSH-Win64.zip'
$dest = 'C:\Program Files\OpenSSH'
New-Item -ItemType Directory -Path $dest -Force | Out-Null
Expand-Archive -Path $zip -DestinationPath $dest -Force
Проверяем структуру каталога
В зависимости от структуры архива файлы OpenSSH могут оказаться во вложенной директории. Проверить и при необходимости переместить их можно следующим блоком:
$nested = Get-ChildItem -Directory $dest | Where-Object {
Test-Path "$($_.FullName)\sshd.exe"
}
if ($nested) {
Move-Item -Path "$($nested.FullName)\*" -Destination $dest -Force
Remove-Item $nested.FullName -Recurse -Force
}
Проверяем необходимые файлы
Get-ChildItem "$dest\sshd.exe","$dest\sftp-server.exe","$dest\Install-SSHD.ps1" -ErrorAction SilentlyContinue
В каталоге должны присутствовать как минимум:
sshd.exe
sftp-server.exe
Install-SSHD.ps1
Если файлы присутствуют, можно переходить к регистрации службы.
Шаг 3 — Регистрация и запуск службы OpenSSH
Устанавливаем службу sshd
Перейдите в каталог OpenSSH:
Set-Location 'C:\Program Files\OpenSSH'
Разблокируйте установочный скрипт и запустите его:
Unblock-File .\Install-SSHD.ps1
.\Install-SSHD.ps1
После установки проверьте наличие службы:
Get-Service sshd
Настроим автоматический запуск и запустим её:
Set-Service -Name sshd -StartupType Automatic
Start-Service sshd
Разрешаем подключения на TCP-порт 22
Создадим правило Windows Defender Firewall:
New-NetFirewallRule -Name 'OpenSSH-Server' `
-DisplayName 'OpenSSH Server (TCP 22)' `
-Direction Inbound `
-Action Allow `
-Protocol TCP `
-LocalPort 22 `
-Profile Any `
-ErrorAction SilentlyContinue
Проверяем службу и порт
Get-Service sshd | Format-List Name, Status, StartType
Ожидаемый результат:
Name : sshd
Status : Running
StartType : Automatic
Теперь проверим TCP-порт:
Test-NetConnection -ComputerName localhost -Port 22
При успешном запуске получим:
TcpTestSucceeded : True
Шаг 4 — Настройка sshd_config в режиме SFTP-only
Теперь ограничим участников группы sftpusers только SFTP. Интерактивная SSH-сессия для них использоваться не будет.
В качестве начального каталога пользователя будет использоваться:
D:\SFTP\<имя_пользователя>
Настраиваем Subsystem sftp
Конфигурационный файл OpenSSH находится по адресу:
C:\ProgramData\ssh\sshd_config
Перед изменением конфигурации рекомендую сделать резервную копию:
Copy-Item 'C:\ProgramData\ssh\sshd_config' 'C:\ProgramData\ssh\sshd_config.bak' -Force
Затем выполним:
$cfg = 'C:\ProgramData\ssh\sshd_config'
New-Item -ItemType Directory -Path 'D:\SFTP' -Force | Out-Null
$raw = Get-Content $cfg -Raw
$raw = $raw -replace '(?m)^\s*Subsystem\s+sftp\s+.*$', 'Subsystem sftp "C:/Program Files/OpenSSH/sftp-server.exe"'
Set-Content -Path $cfg -Value $raw -Encoding ASCII
Добавляем Match Group sftpusers
В конец sshd_config необходимо добавить:
Match Group sftpusers
ForceCommand "C:/Program Files/OpenSSH/sftp-server.exe" -d "D:/SFTP/%u"
AllowTcpForwarding no
X11Forwarding no
PermitTTY no
Здесь:
Match Group sftpusersприменяет настройки только к участникам группы;ForceCommandзапускает SFTP-сервер и задаёт начальный каталог пользователя;AllowTcpForwarding noотключает TCP forwarding;X11Forwarding noотключает X11 forwarding;PermitTTY noзапрещает выделение терминала.
Проверяем конфигурацию
Перед перезапуском службы обязательно проверьте синтаксис:
& 'C:\Program Files\OpenSSH\sshd.exe' -t -f $cfg
Если ошибок нет, команда ничего не выведет.
После успешной проверки перезапустите службу:
Restart-Service sshd
Шаг 5 — Создание пользователя и настройка NTFS-прав
Теперь создадим локального пользователя, добавим его в группу sftpusers и подготовим персональный каталог.
Создаём пользователя
$User = 'sftp-test1'
$Pwd = Read-Host -AsSecureString 'Введите пароль для sftp-test1'
New-LocalUser `
-Name $User `
-Password $Pwd `
-PasswordNeverExpires:$true `
-AccountNeverExpires:$true `
-Description 'Тестовый пользователь для SFTP'

Добавляем пользователя в группу sftpusers
Add-LocalGroupMember -Group 'sftpusers' -Member $User

Настраиваем корневой каталог SFTP
icacls 'D:\SFTP' /inheritance:r
icacls 'D:\SFTP' /grant:r "Administrators:(OI)(CI)F"
icacls 'D:\SFTP' /grant "SYSTEM:(OI)(CI)F"
Корневой каталог D:\SFTP не должен предоставлять обычным SFTP-пользователям доступ к чужим домашним директориям.
Настраиваем домашний каталог пользователя
$UserHome = "D:\SFTP\$User"
$LocalAcct = "$env:COMPUTERNAME\$User"
New-Item -ItemType Directory -Path $UserHome -Force | Out-Null
icacls $UserHome /inheritance:r
icacls $UserHome /grant:r "Administrators:(OI)(CI)F"
icacls $UserHome /grant "SYSTEM:(OI)(CI)F"
icacls $UserHome /grant "$($LocalAcct):(OI)(CI)M"
Проверить итоговые ACL можно командой:
icacls $UserHome
В результате администраторы и SYSTEM должны иметь полный доступ, а сам пользователь — права Modify только к своей директории.
Шаг 6 — Проверка подключения
Проверка через sftp.exe
Для первоначального теста можно использовать клиент из установленного комплекта OpenSSH:
& "C:\Program Files\OpenSSH\sftp.exe" sftp-test1@localhost
После ввода пароля появится приглашение:
Connected to localhost.
sftp>
Проверьте работу команд:
pwd
ls
mkdir test
ls
cd ..
ls
exit
Пользователь должен иметь возможность создавать и изменять данные внутри своей директории, но не должен получать доступ к каталогам, на которые у его учётной записи отсутствуют NTFS-права.
Проверка через WinSCP
Теперь проверим подключение через WinSCP:
- протокол — SFTP;
- хост — IP-адрес SFTP-сервера;
- порт — 22;
- имя пользователя —
sftp-test1; - пароль — пароль, заданный при создании пользователя.

Проверяем попытку выхода из домашнего каталога
Особое внимание уделите попытке перейти на уровень выше домашней директории.
В нашем тесте при первом переходе вверх WinSCP получил ошибку доступа:

Однако дальнейшая проверка показала важную особенность используемой конфигурации.

Клиент смог перейти выше и увидеть содержимое диска D:.

При дальнейшем переходе стало возможно увидеть доступные тома файловой системы.

Шаг 7 — Ограничение доступа к диску с SFTP
В нашем примере для SFTP выделен диск D:. Если этот том используется исключительно для SFTP, можно дополнительно ограничить доступ к его корню.
Сохраняем текущие ACL
Перед изменениями сохраните существующие права:
icacls D:\ /save C:\Temp\acl_backup_before_sftp_root.txt
Резервную копию лучше хранить не на томе, ACL которого вы собираетесь изменять.
Настраиваем права на выделенном SFTP-томе
Если D: действительно является отдельным томом исключительно для SFTP, после проверки существующих ACL можно применить ограниченную конфигурацию:
icacls D:\ /inheritance:r
icacls D:\ /remove "Users" "Authenticated Users" "Everyone"
icacls D:\ /grant:r "Administrators:(OI)(CI)F"
icacls D:\ /grant "SYSTEM:(OI)(CI)F"
После этого проверьте результат:
icacls D:\
Шаг 8 — Ограничение доступа к другим каталогам
Вместо массового изменения ACL системного диска C:\ безопаснее исходить из принципа минимальных прав: SFTP-пользователь не должен состоять в лишних локальных группах и не должен получать дополнительные разрешения на каталоги за пределами своей рабочей области.
Проверьте членство пользователя:
Get-LocalGroup | ForEach-Object {
$Group = $_.Name
Get-LocalGroupMember -Group $Group -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match 'sftp-test1' } |
Select-Object @{Name='Group';Expression={$Group}}, Name
}
Также можно проверить конкретные каталоги, которые пользователь потенциально способен открыть:
icacls 'C:\'
icacls 'D:\'
icacls 'D:\SFTP'
icacls 'D:\SFTP\sftp-test1'
Шаг 9 — Автоматическое создание SFTP-пользователей
Когда базовая конфигурация проверена, создание пользователей можно автоматизировать. Скрипт будет:
- создавать локального пользователя;
- добавлять его в группу
sftpusers; - создавать домашнюю директорию;
- настраивать NTFS ACL;
- создавать каталог
upload; - добавлять файл
README.txt.
Скрипт adduser.ps1
Готовый скрипт можно скачать здесь или создать файл C:\scripts\adduser.ps1.
Set-ExecutionPolicy -Scope Process Bypass -Force
$ErrorActionPreference = "Stop"
$Root = "D:\SFTP"
if (-not (Test-Path $Root)) {
New-Item -ItemType Directory -Path $Root -Force | Out-Null
}
if (-not (Get-LocalGroup -Name 'sftpusers' -ErrorAction SilentlyContinue)) {
New-LocalGroup -Name 'sftpusers' | Out-Null
}
$UserName = Read-Host "Введите ЛОГИН нового SFTP-пользователя"
$Desc = Read-Host "Введите ОПИСАНИЕ пользователя"
function Read-Secret([string]$Prompt) {
Read-Host -AsSecureString -Prompt $Prompt
}
do {
$Pwd1 = Read-Secret "Введите ПАРОЛЬ"
$Pwd2 = Read-Secret "Повторите ПАРОЛЬ"
$b1 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd1)
$p1 = [Runtime.InteropServices.Marshal]::PtrToStringUni($b1)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($b1)
$b2 = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Pwd2)
$p2 = [Runtime.InteropServices.Marshal]::PtrToStringUni($b2)
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($b2)
if ($p1 -ne $p2) {
Write-Host "Пароли не совпадают, попробуйте снова." -ForegroundColor Yellow
}
} until ($p1 -eq $p2)
if (-not (Get-LocalUser -Name $UserName -ErrorAction SilentlyContinue)) {
New-LocalUser `
-Name $UserName `
-Password $Pwd1 `
-PasswordNeverExpires:$true `
-AccountNeverExpires:$true `
-Description $Desc | Out-Null
}
else {
$adsi = [ADSI]"WinNT://$env:COMPUTERNAME/$UserName,user"
$adsi.SetPassword($p1)
$adsi.SetInfo()
Set-LocalUser -Name $UserName -Description $Desc
}
if (-not (
Get-LocalGroupMember -Group 'sftpusers' -ErrorAction SilentlyContinue |
Where-Object { $_.Name -ieq "$env:COMPUTERNAME\$UserName" }
)) {
Add-LocalGroupMember -Group 'sftpusers' -Member $UserName
}
$UserHome = Join-Path $Root $UserName
New-Item -ItemType Directory -Path $UserHome -Force | Out-Null
icacls $Root /inheritance:r | Out-Null
icacls $Root /grant:r "Administrators:(OI)(CI)F" | Out-Null
icacls $Root /grant "SYSTEM:(OI)(CI)F" | Out-Null
$acct = "$env:COMPUTERNAME\$UserName"
icacls $UserHome /inheritance:r | Out-Null
icacls $UserHome /grant:r "Administrators:(OI)(CI)F" | Out-Null
icacls $UserHome /grant "SYSTEM:(OI)(CI)F" | Out-Null
icacls $UserHome /grant ("{0}:(OI)(CI)M" -f $acct) | Out-Null
$Upload = Join-Path $UserHome 'upload'
if (-not (Test-Path $Upload)) {
New-Item -ItemType Directory -Path $Upload -Force | Out-Null
}
icacls $Upload /inheritance:r | Out-Null
icacls $Upload /grant:r "Administrators:(OI)(CI)F" | Out-Null
icacls $Upload /grant "SYSTEM:(OI)(CI)F" | Out-Null
icacls $Upload /grant ("{0}:(OI)(CI)M" -f $acct) | Out-Null
$readme = Join-Path $UserHome 'README.txt'
if (-not (Test-Path $readme)) {
Set-Content $readme -Encoding UTF8 -Value "Добро пожаловать! Загружайте файлы в папку 'upload'."
}
Write-Host "Готово: $UserName → $UserHome. Описание: $Desc" -ForegroundColor Green
Запускаем скрипт
cd C:\scripts
.\adduser.ps1
После выполнения скрипта новый пользователь появится в группе sftpusers, а в D:\SFTP будет создан его персональный каталог.
Проверяем нового пользователя
Подключитесь через WinSCP:
- протокол — SFTP;
- хост — IP-адрес сервера;
- порт — 22;
- имя пользователя — созданная учётная запись;
- пароль — пароль, указанный при запуске скрипта.
После входа пользователь должен иметь доступ к своей директории и папке upload.

FAQ
Можно ли развернуть SFTP-сервер на Windows Server?
Да. Для этого можно использовать OpenSSH Server. Пользователи подключаются по протоколу SFTP через TCP-порт 22, а управление сервером и конфигурацией выполняется стандартными средствами Windows и PowerShell.
Чем SFTP отличается от FTP?
SFTP работает поверх SSH и использует защищённое соединение. Обычный FTP без дополнительного шифрования передаёт данные и учётные сведения небезопасным способом.
Можно ли разрешить SFTP, но запретить пользователю обычный SSH?
Да. В OpenSSH для участников отдельной группы можно использовать блок Match Group и параметр ForceCommand, принудительно запускающий SFTP-сервер вместо обычной интерактивной SSH-сессии.
ForceCommand изолирует пользователя внутри его домашней папки?
Нет. Параметр -d задаёт начальный каталог SFTP-сеанса, но не превращает его в настоящий chroot. Доступ пользователя к другим каталогам Windows определяется в том числе NTFS-разрешениями.
Как запретить SFTP-пользователю просматривать чужие файлы?
Необходимо правильно настроить NTFS ACL: пользователь должен иметь необходимые права на свою рабочую директорию и не получать разрешений на чужие каталоги. После настройки обязательно проверьте фактический доступ под тестовой учётной записью.
Нужно ли открывать порт 22 в Windows Firewall?
Да, если к SFTP-серверу будут подключаться с других компьютеров. При этом лучше ограничить правило нужными сетями или IP-адресами, если архитектура инфраструктуры это позволяет.
Можно ли использовать доменные учётные записи Active Directory?
Да, OpenSSH в Windows может работать с доменными учётными записями, однако настройка групп, идентификаторов пользователей и NTFS-разрешений будет отличаться от рассмотренного здесь сценария с локальными пользователями.
Заключение
Мы настроили SFTP-сервер на Windows с помощью OpenSSH: установили и запустили службу sshd, ограничили отдельную группу пользователей режимом SFTP-only, создали персональные каталоги и настроили NTFS-права.
Главный момент такой конфигурации — не путать начальную директорию SFTP с полноценным chroot. В Windows OpenSSH фактический доступ пользователя к файловой системе необходимо контролировать с помощью NTFS ACL и обязательно проверять под обычной пользовательской учётной записью.
После проверки базовой конфигурации создание новых SFTP-пользователей можно автоматизировать PowerShell-скриптом. Это значительно упрощает дальнейшее администрирование сервера и позволяет выдавать отдельные каталоги сотрудникам или внешним контрагентам без ручной настройки каждого пользователя.
Вам понравилась эта статья? Тогда вам, скорее всего, будет интересна другая полезная статья Проверка состояния Active Directory с помощью PowerShell.