Before network exposure

Start local, then change one boundary at a time.

  1. Keep loopback for local tools.The default host:'127.0.0.1' accepts connections only from this machine.
  2. Set an external bind deliberately.host:'0.0.0.0' or --host 0.0.0.0 listens on IPv4 interfaces. Connect through the machine's real interface address.
  3. Apply network controls.Configure firewall, container, VM, or platform ingress to expose only the intended port and sources.
  4. 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.
  5. Set body/time protections.Choose finite request limits and timeouts for expected traffic. The body cap is unlimited until configured.
  6. Review the served root.Keep secrets, source maps, private keys, logs, and unrelated files outside every configured root.
  7. Keep dotfiles blocked.The default rejects every dot-prefixed segment. Set allowDotfiles:true only after auditing the complete dedicated root.
  8. Protect logs and TLS keys.Restrict filesystem access and never serve their directories.
Host routing scope: 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 + HTTPS contract

Expected request behavior.

RequestResultOperational note
GETStreams the selected file.A custom beforeServe buffers file bodies and bypasses automatic compression.
HEADFull-GET status/headers, no body.Range is ignored.
Other method405 plus Allow: GET, HEAD.Hooks run before static fallback; body collection may produce 413 first.
Matching ETag/date validator304.Weak ETag and Last-Modified are generated for files.
One satisfiable GET byte range206.Range responses are not compressed.
Valid unsatisfiable range416.Includes unsatisfied content-range information.
Malformed/unsupported/multi-rangeIgnored; full 200.Only a single byte range is supported.
Directory pathConfigured index or 404.No directory listing.
Traversal/symlink/ADS escapeDenied.Decoded paths and real paths remain inside the selected root.
Dot-prefixed path segment403 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.

SignalMeaningNext check
400Malformed request/URL.Request syntax and encoded path.
403Traversal, dotfile policy, outside-root real path, ADS, or restricted extension.Requested path, server.allowDotfiles, symlink target, and restrictedType.
404Missing file/index or SPA conditions not met.Root, index, extension, and Accept header.
405Static fallback supports only GET/HEAD.Use a hook for application methods.
413Body exceeded the configured cap.server.maxRequestBodyBytes.
415Extension MIME entry is explicitly false.contentType map.
416Valid byte range is unsatisfiable.Requested range and file length.
421Request authority did not match primary/domain mapping.domain, domains, and HTTP/1 Host or HTTP/2 :authority.
500Sanitized request/stream/filesystem failure.lastError, logs, filesystem permissions; keep raw error text private.
EADDRINUSEPort already owned.Stop conflict, choose another port, or use port 0.
EACCESBind not permitted.Use an allowed address/port and check runtime permissions.
Process stability: the process continues after request-level 4xx/5xx responses and asynchronous log-write errors. CLI parse errors exit 2; deployment/listener/shutdown failures exit 1.

Clean shutdown

Stop accepting work before process exit.

ESM · close HTTP and HTTPS
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.

Listener errors: attach Node 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.

BehaviorContract
FormatOne JSON object per line with timestamp.
Redactionauthorization, cookie, proxy-authorization, set-cookie, and x-api-key are replaced.
BodyExcluded unless logBody:true.
Write failureCaptured in lastError; default serving continues. CLI shows it only with verbose output.
DestinationParent directory must exist and be writable; use restrictive permissions.

Redaction covers known credential fields. URLs, addresses, custom headers, and deliberately logged bodies can still contain sensitive data.

Certificates

Generate local material outside the served root.

  1. Create a local certificate directory.Keep the private subdirectory ignored and outside every static root.
  2. Generate a new private key and certificate.Use the local certificate instructions for development, or your deployment's certificate authority for production.
  3. Pass paths through the module API.HTTPS configuration is available through the module API.
  4. Protect and rotate.Use restrictive permissions and replace compromised or expired keys.
Old fixtures are untrusted: v8 certificate/key fixtures were removed from the tracked tree. Any copies or values recoverable from history are public and must never be used.

Migrating from v9 to v10

Review HTTPS integrations for 10.0.0.

Configured HTTPS defaults to HTTP/2 with HTTP/1.1 negotiation on the same port. Existing key and certificate paths still configure the listener; plain HTTP and the CLI remain HTTP/1.1.

Integrationv10 behavior and action
Native HTTPS listenersecureServer is a Node Http2SecureServer by default. Set https.http2:false with the existing certificate configuration when the application requires the original https.Server or HTTP/1-specific native APIs.
Listener selectionhttps.only still controls the separate plain HTTP listener. TLS negotiation selects HTTP/2 or HTTP/1.1 for each HTTPS connection.
Custom hooksHook names and takeover semantics remain the same. HTTP/2 uses Node's compatibility request/response objects; use public APIs and compatible headers. Raw HTTP/1 socket writes, Connection, and Transfer-Encoding cannot serve an HTTP/2 stream. Read request.httpVersionMajor when protocol-specific behavior is needed.
Host routing and request bodiesRouting accepts HTTP/2 :authority or HTTP/1 Host. Complete HTTP/2 bodies reach the existing body fields without requiring Content-Length.
Timeoutsserver.timeout covers HTTP/2 session inactivity and HTTP/1 socket inactivity. requestTimeout, headersTimeout, and keepAliveTimeout apply to HTTP/1 connections, including HTTPS fallback; they do not set HTTP/2 stream deadlines.
Shutdown and failuresclose() closes HTTP/2 sessions gracefully after their active streams finish. Captured HTTP/2 session and stream failures are available through lastError; listener failures remain native Node error events.
Runtime and static servingNode.js 22.12 remains the minimum, with zero runtime dependencies. Static files, ranges, validators, compression, and manual serving use the shared pipeline.

Earlier v8-to-v9 migration

Handle the earlier deliberate changes.

Checkv9 action
RuntimeUse Node.js 22.12 or newer.
Network bindDefault changed to 127.0.0.1; set host explicitly for external traffic.
Module systemKeep CommonJS or use ESM; default singleton and named constructors are available in both.
Multiple serversCreate isolated new Server(config) instances.
ShutdownAwait Promise-based close(); redeployment is supported afterward.
Request bytesUse request.bodyBuffer for original bytes; request.body remains UTF-8 text.
Static HTTPReview streaming, HEAD, range, ETag, Last-Modified, method, SPA, and dotfile behavior.
DotfilesKeep the default denial or explicitly audit every root before allowDotfiles:true.
MIMEUnknown types now use octet-stream; overlay the built-in map, reject one type with false, or set contentType:false.
CLI parsingConfig no longer reads process arguments; use the CLI entry or pass objects.
CertificatesGenerate 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.