The daemon defaults to the foreground. On Unix you can pass -d / daemonize = true for a double-fork, or prefer systemd/launchd/Docker as the supervisor. After bind, user / group drop privileges (Unix). A pid_file is written after a successful bind.
./build/simple-httpd --config /etc/simple-httpd/simple-httpd.conf
# optional: --daemonize --user httpd --group httpd --pid-file /run/simple-httpd.pidSIGINT and SIGTERM stop the accept loop and wait briefly for in-flight connections. SIGPIPE is ignored on Unix.
Exit codes: 0 clean stop after a successful listen, 1 listen/runtime failure, 2 usage or config/validation error.
curl -sS http://127.0.0.1:8080/healthz
# okDefault path is /healthz (health_path). Response is 200 and text/plain body ok\n. Empty health_path disables it. The endpoint skips HTTP Basic and rate limits.
A process that can accept TCP and return ok is up. It does not check disk, TLS expiry, or upstreams.
curl -sS http://127.0.0.1:8080/metricsPrometheus text (metrics_path, default /metrics). Counters and gauges:
| Metric | Meaning |
|---|---|
simple_httpd_requests_total{status="…"} |
Responses by HTTP status |
simple_httpd_requests_sum |
All recorded requests |
simple_httpd_bytes_sent_total |
Response body bytes (not headers) |
simple_httpd_request_duration_milliseconds_sum / _count |
Handling time |
simple_httpd_connections_accepted_total |
Accepted clients |
simple_httpd_connections_rejected_total |
Rejected at max_connections |
simple_httpd_connections_active |
In-flight connections (gauge) |
simple_httpd_queue_depth |
Accepted fds waiting for a worker (gauge) |
simple_httpd_cache_hits_total / cache_misses_total |
File-body cache |
Also skipped by auth and the rate limiter. Scrape from a private network.
Example Prometheus scrape:
scrape_configs:
- job_name: simple-httpd
static_configs:
- targets: ["127.0.0.1:8080"]
metrics_path: /metricsSNMP is not implemented. Use Prometheus (or scrape /status).
curl -sS http://127.0.0.1:8080/statusJSON snapshot (status_path, default /status): version, uptime start, request/connection/cache counters, worker and max connection settings. Read-only. Skips auth and rate limits like health/metrics. Empty status_path disables it.
When admin_path is set (and auth_basic_file is configured):
curl -u alice:secret http://127.0.0.1:8080/admin/config
curl -u alice:secret -X POST http://127.0.0.1:8080/admin/reload/admin/reload requests the same soft reload as SIGHUP. Bind to a private interface or put the admin path behind a network ACL; Basic auth is not enough alone on the public internet.
| Stream | Default | Content |
|---|---|---|
error_log |
stderr (-) |
Start/stop, bind errors, TLS handshake failures, reload |
access_log |
stdout (-) |
Combined-style lines, GMT date |
security_log |
stderr (with errors) | ip_denied, auth_failed, rate/connection limits, admin actions |
127.0.0.1 - - [Sun, 30 Aug 2026 12:00:00 GMT] "GET / HTTP/1.1" 200 1234 "-" "curl/8.0"
log_level is debug | info | warn | error. Put files under /var/log/simple-httpd/ in production and rotate them (see deployment/logrotate.d/).
SIGHUP reloads the config file that was used at start (--config or the discovered path). Soft keys apply live: document roots / vhosts, headers, rate limits, IP allow/deny, connection limits, rewrites, proxy/CGI/SSI/mime, compression, file-cache knobs, log paths/level (including security_log), health/metrics/status/admin paths, tcp_nodelay / sendfile, auth file.
These still need a restart: listen_address, listen_port, ssl_cert / ssl_key / tls_min_version, worker_threads, user / group / daemonize / pid_file.
kill -HUP $(pidof simple-httpd)
# or: systemctl reload simple-httpd # if the unit sends SIGHUPIf no config file was loaded (CLI-only defaults), SIGHUP is ignored with a warning.
Accept hands sockets to a worker_threads pool, gated by max_connections. At capacity, new clients are rejected (and counted) instead of blocking accept. tcp_nodelay (default on) reduces latency for small responses. sendfile (default on) uses kernel sendfile for cleartext static bodies on Linux/macOS; TLS and Windows fall back to a thread-local userspace buffer.
The process is stateless. HA is multi-instance + an external load balancer:
- Run N copies of
simple-httpd(same or synced document root). - Put a load balancer / reverse proxy in front for TLS termination or distribution.
- Point health checks at
/healthz. - Proxy backend failover inside one process is round-robin + connect retry (
proxykey from 0.6.0).
There is no clustering protocol, shared memory, or data replication inside the daemon. Replicate files with rsync/nfs/object storage as you prefer.
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple-httpd
spec:
replicas: 3
selector:
matchLabels: { app: simple-httpd }
template:
metadata:
labels: { app: simple-httpd }
spec:
containers:
- name: httpd
image: your.registry/simple-httpd:0.8.0
ports:
- containerPort: 8080
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
volumeMounts:
- name: www
mountPath: /var/www/html
- name: conf
mountPath: /etc/simple-httpd
volumes:
- name: www
persistentVolumeClaim: { claimName: www }
- name: conf
configMap: { name: simple-httpd-conf }
---
apiVersion: v1
kind: Service
metadata:
name: simple-httpd
spec:
selector: { app: simple-httpd }
ports:
- port: 80
targetPort: 8080Scale with the Deployment replica count. Terminate TLS at the Ingress or keep PEM mounts and ssl_cert / ssl_key in the pod.
Back up:
- The config file and htpasswd
- TLS material
- The document root (the daemon does not mutate it)
Restore: install the binary, restore those three trees, start the process. Logs are expendable if you ship them elsewhere. There is no built-in backup command.
Templates live in deployment/. They are examples: edit User, ExecStart, and config paths.
Linux sketch:
[Service]
ExecStart=/usr/local/bin/simple-httpd --config /etc/simple-httpd/simple-httpd.conf
Restart=on-failure
KillSignal=SIGTERM
# Optional: ExecReload=/bin/kill -HUP $MAINPIDThe process does not notify systemd (Type=notify) and does not drop to a dedicated user by itself — set User= in the unit after the document root and logs are writable by that user. Binding port 80/443 as non-root needs CAP_NET_BIND_SERVICE or a proxy in front.
Docker: deployment/examples/docker/README.md.