Things that bite on Windows but work transparently on macOS/Linux.
Portless uses OpenSSL to generate the local CA on first run. Without it:
Error: openssl failed: spawnSync openssl ENOENT
Git for Windows ships a usable OpenSSL at C:\Program Files\Git\usr\bin\openssl.exe. Add it to your user PATH permanently:
$gitBin = "C:\Program Files\Git\usr\bin"
$current = [Environment]::GetEnvironmentVariable("PATH", "User")
if ($current -notlike "*$gitBin*") {
[Environment]::SetEnvironmentVariable("PATH", "$gitBin;$current", "User")
}
# Verify
openssl version
For Task Scheduler / boot-time launches, the PATH must be set in the wrapper script (Task Scheduler runs with minimal PATH by default).
winget install -e --id ShiningLight.OpenSSL.Light
# or
scoop install openssl
portless trust calls Windows's certutil.exe to add the portless CA to the system trust store. Side effects:
*.<tld> certs after portless trustsecurity.enterprise_roots.enabled=true in about:configportless trust may prompt the UAC dialog; without admin elevation it may silently fail to add system-wide trust. Run from an elevated PowerShell for reliable installation.
Symptom: curl https://myapp.test/ returns HTTP code 000, but the browser loads https://myapp.test/ fine with a green padlock.
Reason: curl uses its own CA bundle, browsers use the OS trust store.
Three workarounds for curl on Windows:
# 1. Skip verification (quickest, fine for local dev)
curl -k https://myapp.test/
# 2. Point curl at portless's CA explicitly
curl --cacert "$env:USERPROFILE/.portless/ca.pem" https://myapp.test/
# 3. Add the portless CA to curl's bundle (one-time setup)
# Locate curl's CA bundle (varies by install):
curl-config --ca # if curl-config is available
# Or check $env:CURL_CA_BUNDLE or the bundle at the curl install dir
# Then append portless's CA to it:
type "$env:USERPROFILE\.portless\ca.pem" >> "C:\path\to\curl\bin\curl-ca-bundle.crt"
For most dev workflows just use -k — it's the fastest path.
portless service install registers a Task Scheduler entry that runs the proxy at system startup. Notes:
%USERPROFILE%\.portless\ — Task Scheduler running as SYSTEM might not see it. Override with PORTLESS_STATE_DIR env if needed.portless service uninstall or portless clean (also removes the task)For Process Compose's boot task, see process-compose-ops skill's boot-persistence-windows.md.
Windows uses C:\Windows\System32\drivers\etc\hosts. portless hosts sync writes to it — requires admin elevation.
If portless isn't auto-syncing:
# Check what's in hosts
notepad C:\Windows\System32\drivers\etc\hosts
# Force a re-sync (run as admin)
portless hosts sync
On macOS/Linux, binding port 443 requires sudo (portless auto-elevates). On Windows, no elevation is required to bind privileged ports for the current user — portless just binds them directly.
Caveat: if another service is already bound to 443 (IIS, Skype, Caddy from old setup), portless will fail to start with EADDRINUSE. Find the culprit:
netstat -ano | findstr ":443 "
# Look at the PID, then:
Get-Process -Id <pid>
Stop the conflicting service or pick a different port (--port 1355).
PowerShell 5.1 (the default Windows PowerShell that ships with Windows) lacks some newer flags that PowerShell 7 has. Examples that bite:
# PS 7+: -SkipCertificateCheck
Invoke-WebRequest -Uri https://x.lab -SkipCertificateCheck
# PS 5.1: parameter not recognized → use curl.exe -k instead
# PS 7+: ternary operator
$x = $foo ? "yes" : "no"
# PS 5.1: parse error → use if-else
# PS 7+: pipeline parallel
... | ForEach-Object -Parallel { ... }
# PS 5.1: -Parallel not available
If a script needs PS 7+ features, the shebang doesn't help on Windows — invoke explicitly with pwsh instead of powershell:
pwsh -File .\myscript.ps1
# Stop proxy + uninstall boot task + clear state
portless clean
# Verify clean state
Get-NetTCPConnection -LocalPort 443 -State Listen -ErrorAction SilentlyContinue
# Should return nothing if portless was the only thing on 443
portless clean removes the portless CA from the trust store too, so browsers will see warnings again until you re-trust.