A portable, single-file web server for Windows. Drop the executable in a
folder, put your files in www\, and serve them over HTTP or HTTPS — from a
console window or as a native Windows service. No installer, no runtime, no
dependencies.
- Truly portable — configuration, logs and content all live next to the executable. Copy the folder anywhere (USB stick included) and it works.
- Windows service built in — install, start, stop and remove the service from the same executable. Talks to the Service Control Manager directly via the Win32 API; pywin32, NSSM or srvany are not needed.
- Least privilege by default — the service runs under the
NT SERVICE\PocketWebServervirtual account, not LocalSystem. File permissions are granted on install and revoked on removal. - HTTPS — point it to a PEM certificate and key and it serves TLS.
- Optional CGI — run batch, PowerShell, Python or PHP scripts from a
cgi-binfolder. Interpreters are detected automatically; custom ones can be mapped in the config. - Safe by default — no directory listing, path traversal blocked, CGI disabled unless requested, runaway scripts killed with their whole process tree.
- Friendly CLI — colored output, plain-language errors instead of stack
traces,
--helpwith practical examples.
:: Serve the www\ folder next to the exe on port 8080 (Ctrl+C to stop)
PocketWebServer.exe run
:: Serve an arbitrary folder on another port
PocketWebServer.exe run --port 9000 --root C:\path\to\siterun serves index.html for / and for any sub-folder that contains one;
everything else maps to files inside the root. /healthz answers OK, handy
for monitoring.
Service commands need an elevated (Administrator) prompt:
:: Install: --auto starts at boot, --manual (default) starts on demand,
:: --start also starts it right now
PocketWebServer.exe install --root C:\path\to\site --auto --start
:: Day-to-day management
PocketWebServer.exe start
PocketWebServer.exe stop
PocketWebServer.exe restart
PocketWebServer.exe status
PocketWebServer.exe removeThe service runs under the NT SERVICE\PocketWebServer virtual account: a
per-service identity that Windows creates and removes together with the
service, with no password to manage. On install the account is granted
read/execute on the program and content folders and write access to logs\;
remove revokes the grants. Pass install --system if you specifically need
the service to run as LocalSystem.
If the served folder lies outside the program folder, permissions are granted
there as well — avoid pointing --root at system locations.
Note for onefile builds: the service account must be able to unpack the
onefile bundle on first start. If the service will not start under the
virtual account, reinstall with --system or build with --standalone.
Settings are stored in config.json next to the executable, created by
install or config. Command-line options always win over the file for a
single run; with config they are saved permanently. A ready-to-edit
template ships as config.example.json — copy it to
config.json and adjust the paths if you prefer editing by hand.
:: Show current configuration and service state
PocketWebServer.exe config
:: Change something, then restart to apply
PocketWebServer.exe config --port 9000
PocketWebServer.exe restart| Key | Default | Meaning |
|---|---|---|
host |
0.0.0.0 |
Address to bind (0.0.0.0 = all interfaces) |
port |
8080 |
TCP port |
root_dir |
www\ next to the exe |
Folder served over HTTP |
log_path |
logs\server.log |
Rotating log file (2 MB × 5) |
cert_file / key_file |
empty | PEM pair; set both to enable HTTPS |
cgi_enabled |
false |
Master switch for CGI |
cgi_dir |
cgi-bin\ next to the exe |
Folder with CGI scripts |
cgi_timeout |
30 |
Seconds before a CGI script is killed |
cgi_handlers |
{} |
Extra interpreters by extension (see CGI) |
Invalid values in config.json fall back to their defaults instead of
crashing the server.
PocketWebServer.exe run --port 8443 --cert C:\certs\cert.pem --key C:\certs\key.pemBoth files must be in PEM format (the usual fullchain.pem /
privkey.pem pair works as-is). To go back to plain HTTP use
config --http, or run --http to ignore the certificate for one run. For
quick internal tests a self-signed certificate does the job:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"Disabled by default. Enable it with --cgi (uses cgi-bin\ next to the
executable, created if missing) or --cgi-dir C:\path\to\scripts; turn it
off again with --no-cgi. While enabled, http://host:port/cgi-bin/<script>
executes the script and returns its output; everything else is still served
statically.
Supported script types:
.exe,.bat,.cmd— run natively, always available..ps1— runs through PowerShell 7 (pwsh) when installed, otherwise the built-in Windows PowerShell 5.1..py— available when a Python interpreter is found inPATH..php— available whenphp-cgi.exeis found inPATHor in a common install folder.- Anything else can be mapped in
config.json; use a list when the interpreter needs arguments:
"cgi_handlers": {
".pl": "C:\\perl\\bin\\perl.exe",
".rb": ["C:\\ruby\\bin\\ruby.exe", "-W0"]
}Scripts receive the standard CGI/1.1 environment (REQUEST_METHOD,
QUERY_STRING, CONTENT_LENGTH, REMOTE_ADDR, HTTP_*, ...), read POST
bodies from stdin, and must print headers, a blank line, then the body:
@echo off
echo Content-Type: text/html
echo.
echo ^<h1^>Hello CGI^</h1^>Commented examples ship in cgi-bin\:
| Script | Language | Shows |
|---|---|---|
hello.cmd |
batch | minimal HTML response |
time.cmd |
batch | dynamic output (server date/time) |
env.cmd |
batch | every CGI variable received (debugging aid) |
echo.cmd |
batch | reading the query string safely (delayed expansion) |
hello.ps1 |
PowerShell | environment variables and dynamic HTML |
info.py |
Python | query parsing and POST body from stdin |
info.php |
PHP | $_GET / $_POST handling via php-cgi |
The examples are harmless but talkative (env.cmd prints the whole
environment): in production keep only the scripts you actually use.
A script that exceeds cgi_timeout is killed together with its whole process
tree, so a hung script cannot pile up orphan processes. Unknown extensions
return 404; traversal attempts return 403.
Security note: CGI is remote code execution by design. The
least-privilege service account limits the blast radius, but a vulnerable
script still exposes whatever that account can reach. In batch scripts always
expand request data with delayed expansion (!QUERY_STRING!), never
%QUERY_STRING%, or a crafted query string can inject commands.
Built for LAN and small-scale use. Keep these in mind before exposing it any wider:
- No cap on concurrent CGI processes; a request flood can spawn many
interpreters (each still dies at
cgi_timeout). HEADon a CGI URL executes the script and discards the body (Apache does the same)./healthzis reserved and shadows awww\healthzfile;/cgi-bin/is reserved while CGI is enabled.- No
Range/ download-resume support for static files.
Requirements:
- Windows 10/11
- Python 3.11 or newer (developed and tested on 3.12)
- Nuitka:
pip install nuitka - A C compiler. Nuitka offers to download a suitable MinGW toolchain on the
first build (the script passes
--assume-yes-for-downloads), or it will use Visual Studio if present.
Then:
build.batThis produces dist\PocketWebServer.exe as a self-contained onefile binary.
The bundle is unpacked once per version into %LOCALAPPDATA% and reused on
later starts. To change the release number, update VERSION in build.bat
and in pocketwebserver.py.
No compilation is required during development — the script runs directly:
python pocketwebserver.py run --port 8080If you find this project useful and would like to support its development, consider making a donation. Any contribution is greatly appreciated!
Bitcoin (BTC) Addresses:
- 1LToggiof3rNUTCemJZSsxd1qubTYoSde6
- 3LToggio7Xx8qMsjCFfiarV4U2ZR9iU9ob
Licensed under the Apache License 2.0. You are free to use,
modify and redistribute this software, provided the copyright notice and
license are preserved — see the LICENSE and NOTICE files.
Copyright (C) 2026 Luca Soltoggio - Edptech S.r.l.