How to Grant Specific Service Control Permissions (Start/Stop/Restart) to Non-Admin Users on Standalone Windows Servers


3 views

When managing Windows Services on non-domain-joined servers (particularly in internet-facing environments), administrators often need to delegate service control permissions without granting full administrative rights. The standard Windows security model doesn't provide granular service control permissions out of the box.

The most straightforward method uses Windows' native sc.exe command-line tool to modify the security descriptor of specific services:

sc sdshow "YourServiceName" > currentSD.txt
# Modify the output with appropriate permissions (more on this below)
sc sdset "YourServiceName" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWRPWPDTLOCRRC;;;SU)(A;;RPWPDTLO;;;S-1-5-32-555)"

The security descriptor string follows this pattern:

D:(ace1)(ace2)...(aceN)

Where each ACE (Access Control Entry) has the structure:

(A;;access_mask;;;sid)
  • RP: SERVICE_START
  • WP: SERVICE_STOP
  • DT: SERVICE_PAUSE_CONTINUE
  • LO: SERVICE_QUERY_STATUS
  • CR: SERVICE_USER_DEFINED_CONTROL

To grant a local group "ServiceOperators" start/stop/restart permissions on "MyBackgroundService":

# First get the SID of your target group
wmic group where name='ServiceOperators' get sid
# Output might look like: S-1-5-21-3623811015-3361044348-30300820-1013

# Then apply the modified SDDL
sc sdset "MyBackgroundService" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWRPWPDTLOCRRC;;;SU)(A;;RPWPDTLO;;;S-1-5-21-3623811015-3361044348-30300820-1013)"

For modern servers, PowerShell provides more intuitive control:

$service = Get-Service -Name "MyBackgroundService"
$acl = Get-Acl -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)"
$rule = New-Object System.Security.AccessControl.ServiceAccessRule("DOMAIN\User", "Start,Stop", "Allow")
$acl.AddAccessRule($rule)
Set-Acl -Path "HKLM:\SYSTEM\CurrentControlSet\Services\$($service.Name)" -AclObject $acl

Always verify changes with:

sc sdshow "ServiceName"
# Or for PowerShell:
Get-Acl "HKLM:\SYSTEM\CurrentControlSet\Services\ServiceName" | Format-List
  • Never grant SERVICE_CHANGE_CONFIG or SERVICE_ALL_ACCESS to non-admin users
  • Prefer group-based permissions over individual user accounts
  • Document all permission changes in your change control system
  • Consider creating a dedicated local group for service operators

Managing Windows Services often requires administrative privileges, which creates security concerns when you need to delegate specific service control to non-admin users or service accounts. The common workarounds like modifying registry ACLs (as mentioned in KB907460) or granting full administrator rights are either too broad or potentially unsafe.

Windows provides a robust way to modify service permissions through Service Control (SC) commands using Security Descriptor Definition Language (SDDL). Here's how to implement it:

:: Grant "Start/Stop" permissions to a specific user
sc sdset "YourServiceName" "D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;RPWPDTLO;;;S-1-5-21-123456789-1234567890-123456789-1001)"

The SDDL string contains these key components:

  • D: - DACL (Discretionary Access Control List) prefix
  • A;;CCLCSWRPWPDTLOCRRC;;;SY - System account permissions
  • A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA - Built-in administrators permissions
  • A;;CCLCSWLOCRRC;;;IU - Interactive users permissions
  • A;;RPWPDTLO;;;S-1-5-21-... - Custom user permissions (replace with target user/group SID)

Here's a more maintainable PowerShell script to set these permissions:

$serviceName = "YourServiceName"
$userSid = (New-Object System.Security.Principal.NTAccount("DOMAIN\Username")).Translate([System.Security.Principal.SecurityIdentifier]).Value

$sddl = @"
D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;RPWPDTLO;;;$userSid)
"@

sc.exe sdset $serviceName $sddl

To check the current security descriptor of a service:

sc.exe sdshow "YourServiceName"

Microsoft's SubInACL provides another approach:

subinacl.exe /service "YourServiceName" /grant=domain\username=STO

Where STO represents:

  • S - Start service
  • T - Stop service
  • O - Other control permissions

When implementing this solution:

  • Always use the principle of least privilege
  • Prefer granting permissions to groups rather than individual users
  • Document all permission changes for audit purposes
  • Consider implementing just-in-time access for production environments

For local accounts (non-domain), first get the SID using:

wmic useraccount where name='username' get sid

Then use the SID in your SDDL string as shown in the PowerShell example.