ZWServe: A Zero-Config Local CORS Web Server for Developers
One binary. No runtime, no dependencies. Copy it next to your project, run it, and instantly serve any folder over HTTP or HTTPS — with permissive CORS, caching disabled by default, and a built-in 30-year TLS certificate for
https://127.0.0.1. Perfect for local development, cross-origin testing, and serving m3u8 streams to a web player.
Download (pre-built, no install):
HTTPS cert + one-click installer (optional, only if you use -https):
Free · Self-contained binary · No runtime required · No sign-up
Why You Need a CORS-Aware Local Server
Most developers already reach for a local HTTP server at some point — to preview a built site, test an m3u8 stream, or load local assets. The problem is that the usual one-liners all fall short:
python -m http.serverworks, but it does not send CORS headers. The moment a page on another origin (an online tool, a deployed app, a web player) tries tofetchyour local files, the browser blocks it.- Node dev servers (
vite,webpack-dev-server, …) are great for a full project but are heavy when you just want to serve a folder of files — they neednode_modules, config, and a build setup. - A custom Express/Koa server means writing code every time, and you still have to remember to wire up CORS correctly.
ZWServe is the missing piece: a tiny, single-file binary that serves any folder, sends permissive CORS headers on every response, handles OPTIONS preflight correctly, and disables caching by default so you never debug stale-file ghosts. Copy it in, run it, done.
What ZWServe Does
Give it a directory (defaults to the current one) and a port. It will:
- Serve the folder over HTTP with directory listing,
index.htmlauto-indexing, and Range support (so you can scrub through large media). - Inject CORS headers (
Access-Control-Allow-Origin: *and friends) on every response, and answerOPTIONSpreflight requests with204 No Content. - Disable caching by default (
Cache-Control: no-store), so a refresh always returns the latest file on disk.
That’s it — three things, done right, with zero configuration.
Core Features
| Feature | Details |
|---|---|
| Zero configuration | Run the binary with no arguments and it serves the current directory on port 8000. That’s the whole UX. |
| Permissive CORS | Every response gets Access-Control-Allow-Origin: * plus the headers browsers need for cross-origin fetch/XHR. |
| Correct preflight | OPTIONS requests are answered with 204 No Content and the right CORS headers — no “method not allowed” surprises. |
| Caching disabled by default | Cache-Control: no-store is sent on every response, so you always see fresh files. Add -cache to allow browser caching for demos/production. |
| Optional HTTPS | Add -https to serve over TLS with a built-in 30-year self-signed cert (bound to 127.0.0.1 + localhost). Or bring your own cert with -cert/-key. |
| Range support | Honors Range/Accept-Ranges, so media players can seek and resume large files. |
| Directory listing + index | Browsing a folder shows its contents; an index.html is served automatically when present. |
| No runtime, no dependencies | A single self-contained binary per platform. No Python, Node, or any VM to install. |
| Pick your port & dir | -port (default 8000) and -dir (default .) flags. |
Quick Start on Each Platform
Download the binary for your OS, drop it in the folder you want to serve, and run.
Windows
# Serve the current folder on port 8000 (CORS + no-cache on)
.\zwserv-win.exe
# Custom port and folder
.\zwserv-win.exe -port 9000 -dir .\dist
macOS (Apple Silicon)
chmod +x zwserv-macos
./zwserv-macos # current dir, port 8000
./zwserv-macos -port 9000 -dir ./dist
First launch may be blocked by Gatekeeper for an unsigned binary. Right-click → Open → confirm, or run
xattr -d com.apple.quarantine zwserv-macosonce.
Linux
chmod +x zwserv-linux
./zwserv-linux # current dir, port 8000
./zwserv-linux -port 9000 -dir ./dist
On startup ZWServe prints every reachable URL (all local IPv4 addresses + localhost), so you can copy the right one straight away:
ZWServe — from zwplayer.com (local CORS web server)
-------------------------------------------------
ZWServe — minimal CORS local web server
---------------------------------------------------------
serving : /home/me/project/dist
port : 8000
cache : off (no-store — every refresh fetches the latest)
Serving at:
http://192.168.1.20:8000/
http://localhost:8000/
Why Choose ZWServe
ZWServe shines in frontend local development and cross-origin testing — anywhere you’d otherwise reach for python -m http.server, a heavy Node dev server, or a reverse proxy just to bypass CORS. It’s the fast, zero-config alternative for serving local files during dev.
1. Copy and run — no environment to set up
There’s nothing to install. ZWServe is a single self-contained binary — no Python, no Node, no node_modules, no virtualenv, no package manager. Drop it next to your project (or keep one copy in ~/bin) and run it. On a fresh machine, a colleague’s laptop, or a CI box, it just works. This makes it ideal for quick previews, sharing a build artifact for review, or spinning up a throwaway server in seconds.
2. Built-in CORS — cross-origin testing without the headache
This is ZWServe’s defining feature for development. Cross-origin requests are everywhere in modern workflows, and the browser’s CORS rules routinely get in the way:
- Test a local m3u8 in an online player. ZWPlayer runs on
zwplayer.com. When it tries tofetchyourlocalhost:8000/master.m3u8, that’s a cross-origin request that a normal server (and the file system) would block. ZWServe’sAccess-Control-Allow-Origin: *lets it through, and its correctOPTIONShandling means the preflight that players send before the real request succeeds. - Frontend hitting a remote API during dev. Point your dev frontend at local fixtures served by ZWServe, and your browser-based tools/fetches won’t be CORS-blocked.
- Mock third-party APIs and assets. Serve static JSON / images / subtitles and load them cross-origin from any page, no proxy needed.
Because CORS is always on and always correct, you stop fighting the browser and focus on the actual feature.
3. Caching disabled by default — no more “did my change take effect?”
The single most common debugging time-sink with a local server is a stale cache: you edit a file, refresh, and nothing changed — because the browser served the old version. ZWServe sends Cache-Control: no-store, no-cache, must-revalidate (plus matching Pragma/Expires) on every response by default, so every refresh hits the disk and returns the latest file. No hard-refresh, no DevTools “disable cache” toggle, no cache-busting query strings.
If you ever do want caching (a client demo, a production-ish static host), flip it on:
./zwserv-macos -cache # allow browser caching
Running ZWServe in the Background
For a long-running dev server you’ll keep up while you work, run ZWServe detached from the terminal so it survives you closing the window.
Linux & macOS — nohup
nohup (no hangup) detaches the process from the terminal and keeps it running after you log out. Redirect output to a log file so you can still inspect it:
# Start in the background, logs to zwserv.log
nohup ./zwserv-linux -port 8000 -dir ./dist > zwserv.log 2>&1 &
# Note the PID printed (e.g. [1] 12345), then verify
jobs -l
# or
pgrep -fa ZWServe
# Follow the log
tail -f zwserv.log
# Stop it later
kill $(pgrep -f zwserv)
> zwserv.log 2>&1merges stdout and stderr into the log file.- The trailing
&puts it in the background. nohupmakes it ignoreSIGHUP, so it keeps running when the terminal closes.
Windows — Start-Process (PowerShell)
PowerShell’s Start-Process launches ZWServe in its own process, detached from your console:
# Start detached, logging to zwserv.log
Start-Process -FilePath ".\zwserv-win.exe" -ArgumentList "-port","8000","-dir",".\dist" `
-WindowStyle Hidden -RedirectStandardOutput "zwserv.log" -RedirectStandardError "zwserv.err"
# Find it later
Get-Process zwserv-win
# Stop it
Stop-Process -Name zwserv-win
Windows — start (Command Prompt)
In cmd, the built-in start command detaches into a new window:
start "zwserv" /min zwserv-win.exe -port 8000 -dir .\dist
For a truly detached, no-window run on Windows, prefer the PowerShell
Start-Processform above.
Serving Over HTTPS (Built-in TLS Certificate)
Some things genuinely need HTTPS to test — Secure-Context-only APIs (service workers, getUserMedia, clipboard, crypto.subtle), Service Workers, or any code that behaves differently on a secure origin. ZWServe ships with a built-in 30-year self-signed certificate bound to 127.0.0.1 and localhost, so you can flip on HTTPS with one flag and test against https://127.0.0.1:8000.
1. Serve over HTTPS
# Use the built-in cert (bound to 127.0.0.1 + localhost)
./zwserv-win.exe -https
./zwserv-macos -https
./zwserv-linux -https -port 8443
# Or bring your own cert/key
./zwserv-win.exe -https -cert /path/my.crt -key /path/my.key
Without trusting the cert, your browser shows the usual “self-signed certificate” warning. To make https://127.0.0.1 load with a green lock, install the cert into your system trust store (next step).
2. Trust the built-in cert (one click)
Download the cert and the installer for your platform (links at the top of this page), then:
- Windows — right-click
install-cert-windows.bat→ Run as administrator. It runscertutil -addstore Rootto add the cert to your Trusted Root store. - macOS —
chmod +x install-cert-macos.sh && sudo ./install-cert-macos.sh. It adds the cert to the System keychain as a trusted root viasecurity add-trusted-cert. - Linux —
chmod +x install-cert-linux.sh && sudo ./install-cert-linux.sh. It installs into the distro CA store and runsupdate-ca-certificates/update-ca-trust.
After that, restart your browser and open https://127.0.0.1:8000/ — no warning.
3. Export the cert yourself (optional)
You can also write the built-in cert to the current directory at any time, then install it manually:
./zwserv-win.exe -writecert # writes zwserver.crt / .key / .pem in the served dir
About this certificate (read this)
- The cert (and its private key) is baked into every ZWServe binary — so all users share the same private key. That is fine only for
127.0.0.1/localhostlocal testing, where there’s no network exposure. Never expose this cert’s HTTPS endpoint to the public internet — anyone who has the binary has the private key. (For a browser to trust it, you still install it via the script below; the cert is not in any public root program.) - For a per-machine, non-shared local CA, use a tool like mkcert and pass your cert with
-cert/-key. - The cert is valid 30 years (2026–2056), SAN =
localhost+::1+127.0.0.1, RSA 2048.
Pairing ZWServe with ZWPlayer (and M3U8Grab)
ZWServe was built to slot into the ZWPlayer workflow, but it’s a general-purpose server you can use for anything.
Serving an m3u8 stream to ZWPlayer:
- Download a stream with M3U8Grab (mirrors the entire HLS tree to a self-contained folder).
- Serve that folder with ZWServe:
./zwserv-linux -dir ./media/myvideo. - Open ZWPlayer, paste
http://localhost:8000/master.m3u8, and play — with all subtitle tracks auto-mounted.
Because ZWServe provides the CORS headers ZWPlayer needs to reach your localhost, the whole loop “just works” — no proxy, no special build, no CORS errors.
Beyond video: use ZWServe to preview any static site build (-dir ./dist), serve local API fixtures, or share files across devices on your LAN via the printed network URL.
Command-Line Reference
| Option | Default | Description |
|---|---|---|
-port |
8000 |
TCP port to listen on |
-dir |
. |
Directory to serve (default: current dir) |
-cache |
false |
Allow client caching. Off by default (no-store), which is best for development. |
-https |
false |
Serve over HTTPS using the built-in self-signed cert (bound to 127.0.0.1/localhost). |
-cert |
"" |
Path to your own TLS cert (PEM). Implies HTTPS; pair with -key. |
-key |
"" |
Path to your own TLS key (PEM). Implies HTTPS; pair with -cert. |
-writecert |
false |
Write the built-in cert to zwserver.crt/.key/.pem in the served dir and exit. |
-h |
— | Show usage and examples. |
Common Recipes
# Default: current dir, port 8000, no-cache
./zwserv-win.exe
# Preview a built site on a custom port
./zwserv-win.exe -port 3000 -dir ./dist
# Allow caching (demo / production-ish)
./zwserv-win.exe -cache
# Background it on Linux/macOS and log output
nohup ./zwserv-linux -port 8000 -dir ./dist > zwserv.log 2>&1 &
FAQ
Does it support HTTPS?
Yes. Add -https to serve over TLS with the built-in 30-year self-signed cert (bound to 127.0.0.1/localhost), or use -cert/-key for your own cert. See Serving Over HTTPS above. To use the built-in cert without browser warnings, run the one-click installer for your platform.
Is it safe to leave running?
ZWServe binds to 0.0.0.0, so it’s reachable on your LAN — handy for testing on other devices, but be mindful if you’re on a shared network. It’s a static file server with no write/delete operations, so it can’t modify or delete your files.
How do I serve a file the browser keeps caching?
You usually don’t have to — caching is disabled by default. If you explicitly turned on -cache and want one file to always be fresh, just drop the -cache flag again.
It’s blocked by antivirus / Gatekeeper.
Because the binaries are unsigned, some systems flag them. macOS: right-click → Open → confirm, or xattr -d com.apple.quarantine <file>. Windows: in SmartScreen click “More info” → “Run anyway”.
Summary
ZWServe is the local server you keep in your back pocket: a single self-contained binary, no runtime, no config. It serves any folder with CORS always on, caching off by default, and correct preflight handling — so you can test cross-origin requests, serve m3u8 to an online player, or preview a build without fighting the browser. Run it in the foreground for a quick check, or background it with nohup / Start-Process for a long dev session.
- 🪟 Windows (.exe) · 🍎 macOS · 🐧 Linux
- ▶️ Pair it with ZWPlayer to play local m3u8 streams
- ⬇️ Grab streams first with M3U8Grab
- 💬 Subtitle Editor