Everything You Need
++ Production-grade features without the bloat. Every feature is battle-tested and security-hardened. +
+ +Zero-Alloc Hot Path
+
+ LRU cache hit at ~27 ns/op with 0 allocations. os.Stat is never called on
+ a cache hit — pure memory.
+
gzip + Brotli
+
+ On-the-fly gzip via pooled writers, plus pre-compressed .gz/.br sidecar file
+ support. Brotli preference over gzip.
+
TLS 1.2 / 1.3
+
+ Modern cipher suites, automatic HTTP→HTTPS redirects, HSTS, and HTTP/2 via ALPN negotiation — just set
+ tls_cert and tls_key.
+
Security Hardened
++ Path traversal prevention, dotfile blocking, CSP, HSTS, Referrer-Policy, Permissions-Policy — set on + every response. +
+Smart Caching
++ Byte-accurate LRU cache with configurable max size, per-file size cap, ETag, and live flush via SIGHUP + without downtime. +
+HTTP/2 & Range Requests
++ Full HTTP/2 support, byte-range serving for video / large files, conditional requests (ETag, + If-Modified-Since, 304). +
+CORS Built-In
+
+ Wildcard or per-origin CORS. Preflight returns 204 with proper headers. Wildcard emits literal
+ * — origin is never reflected.
+
Container Ready
++ Most settings overridable via environment variables. Graceful shutdown on SIGTERM/SIGINT with + configurable drain timeout. +
+Directory Listing
++ Optional HTML directory index with breadcrumb nav, sorted entries, human-readable sizes, and automatic + dotfile filtering. +
+Getting Started
+From zero to serving files in under 60 seconds.
+ +# Install via go install (requires Go 1.26+)
+go install github.com/BackendStack21/static-web/cmd/static-web@latest
+
+# Serve current directory on :8080
+static-web .
+
+# Serve a build output folder on port 3000
+static-web --port 3000 ./dist
+
+# Scaffold a config file
+static-web init
+
+# Start with a config file
+static-web --config config.toml .
+ # config.toml
+
+[server]
+addr = ":80"
+tls_addr = ":443"
+tls_cert = "/etc/ssl/certs/server.pem"
+tls_key = "/etc/ssl/private/server.key"
+
+[files]
+root = "./public"
+index = "index.html"
+not_found = "404.html"
+
+[cache]
+enabled = true
+max_bytes = 268435456 # 256 MiB
+max_file_size = 10485760 # 10 MiB
+
+[compression]
+enabled = true
+min_size = 1024
+precompressed = true
+
+[security]
+block_dotfiles = true
+directory_listing = false
+cors_origins = ["https://app.example.com"]
+csp = "default-src 'self'"
+ # Build the binary
+FROM golang:1.26-alpine AS builder
+WORKDIR /app
+RUN go install github.com/BackendStack21/static-web/cmd/static-web@latest
+
+# Minimal runtime image
+FROM alpine:3.19
+COPY --from=builder /root/go/bin/static-web /usr/local/bin/
+COPY ./public /public
+
+# Override config via env vars
+ENV STATIC_SERVER_ADDR=":8080"
+ENV STATIC_FILES_ROOT="/public"
+ENV STATIC_CACHE_ENABLED="true"
+
+EXPOSE 8080
+CMD ["static-web", "/public"]
+ Request Pipeline
++ Every request flows through a layered middleware chain — security first, cache last. +
+ +HTTP Request
+Incoming GET / HEAD / OPTIONS
+Recovery Middleware
+Panic → 500, log stack trace
+Logging Middleware
+Pooled status writer, method / path / status / bytes / duration
+Security Middleware
+Method whitelist · security headers · path safety · dotfile block · CORS
+Headers Middleware
+ETag · 304 Not Modified · Cache-Control · immutable assets
+Compress Middleware
+Lazy gzip via pooled writers · content-type / size gating
+File Handler
++ Cache hit → serve from memory (zero syscall) · brotli/gzip sidecar negotiation · miss → stat → read → + cache +
+Performance Benchmarks
+Measured on Apple M2 Pro · go test -bench=. -benchtime=5s
| Benchmark | +ops/s | +ns/op | +allocs/op | +
|---|---|---|---|
| BenchmarkCacheGet | +87–131 M | +27 | +0 | +
| BenchmarkCachePut | +42–63 M | +57 | +0 | +
| BenchmarkCacheGetParallel | +15–25 M | +142–147 | +0 | +
| BenchmarkHandler_CacheHit | +— | +~5,840 | +— | +
Cache-before-stat
+
+ os.Stat is never called on a cache hit. The hot path is pure memory — no filesystem touch.
+
Zero-alloc encoding
+
+ AcceptsEncoding parses the header without strings.Split — no heap allocation
+ on the hot path.
+
Pooled writers
+
+ sync.Pool for both gzip.Writer and statusResponseWriter — GC
+ pressure stays minimal.
+
Configuration Reference
++ All settings are optional — sensible defaults out of the box. Every TOML key has a matching env var for + containers. +
+ +| Key | +Default | +Description | +
|---|---|---|
addr |
+ :8080 |
+ HTTP listen address | +
tls_addr |
+ :8443 |
+ HTTPS listen address | +
tls_cert |
+ — | +Path to TLS certificate (PEM) | +
tls_key |
+ — | +Path to TLS private key (PEM) | +
read_header_timeout |
+ 5s |
+ Slowloris protection | +
read_timeout |
+ 10s |
+ Full request read deadline | +
write_timeout |
+ 10s |
+ Response write deadline | +
idle_timeout |
+ 75s |
+ Keep-alive idle timeout | +
shutdown_timeout |
+ 15s |
+ Graceful drain window | +
| Key | +Default | +Description | +
|---|---|---|
root |
+ ./public |
+ Directory to serve | +
index |
+ index.html |
+ Index file for directory requests | +
not_found |
+ — | +Custom 404 page (relative to root) | +
| Key | +Default | +Description | +
|---|---|---|
enabled |
+ true |
+ Toggle in-memory LRU cache | +
max_bytes |
+ 256 MiB |
+ Cache size cap (bytes) | +
max_file_size |
+ 10 MiB |
+ Max file size to cache | +
ttl |
+ 0 |
+ Entry TTL (0 = no expiry; flush with SIGHUP) | +
| Key | +Default | +Description | +
|---|---|---|
enabled |
+ true |
+ Enable on-the-fly gzip compression | +
min_size |
+ 1024 |
+ Minimum response bytes to compress | +
level |
+ 5 |
+ gzip compression level (1–9) | +
precompressed |
+ true |
+ Serve .gz/.br sidecar files when available | +
| Key | +Default | +Description | +
|---|---|---|
immutable_pattern |
+ — | +Glob for immutable assets (e.g. *.chunk.js) |
+
static_max_age |
+ 3600 |
+ Cache-Control max-age for non-HTML (seconds) | +
html_max_age |
+ 0 |
+ Cache-Control max-age for HTML (seconds) | +
| Key | +Default | +Description | +
|---|---|---|
block_dotfiles |
+ true |
+ Block .env, .git, etc. | +
directory_listing |
+ false |
+ Enable HTML directory index | +
cors_origins |
+ [] |
+ Allowed CORS origins (["*"] for wildcard) | +
csp |
+ default-src 'self' |
+ Content-Security-Policy value | +
referrer_policy |
+ strict-origin-when-cross-origin |
+ Referrer-Policy value | +
permissions_policy |
+ geolocation=(), microphone=(), camera=() |
+ Permissions-Policy value | +
hsts_max_age |
+ 31536000 |
+ HSTS max-age in seconds (HTTPS only; 0 disables) | +
hsts_include_subdomains |
+ false |
+ Add includeSubDomains to HSTS | +
Security Model
++ Defense in depth — every layer adds protection, even before a file is touched. +
+ +6-Step Path Validation
+
+ Every request URL passes through PathSafe before any filesystem access.
+
-
+
- Null byte rejection — prevents C-level path truncation +
-
+ Root symlink resolution —
EvalSymlinkson the root at startup (prevents +/tmp→/private/tmpbypass) +
+ -
+
path.Cleannormalisation — collapses/../,//, trailing + slashes +
+ -
+ Separator-aware prefix check — trailing separator prevents
/rootmatching +/rootsuffix+
+ -
+
EvalSymlinksre-verification — resolves the canonical path; symlinks outside root + returnErrPathTraversal+
+ - Per-segment dotfile blocking — each path component checked for leading
.
+
+ Validated path is injected into the request context — minimizes TOCTOU surface by avoiding redundant + path resolution. +
+Default HTTP Headers
+Set on every response — including 4xx/5xx errors.
+X-Content-Type-OptionsnosniffX-Frame-OptionsSAMEORIGINContent-Security-Policydefault-src 'self'Referrer-Policystrict-origin-when-cross-origin
+ Permissions-Policygeolocation=(), microphone=(), camera=()
+ Strict-Transport-Securitymax-age=31536000 (HTTPS)
+
+ X-Content-Type-Options and X-Frame-Options are unconditional. CSP,
+ Referrer-Policy, Permissions-Policy, and HSTS are configurable defaults.
+
DoS Mitigations
+Timeouts and limits applied to both HTTP and HTTPS servers.
+ReadHeaderTimeout5s (Slowloris)ReadTimeout10sWriteTimeout10sIdleTimeout75s (keep-alive)MaxHeaderBytes8 KiB (hardcoded)Method whitelistGET, HEAD, OPTIONS only
+ All other methods (TRACE, PUT, POST, DELETE, PATCH) receive 405. TRACE-based XST attacks
+ are impossible by design.
+
CORS Policy
+RFC 6454 compliant — origin is never reflected for wildcard.
+Wildcard ["*"]Literal *, never reflected
+ Specific originsExact match + Vary: Origin
+ Preflight204 No ContentAllow-MethodsGET, HEAD, OPTIONSMax-Age86400 (24 hours)
+ Vary: Origin only added for per-origin matching. Disallowed origins still pass through —
+ CORS is enforced by the browser, not the server.
+
TLS Hardening
+Modern ciphers, enforced server preference, automatic HTTPS redirect.
+Min versionTLS 1.2CurvesX25519, P-256Cipher suitesECDHE + AEAD onlyServer preferenceEnforcedHTTP → HTTPSAuto 301 redirectHSTS subdomainsOptional opt-inAES-256-GCM, ChaCha20-Poly1305, AES-128-GCM — no CBC, no RSA key exchange.
+Runtime Resilience
+Multiple layers of protection keep the server stable under adversity.
+-
+
- + Panic recovery middleware — catches panics, logs stack traces, returns 500 with zero info leak + +
- + Graceful shutdown on SIGTERM/SIGINT — drains in-flight requests (configurable timeout, default 15s) + +
- SIGHUP cache flush — purge in-memory cache without downtime +
- Embedded fallback pages — server ships default assets, guards against empty-root scenarios +
- Custom 404 path validation — even the not-found page passes through
PathSafe
+
Ready to serve with confidence?
+Production-ready in under a minute. Minimal dependencies. Security included.
+$ go install
+ github.com/BackendStack21/static-web/cmd/static-web@latest
+
+