# Deployment Guide
eggserve is a hardened static file server intended for local development, internal tools, and controlled environments. Production deployment is defined through explicit profiles — see README.md for the full profile table. This guide covers common deployment patterns.
## Pattern 1: Local-only HTTP
The simplest usage. Serve files on loopback only:
```sh
eggserve --directory public
eggserve 9000 public
```
The server binds to `127.0.0.1:8000` by default. Only local processes can connect. This is the recommended pattern for local development.
## Pattern 2: Reverse proxy TLS
For public-facing deployments, terminate TLS at a reverse proxy and forward to eggserve on loopback:
**Caddy:**
```
example.com {
reverse_proxy 127.0.0.1:8000
}
```
**nginx:**
```nginx
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://127.0.0.1:8000;
}
}
```
Then start eggserve without TLS:
```sh
eggserve --directory public
```
This is the recommended pattern for production deployments. Reverse proxies handle certificate management, renewal, HTTP/2, and other TLS features that eggserve intentionally does not implement.
### Connection metadata behind a reverse proxy
When eggserve runs behind a reverse proxy, connection metadata (`remote_addr`, `local_addr`, `scheme`, `tls`) reflects the **transport peer** — the proxy's address, not the end client's. eggserve does not automatically trust `Forwarded` or `X-Forwarded-*` headers. If you need end-client identity, implement proxy-header validation in your service layer with an explicit allowlist.
### Body handling behind a reverse proxy
eggserve rejects request bodies by default (safe default). When a reverse proxy forwards requests with bodies (e.g., POST, PUT), the runtime enforces body policy before invoking any service code. If the proxy and eggserve disagree on framing (TE+CL conflicts, duplicate Content-Length), the request is rejected with 400 at the origin. When Hyper's HTTP parser normalizes headers (e.g., stripping Content-Length when Transfer-Encoding is present), the rejection occurs if both headers survive parser extraction. When a handler returns without fully consuming the body, the connection is closed to prevent request smuggling through leftover bytes. Reverse proxies should be configured to forward `Content-Length` and `Transfer-Encoding` headers without modification to preserve framing integrity.
### Production profile: unix-reverse-proxy
The reverse-proxy profile is the preferred public deployment. eggserve binds to loopback, the reverse proxy terminates TLS and handles public binding. External qualification evidence collection is pending; the profile remains functional until all gates pass. See README.md for the full specification.
### Production profile: unix-direct-https
Native TLS is functional for small deployments or internal tools where reverse proxy complexity is not warranted. It is limited to HTTP/1.1 with manual certificate management. It is not an edge platform — no ACME, virtual hosting, HTTP/2, or multi-certificate routing. External qualification pending. See README.md for the full specification.
## Pattern 3: Native TLS
eggserve can terminate TLS directly when built with the `tls` feature:
```sh
eggserve --tls-cert cert.pem --tls-key key.pem --directory public
```
See [tls.md](tls.md) for details on the TLS feature, certificate requirements, and limitations.
## Windows deployment
Windows implements handle-relative confinement with parser-level protections rejecting Windows reserved names, ADS syntax, drive prefixes, and backslash in path components. Directory listing is disabled by default. The adversarial qualification suite covers reparse-point denial, namespace normalization, race harnesses, root identity, file validators, ACL/sharing, resource stability, installed artifact parity, and fuzz corpus replay. Two open-descendant root-rename cases are skipped because NTFS rejects that external path operation, so Windows remains functional-only for public deployment.
See README.md for Windows-specific profiles (windows-reverse-proxy, windows-direct-https, windows-functional).
## Binding to all interfaces
To make eggserve accessible from other machines (without a reverse proxy), use `--public`:
```sh
eggserve --public --port 8000 --directory public
```
This binds to `0.0.0.0`. The `--public` flag is required to acknowledge public exposure intent. When binding publicly, consider using a reverse proxy for TLS termination and access control.
## Combining patterns
A common setup for small deployments:
- eggserve on `127.0.0.1:8000` (no TLS, no public exposure)
- Caddy or nginx on `0.0.0.0:443` (TLS termination, access control)
- Optional: WireGuard or Tailscale for private network access without a public endpoint
## Security considerations
- eggserve does **not** manage certificates. You must obtain, install, and renew certificates separately.
- eggserve does **not** implement ACME. Use certbot, Caddy's built-in ACME, or your hosting provider's certificate management.
- For production, always prefer a mature TLS terminator unless eggserve's native TLS is sufficient for your threat model.
- Never expose eggserve directly to the public internet without proper TLS and access control.
- Every production deployment must name a profile from the production profiles table in README.md. No document should claim production support without naming the profile.
- **Directory listing is opt-in and disabled by default.** When enabled with `--directory-listing`, it exposes file names and directory structure. Listing responses are bounded (max 4096 entries, 1 MiB body). Symlink entries are hidden from listings by default. Do not enable directory listing for untrusted content without understanding the information disclosure implications.
- **Connection metadata is transport-peer metadata.** `remote_addr` on the `Request` object reflects the TCP peer address (proxy address when behind a reverse proxy). Do not use it for end-client identification without proxy-header validation.
## Structured Logging
eggserve emits structured operational logs to stderr. Use `--log-format` to select the output mode:
- `--log-format json` — JSON Lines to stderr. One valid JSON object per line with fields: `schema_version`, `severity`, `event`, `timestamp`, `message`, `connection_id`, `request_seq`, `fields`.
- `--log-format text` — Human-readable text to stderr (default). Format: `[severity] event_name: message`. Control characters are sanitized and long fields are truncated.
- `--log-format none` — Disables all operational logs. No structured output is emitted during normal operation.
### Event Categories
| Category | Examples |
|----------|----------|
| Process/config | `process_starting`, `root_initialized`, `listener_ready`, `shutdown_requested` |
| Connection lifecycle | `connection_accepted`, `tls_handshake_success`, `keep_alive_closed` |
| Request/service | `request_completed`, `file_not_found`, `file_denied`, `body_policy_rejection` |
| Operational faults | `listener_transient_error`, `resource_exhaustion`, `blocking_worker_saturation` |
### Privacy
- No absolute filesystem paths in request logs (startup diagnostics only)
- No `Authorization` or `Cookie` headers in logs
- Query strings omitted from request path fields
- Request paths truncated to last component (max 128 chars)
### Stderr Destination
All log output goes to stderr. stdout remains clean for CLI conventions (e.g. piped output, scripted usage).