SMB File Shares on a Windows VPS: Permissions, Access Control, and Automation

SMB looks simple until the second user appears. A share that works perfectly for the local administrator fails for a service account because of one inherited ACE, and the symptom is never “access denied” in a useful place — it is an application log line saying the file is in use, or a backup job that silently writes zero bytes. On a Windows VPS where the share is the actual storage layer for IIS content or a scheduled export, permissions are the difference between a working service and a 3 a.m. call.

This covers how NTFS and share permissions combine, where inheritance breaks, how to audit effective access without guessing, and how to automate share provisioning with PowerShell so the same ACE set lands on every VPS you build. All examples assume a Windows Server 2019/2022 VPS with a single data volume, which is how most hosting images are laid out — the storage and OS options are summarised at our Windows VPS comparison.

Share permissions are a ceiling, not a grant

Windows evaluates share permission and NTFS permission separately and applies the more restrictive of the two. A user with Full Control on the share and Modify on NTFS gets Modify. A user with Read on the share and Full Control on NTFS gets Read. The trap is that share permissions default to Everyone:Read on a new share, so teams spend an hour editing ACLs before realising the ceiling was never raised.

Set the share ceiling to Everyone:Change and do all real authorisation in NTFS, where you have inheritance, auditing, and icacls support. Never create per-user share permissions — they do not survive a share recreation and are invisible in Explorer’s Security tab. Check the ceiling with Get-SmbShareAccess -Name data and confirm it looks identical on every host you build.

Where inheritance silently breaks

An ACE marked “This folder only” stops propagating the moment someone copies a directory tree with robocopy without /SEC, or restores files from a backup catalog. The child objects keep explicit ACEs copied from the source and no longer inherit. Result: new files land in a folder whose inherited policy no longer reaches subfolders, and permissions drift only on that branch.

Audit for it with a one-liner that lists directories where inheritance is disabled: Get-ChildItem D:\data -Directory -Recurse | Where-Object { (Get-Acl $_.FullName).AreAccessRulesProtected } | Select-Object FullName. Re-enable inheritance deliberately, not in bulk, because enabling it on a folder that relied on explicit denies will broaden access.

A permission model that survives rebuilds

Use three groups per share and nothing else: SG_Data_Read, SG_Data_Modify, and SG_Data_Admin. Assign users to groups, never to folders. Grant the groups at the share root with inheritance enabled, and add a single explicit deny only where a genuine exception exists — a quarantine folder that no one but the admin group can read.

This model means a rebuild is three icacls calls and two Add-LocalGroupMember calls. It also means access reviews are group membership exports, which any auditor will accept, rather than an ACL diff that no one can read. Keep the group names stable across hosts so a script written once applies everywhere.

Recommended ACL matrix

PathPrincipalNTFS rightsApplies to
D:\dataSG_Data_ReadRead & ExecuteThis folder, subfolders, files
D:\dataSG_Data_ModifyModifyThis folder, subfolders, files
D:\dataSG_Data_AdminFull ControlThis folder, subfolders, files
D:\data\quarantineSG_Data_ReadDeny: List folderThis folder only
D:\data\quarantineSG_Data_AdminFull ControlThis folder, subfolders, files
D:\data\dropSG_Data_ModifyWrite, Create filesThis folder only

Where the model breaks on a workgroup VPS

Most single-server plans are not domain-joined, so local groups are the only option and they do not survive an image rebuild. Export them to CSV as a build artefact: Get-LocalGroupMember SG_Data_Modify | Export-Csv members.csv. Restoring a rebuilt host becomes one import loop, and the ACLs come back from the same script that created them.

Access-based enumeration adds a second trap. With ABE enabled, folders a user cannot read simply do not appear in the share, so a user reports a missing folder when in reality they lack List on a parent. Set it per share with Set-SmbShare -Name data -FolderEnumerationMode AccessBased — off by default on Server 2019/2022 — and leave it off for application shares, where a service expecting a predictable listing fails in a way that looks like a timeout.

Automating share creation with PowerShell

The following has to be idempotent — it must be safe to run twice on the same host. New-SmbShare fails if the share exists, so test first, and use icacls with /grant rather than rebuilding the whole DACL, which would drop explicit denies you set on purpose.

$path = 'D:\data'
$name = 'data'
if (-not (Get-SmbShare -Name $name -ErrorAction SilentlyContinue)) {
  New-SmbShare -Name $name -Path $path -ChangeAccess 'Everyone' -FullAccess 'Administrators'
}
foreach ($g in 'SG_Data_Read','SG_Data_Modify','SG_Data_Admin') {
  if (-not (Get-LocalGroup -Name $g -ErrorAction SilentlyContinue)) { New-LocalGroup -Name $g }
}
icacls $path /grant 'SG_Data_Read:(OI)(CI)RX' 'SG_Data_Modify:(OI)(CI)M' 'SG_Data_Admin:(OI)(CI)F' /T /Q
icacls $path /inheritance:e

Verifying effective access before users complain

icacls shows ACEs but not the result. Use Get-Acl plus a group expansion to compute effective access for a named user, or rely on the SMB client itself: net use Z: \\localhost\data /user:DOMAIN\svc_web from the server, then attempt a write. A successful write from a session running as the service account is the only proof that matters.

For scheduled jobs, verify with the actual task principal rather than your own account — a task running as SYSTEM bypasses most denies and will pass a test that your application fails. Log the verification result in the build script so a rebuild that breaks permissions fails loudly instead of at the first customer upload. Provider-side disk layout and snapshot behaviour also affect whether ACLs survive a restore, which is worth confirming on the plan comparison table before you commit a data volume to a single host.

Leave a Comment