Operations + migration + security
Know the boundary you are opening.
The safe default is local. Public exposure, TLS, limits, logs, and Host routing are deliberate operator choices.
Before network exposure
Start local, then change one boundary at a time.
- Keep loopback for local tools.The default
host:'127.0.0.1'accepts connections only from this machine. - Set an external bind deliberately.
host:'0.0.0.0'or--host 0.0.0.0listens on IPv4 interfaces. Connect through a real interface address;0.0.0.0is not a browser destination. - Apply network controls.Configure firewall, container, VM, or platform ingress to expose only the intended port and sources.
- Put edge controls at the proxy.node-http-server focuses on static serving and Host/root routing. Add authentication, authorization, rate limits, WAF policy, and certificate automation at the reverse proxy or hosting edge.
- Set body/time protections.Choose finite request limits and timeouts for expected traffic. The body cap is unlimited until configured.
- Review the served root.Keep secrets, source maps, private keys, logs, and unrelated files outside every configured root.
- Keep dotfiles blocked.The default rejects every dot-prefixed segment. Set
allowDotfiles:trueonly after auditing the complete dedicated root. - Protect logs and TLS keys.Restrict filesystem access and never serve their directories.
domain/domains select allowed Host values and roots. Authenticate clients at the application or edge, configure DNS in the zone, and terminate TLS through https or the proxy.Static HTTP contract
Expected request behavior.
| Request | Result | Operational note |
|---|---|---|
GET | Streams the selected file. | A custom beforeServe buffers file bodies and bypasses automatic compression. |
HEAD | Full-GET status/headers, no body. | Range is ignored. |
| Other method | 405 plus Allow: GET, HEAD. | Hooks run before static fallback; body collection may produce 413 first. |
| Matching ETag/date validator | 304. | Weak ETag and Last-Modified are generated for files. |
| One satisfiable GET byte range | 206. | Range responses are not compressed. |
| Valid unsatisfiable range | 416. | Includes unsatisfied content-range information. |
| Malformed/unsupported/multi-range | Ignored; full 200. | Only a single byte range is supported. |
| Directory path | Configured index or 404. | No directory listing. |
| Traversal/symlink/ADS escape | Denied. | Decoded paths and real paths remain inside the selected root. |
| Dot-prefixed path segment | 403 by default. | Encoded, nested, index, fallback, and real symlink targets are checked; literal allowDotfiles:true opts in globally for that server. |
Failure map
Translate status or process errors into the next check.
| Signal | Meaning | Next check |
|---|---|---|
400 | Malformed request/URL. | Request syntax and encoded path. |
403 | Traversal, dotfile policy, outside-root real path, ADS, or restricted extension. | Requested path, server.allowDotfiles, symlink target, and restrictedType. |
404 | Missing file/index or SPA conditions not met. | Root, index, extension, and Accept header. |
405 | Static fallback supports only GET/HEAD. | Use a hook for application methods. |
413 | Body exceeded the configured cap. | server.maxRequestBodyBytes. |
415 | Extension MIME entry is explicitly false. | contentType map. |
416 | Valid byte range is unsatisfiable. | Requested range and file length. |
421 | Host did not match primary/domain mapping. | domain, domains, and request Host. |
500 | Sanitized request/stream/filesystem failure. | lastError, logs, filesystem permissions; do not expose raw error text. |
EADDRINUSE | Port already owned. | Stop conflict, choose another port, or use port 0. |
EACCES | Bind not permitted. | Use an allowed address/port and check runtime permissions. |
Clean shutdown
Stop accepting work before process exit.
import {Server} from 'node-http-server';
const server=new Server({
root:'./public',
host:'127.0.0.1',
port:8080
}).deploy();
for(const signal of ['SIGINT','SIGTERM']){
process.once(signal,async()=>{
await server.close();
});
}The CLI handles SIGINT (exit 130) and SIGTERM (exit 0) after closing owned listeners. Library applications decide their own signal policy. Repeated/concurrent close() calls are safe and share in-flight close work.
error listeners to both server.server and server.secureServer when both protocols run. Listener failures are not stored in lastError.Request logs
Keep records useful and protected.
| Behavior | Contract |
|---|---|
| Format | One JSON object per line with timestamp. |
| Redaction | authorization, cookie, proxy-authorization, set-cookie, and x-api-key are replaced. |
| Body | Excluded unless logBody:true. |
| Write failure | Captured in lastError; default serving continues. CLI shows it only with verbose output. |
| Destination | Parent directory must exist and be writable; use restrictive permissions. |
Redaction does not make the entire record harmless. URLs, addresses, custom headers, and deliberately logged bodies can still contain sensitive data.
Certificates
Generate local material outside the served root.
- Create a local certificate directory.Keep the private subdirectory ignored and outside every static root.
- Generate a new private key and certificate.Use the local certificate instructions for development, or your deployment's certificate authority for production.
- Pass paths through the module API.The CLI does not accept HTTPS configuration.
- Protect and rotate.Use restrictive permissions and replace compromised or expired keys.
Migrating from v8
Handle the deliberate changes first.
| Check | v9 action |
|---|---|
| Runtime | Use Node.js 22.12 or newer. |
| Network bind | Default changed to 127.0.0.1; set host explicitly for external traffic. |
| Module system | Keep CommonJS or use ESM; default singleton and named constructors are available in both. |
| Multiple servers | Create isolated new Server(config) instances. |
| Shutdown | Await Promise-based close(); redeployment is supported afterward. |
| Request bytes | Use request.bodyBuffer for original bytes; request.body remains UTF-8 text. |
| Static HTTP | Review streaming, HEAD, range, ETag, Last-Modified, method, SPA, and dotfile behavior. |
| Dotfiles | Keep the default denial or explicitly audit every root before allowDotfiles:true. |
| MIME | Unknown types now use octet-stream; overlay the built-in map, reject one type with false, or set contentType:false. |
| CLI parsing | Config no longer reads process arguments; use the CLI entry or pass objects. |
| Certificates | Generate new keys; never use old fixture material. |
Security reporting
Report privately when possible.
Use the repository's private vulnerability reporting channel when it is available. Otherwise contact the maintainer without publishing exploit details, keys, credentials, or sensitive logs in a public issue.