“Running scripts is disabled on this system” is the error every new Windows Server admin meets on day one. The cause is the execution policy, and the fix is rarely “set it to Unrestricted.” Execution policy is not a security boundary – a user can bypass it trivially – but it is a useful guardrail that stops accidental script execution and enforces a signing workflow. On an internet-facing VPS, how you configure it matters.
The policy levels and what they actually do
| Policy | Default on | Behavior |
|---|---|---|
| Restricted | Windows clients | No scripts run at all; interactive commands only |
| RemoteSigned | Windows Server | Local scripts run; downloaded scripts must be signed or unblocked |
| AllSigned | – | Every script must carry a valid signature |
| Unrestricted | – | All scripts run; downloaded ones prompt once (and are not blocked) |
| Bypass | – | Nothing is checked; use only for one-off bootstrapping |
| Undefined | – | No policy set at this scope; falls through to the next scope |
RemoteSigned is the sensible default for a server: scripts you write on the box run, and anything that arrived from the internet is blocked until it is signed or explicitly unblocked. The mechanism is the Mark of the Web: files downloaded through a browser carry a Zone.Identifier alternate data stream, and RemoteSigned refuses to run scripts that carry it.
Checking and setting the policy
# See every scope and which one wins
Get-ExecutionPolicy -List
# Set for the machine (needs elevation)
Set-ExecutionPolicy RemoteSigned -Scope LocalMachine
# One-off bypass for a single command, no permanent change
powershell -ExecutionPolicy Bypass -File .\bootstrap.ps1
pwsh -ExecutionPolicy Bypass -File .\bootstrap.ps1
Scopes are evaluated in a fixed order: MachinePolicy, UserPolicy, Process, CurrentUser, LocalMachine. The first one that is set wins. GPO policies (MachinePolicy/UserPolicy) always override everything else, which is how a domain admin pins the setting on managed servers. On a standalone VPS, LocalMachine is the scope to use so the policy survives reboots and applies to scheduled tasks and service accounts, not just your interactive session.
Why you should not reach for Unrestricted
Unrestricted on an exposed server means any script that lands on disk – via a dropped web shell, a compromised upload folder, or a phishing-delivered payload – executes without a prompt when invoked. The cost of RemoteSigned is a few seconds of Unblock-File when a legitimately downloaded script needs to run:
# Only for files you have reviewed
Unblock-File -Path "C:\tools\Get-IISLogStats.ps1"
# or clear the Zone.Identifier stream directly
Remove-Item "C:\tools\Get-IISLogStats.ps1" -Stream Zone.Identifier
Note that curl.exe and Invoke-WebRequest do not add the Mark of the Web, while browsers and Start-BitsTransfer do. A script pulled with curl will run under RemoteSigned even though a browser download of the same file would not – keep that in mind when auditing how files actually arrived.
Signing scripts for production automation
For scheduled tasks and unattended automation, move from “unblock the file” to “sign the script.” Create a code-signing certificate and sign your own scripts:
New-SelfSignedCertificate -Type CodeSigningCert `
-Subject "CN=Ops Scripts (Internal)" `
-CertStoreLocation Cert:\LocalMachine\My
$cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object Subject -like "*Ops Scripts*" | Select-Object -First 1
Set-AuthenticodeSignature -FilePath "C:\scripts\DailyBackup.ps1" -Certificate $cert
# verify
Get-AuthenticodeSignature "C:\scripts\DailyBackup.ps1"
A self-signed certificate keeps the script running under RemoteSigned on the same machine, but other servers will not trust it until you add it to their Trusted Publishers store. For scripts that cross machines, buy a code-signing certificate from a public CA and sign in your build pipeline instead of on the server.
Execution policy vs. Constrained Language Mode
Do not confuse execution policy with language mode. If AppLocker or Windows Defender Application Control (WDAC) policies are active, PowerShell runs in Constrained Language Mode regardless of the execution policy: scripts execute, but they are restricted to a safe subset of the language. On a locked-down VPS, that is the stronger control – execution policy just keeps the careless stuff from running at all.
A sane baseline for a small Windows VPS
Set-ExecutionPolicy RemoteSigned -Scope LocalMachineright after first login.- Leave Unrestricted and Bypass out of permanent configuration; use
-ExecutionPolicy Bypassper-invocation when bootstrapping. - Unblock files only after review; prefer signing for anything that runs unattended.
- Check
Get-ExecutionPolicy -Listafter domain join or GPO changes – policy may silently change. - Audit occasionally:
Get-ChildItem -Recurse -Filter *.ps1 | Unblock-File -WhatIfshows what still carries the Mark of the Web.
Execution policy is ten minutes of setup that prevents a class of “how did that script even run” incidents. If you are setting up a fresh server and want a guided first-hour sequence, the feature checklist on our site on our site covers adjacent hardening steps, and you can compare managed vs. self-managed options on the Windows VPS comparison table on our site before you commit.



