caddy-website/new/features.html
2023-12-08 18:49:07 -05:00

831 lines
No EOL
30 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<title>All features of the Caddy Web Server</title>
{{include "/includes/head.html"}}
<link rel="stylesheet" href="/resources/css/marketing.css">
<link rel="stylesheet" href="/resources/css/features.css">
</head>
<body>
<div class="hero">
{{include "/includes/header.html" "dark-header"}}
<div class="wrapper">
<div class="hero-content">
<h1>
All features
<div class="subheading">
You might want to sit down for this.
</div>
</h1>
</div>
</div>
</div>
<main>
<section class="diagonal up feature">
<div class="wrapper">
<div class="legend">
Features in <span class="nonstandard">this color</span> are provided by available plugins.
</div>
<h2>
Overview
</h2>
<p>
Caddy is essentially a configuration management system that can run various apps like an HTTP server, TLS certificate manager, PKI facilities, and more. It can be extended with plugins known as config modules.
</p>
<p>
Caddy sports a flexible and powerful HTTP reverse proxy, on-line configuration API, and a robust, production-ready static file server, and serves all sites over HTTPS by default with automagic TLS certificates.
</p>
<h3 class="green">Overall program technical specifications</h3>
<div class="feature-list">
<div class="feature-row">
<h4>Language</h4>
<div class="benefits">
The language choice is crucial for a web server. Most servers (NGINX, Apache, HAProxy, etc.) and their dependencies are written in C, which are vulnerable to catastrophic memory safety bugs like Heartbleed. Go programs like Caddy are impervious to a whole class of security vulnerabilities.
</div>
<div class="detail">
Go
</div>
</div>
<div class="feature-row">
<h4>Build artifacts</h4>
<div class="benefits">
Caddy compiles directly to native CPU instructions. There is no interpreter required; and many instructions are architecture-optimized.
</div>
<div class="detail">
Platform-native static binary
</div>
</div>
<div class="feature-row">
<h4>Runtime dependencies</h4>
<div class="benefits">
Caddy is statically compiled. Dynamically-linked applications can easily break in production and may be less secure as shared executable resources are loaded from various places around the system. Generally, Caddy binaries do not necessarily require external libraries &mdash; not even libc.
</div>
<div class="detail">
None
</div>
</div>
<div class="feature-row">
<h4>Compile time</h4>
<div class="benefits">
On consumer hardware, standard Caddy builds compile in just a few seconds. This is crucial for rapid iteration, plugin development, and low-cost deployments.
</div>
<div class="detail">
5 seconds
</div>
</div>
<div class="feature-row">
<h4>Deployment environments</h4>
<div class="benefits">
Caddy can go practically anywhere and be deployed a variety of ways. In general, upgrading is as simple as replacing the binary.
</div>
<ul class="detail">
<li>Command line interface</li>
<li>System service</li>
<li>Containers</li>
<li>Kubernetes</li>
<li>Embedded</li>
</ul>
</div>
<div class="feature-row">
<h4>Supply chain and releases</h4>
<div class="benefits">
Go modules verify the integrity of our dependencies and we cryptographically sign our release artifacts so you know what you can trust.
</div>
<div class="detail">
Cryptographically verified
</div>
</div>
<div class="feature-row">
<h4>Operating systems</h4>
<div class="benefits">
Caddy runs on every major platform for which Go compiles.
</div>
<ul class="detail">
<li>Linux</li>
<li>Windows</li>
<li>macOS</li>
<li>FreeBSD</li>
<li>OpenBSD</li>
<li>NetBSD</li>
<li>Android</li>
</ul>
</div>
<div class="feature-row">
<h4>Microarchitectures</h4>
<div class="benefits">
Run Caddy with native code on numerous CPU platforms.
</div>
<ul class="detail">
<li>x86 (i386, i686)</li>
<li>x86-64 (AMD64)</li>
<li>ARM</li>
<li>ARM 64 (AArch64)</li>
<li>MIPS</li>
<li>MIPS64[LE]</li>
<li>PPC64[LE]</li>
<li>RISCV64</li>
<li>S390X</li>
<li>Apple Silicon (Apple ARM; M1, M2, etc.)</li>
</ul>
</div>
<div class="feature-row">
<h4>Regular expression engine</h4>
<div class="benefits">
Caddy's regular expression language is <a href="https://swtch.com/~rsc/regexp/regexp1.html">based on the Thompson NFA and has numerous performance improvements over PCRE</a> used by other web servers. It guarantees the runtime cost increases linearly instead of exponentially. This is ideal when evaluating untrusted input.
<p>
<a href="https://github.com/google/re2/wiki/Syntax">RE2 Syntax</a>
</p>
</div>
<div class="detail">
RE2
</div>
</div>
<div class="feature-row">
<h4>Concurrency model</h4>
<div class="benefits">
Go's runtime optimizes scheduled CPU time in smarter ways than the operating system can using lightweight user-space threads called goroutines. Caddy easily handles hundreds of thousands of requests per second.
</div>
<div class="detail">
Goroutines (epoll + kqueue)
</div>
</div>
<div class="feature-row">
<h4>Plugin model</h4>
<div class="benefits">
Caddy can be extended by compile-time plugins, which compile as native code, in a way that cannot be broken during deployments or by system upgrades. With no IPC or RPC calls, Caddy extensions perform equally well with native code.
</div>
<div class="detail">
Compile-time static
</div>
</div>
</div>
<h3 class="purple">High-level capabilities</h3>
<div class="feature-list">
<div class="feature-row">
<h4>Configuration changes</h4>
<div class="benefits">
With zero-downtime graceful reloads, Caddy's configuration can be changed while it is running. It's programmable/scriptable for powerful automation.
</div>
<ul class="detail">
<li>RESTful HTTP API</li>
<li>Config files</li>
<li>Secure remote access</li>
<!-- <li>Optimistic concurrency</li> -->
</ul>
</div>
<div class="feature-row">
<h4>App modules</h4>
<div class="benefits">
Top-level configuration structures are called app modules, or Caddy apps. They provide the bulk of Caddy's functionality. Anyone can write app modules, and Caddy comes with several standard apps built-in.
</div>
<ul class="detail">
<li>HTTP</li>
<li>TLS</li>
<li>PKI</li>
<li>Events</li>
<li class="nonstandard">Raw TCP & UDP</li>
<li class="nonstandard">SSH</li>
<li class="nonstandard">PHP</li>
<li class="nonstandard">Dynamic DNS</li>
<li class="nonstandard">Security</li>
<li class="nonstandard">Process supervision</li>
<li class="nonstandard">Profiling</li>
</ul>
</div>
<div class="feature-row">
<h4>Logs</h4>
<div class="benefits">
Caddy's logging can be configured as to format, verbosity, output, and more.
</div>
<ul class="detail">
<li>Leveled</li>
<li>Structured</li>
<li>High efficiency, zero-allocation</li>
</ul>
</div>
<div class="feature-row">
<h4>Storage</h4>
<div class="benefits">
Assets and state, including certificates and OCSP staples, are stored in configurable storage backends. In fact, multiple instances of Caddy configured with the same storage are considered part of a cluster and can coordinate automatically.
</div>
<ul class="detail">
<li>File system</li>
<li>Embedded (in-memory)</li>
<li class="nonstandard">Postgres</li>
<li class="nonstandard">Redis</li>
<li class="nonstandard">Vault</li>
<li class="nonstandard">Consul</li>
</ul>
</div>
</div>
</div>
</section>
<section class="diagonal down dark feature">
<div class="wrapper">
<h2>
Command line interface
</h2>
<p>
Caddy's CLI is not only useful&mdash;it's <i>helpful</i>. While most server CLIs merely run the process and reload config, Caddy's CLI goes the extra lightyear to help make administering your modern web server a breeze.
</p>
<p>
Plugins can register their own subcommands to extend Caddy's CLI.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>Command help</h4>
<div class="benefits">
If you misspell a command or flag, miss an argument, or don't know the subcommand, help text is automatically printed. You can also access overall command help or subcommand help with <code>caddy help</code> or <code>-h</code>.
</div>
<div class="detail">
Built-in, automatic (<code>man</code> pages can also be generated)
</div>
</div>
<div class="feature-row">
<h4>Admin API wrappers</h4>
<div class="benefits">
Several subcommands use administration API endpoints for use with the CLI to help you perform common tasks like loading config from files or stopping the server.
</div>
<ul class="detail">
<li>Adapt config to JSON</li>
<li>Start the server, optionally with config</li>
<li>Gracefully reload configuration</li>
<li>Stop the server</li>
</ul>
</div>
<div class="feature-row">
<h4>Binary utilities</h4>
<div class="benefits">
Since custom builds of Caddy are so common, several commands exist to help you manage and get detailed information about your build.
</div>
<ul class="detail">
<li>Detailed build metadata</li>
<li>List installed config modules</li>
<li>List dependencies</li>
<li>Add and remove plugin packages</li>
<li>Print the version</li>
<li>Upgrade the Caddy binary</li>
</ul>
</div>
<div class="feature-row">
<h4>Configuration utilities</h4>
<div class="benefits">
If you choose to use configuration files, Caddy's CLI helps you manage them.
</div>
<ul class="detail">
<li>Format Caddyfile</li>
<li>Validate configuration</li>
<li>List dependencies</li>
<li>Add and remove plugin packages</li>
<li>Print the version</li>
</ul>
</div>
<div class="feature-row">
<h4>Module utilities</h4>
<div class="benefits">
Modules may register their own subcommands to provide common functionality that can be utilized without a config document.
</div>
<ul class="detail">
<li>Static file server</li>
<li>HTTP reverse proxy</li>
<li>Static HTTP responses (templateable)</li>
<li>Storage import/export (backup/restore)</li>
<li>Hash password for use with HTTP basic auth</li>
<li>Export file browse template</li>
</ul>
</div>
<div class="feature-row">
<h4>Integration utilities</h4>
<div class="benefits">
Several subcommands can help you integrate Caddy into your shell environment.
</div>
<ul class="detail">
<li>Generate shell completion script</li>
<li>Print the environment</li>
<li>Generate <code>man</code> pages</li>
<li>Install Caddy-managed root CA into trust stores</li>
<li>Remove Caddy-managed root CA from trust stores</li>
</ul>
</div>
<div class="feature-row">
<h4>System signals</h4>
<div class="benefits">
Caddy has support for common operating system signals/interrupts, with subtle differences in behavior for each one.
<p>
<a href="/docs/command-line#signals">Signal documentation</a>
</p>
</div>
<ul class="detail">
<li>INT (graceful stop)</li>
<li>QUIT</li>
<li>TERM</li>
</ul>
</div>
<div class="feature-row">
<h4>Exit codes</h4>
<div class="benefits">
Whether Caddy exits successfully or with an error, the <a href="/docs/command-line#exit-codes">exit code</a> can give a hint to your process supervisor or script how to handle that.
</div>
</div>
</div>
</div>
</section>
<section class="feature">
<div class="wrapper">
<h2>
Configuration
</h2>
<p>
We've designed Caddy so that its configuration not only provides access to features, but <i>it IS a feature</i> in and of itself.
</p>
<p>
No more quibbling over which config file format is the best: use whatever you want! Caddy's <a href="/docs/config-adapters">config adapters</a> allow you to use whatever config format you prefer.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>Native config format</h4>
<div class="benefits">
Caddy's native configuration format is ubiquitous: it has tooling in nearly every operating system, platform, programming language, and API ecosystem. Almost all other formats can be translated down into JSON, which balances human readability and programmability. You'll find it a powerful ally of your web server.
</div>
<div class="detail">
JSON
</div>
</div>
<div class="feature-row">
<h4>Config adapters</h4>
<div class="benefits">
You can always write your config in another format and with config adapters, Caddy will implicitly translate it into JSON for you so you can work with what you like.
</div>
<ul class="detail">
<li>Caddyfile</li>
<li class="nonstandard">JSON 5</li>
<li class="nonstandard">JSON-C</li>
<li class="nonstandard">NGINX Conf</li>
<li class="nonstandard">YAML</li>
<li class="nonstandard">CUE</li>
<li class="nonstandard">TOML</li>
<li class="nonstandard">HCL</li>
<li class="nonstandard">Dhall</li>
<li class="nonstandard">MySQL</li>
</ul>
</div>
<div class="feature-row">
<h4>Human-friendly config</h4>
<div class="benefits">
The Caddyfile is most users' favorite way to write their web server config by hand because its syntax is forgiving while also being designed with a structure that makes it easy to read and write. It is translated to JSON automatically.
</div>
<div class="detail">
Caddyfile
</div>
</div>
<div class="feature-row">
<h4>Export</h4>
<div class="benefits">
Caddy's administration API allows you to have runtime access to the current configuration in JSON format with a simple GET request.
</div>
</div>
<div class="feature-row">
<h4>Config API</h4>
<div class="benefits">
Caddy receives its configuration through an API endpoint, which can accept JSON or any other format it has a config adapter for.
</div>
</div>
<div class="feature-row">
<h4>Config files</h4>
<div class="benefits">
If you prefer normal commands to manage configuration, Caddy's CLI wraps the API endpoints for you.
</div>
</div>
</div>
</div>
</section>
<section class="diagonal down gray feature">
<div class="wrapper">
<h2>
HTTP server
</h2>
<p>
Caddy's HTTP server is one-of-a-kind: powerful, extensible, efficient, and modern.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>HTTP versions</h4>
<div class="benefits">
Caddy's HTTP server supports all major versions of HTTP and enables them by default. You can customize exacttly which versions you want to serve.
</div>
<ul class="detail">
<li>HTTP/1.1</li>
<li>HTTP/2</li>
<li>HTTP/2 over cleartext (H2C)</li>
<li>HTTP/3</li>
</ul>
</div>
<div class="feature-row">
<h4>Listen interfaces</h4>
<div class="benefits">
Each HTTP server can listen on one or more sockets and network interfaces. For ports, you can specify specific host interface or all interfaces with just a port. All varieties of unix sockets are also supported.
</div>
<ul class="detail">
<li>TCP</li>
<li>UDP</li>
<li>Unix sockets</li>
</ul>
</div>
<div class="feature-row">
<h4>Listener wrappers</h4>
<div class="benefits">
Listeners can be wrapped by modules that operate at the connection-accept level.
</div>
<ul class="detail">
<li>Redirect HTTP on HTTPS port</li>
<li>PROXY protocol</li>
<li class="nonstandard">Tailscale</li>
</ul>
</div>
<div class="feature-row">
<h4>Timeouts</h4>
<div class="benefits">
Setting timeouts is an important defensive measure for production environments, but must be tuned carefully to accommodate legitimate slow clients with large downloads or uploads.
</div>
<ul class="detail">
<li>Read timeout</li>
<li>Read HTTP header timeout</li>
<li>Write timeout</li>
<li>Idle timeout</li>
<li>TCP keepalive interval</li>
</ul>
</div>
<div class="feature-row">
<h4>Full duplex communication</h4>
<div class="benefits">
Concurrent reading and writing of HTTP/1 is not supported by all clients, but can be enabled for certain clients and applications that require it.
</div>
<ul class="detail">
<li>Configurable for HTTP/1</li>
<li>Default for HTTP/2</li>
</ul>
</div>
</div>
</div>
</section>
<section class="diagonal up dark feature">
<div class="wrapper">
<h2>
Reverse proxy
</h2>
<p>
Caddy has the most flexible general-purpose reverse proxy in the world, featuring advanced request and response handling, dynamic routing, health checking, load balancing, circuit breaking, and more.
</p>
<p>
What makes Caddy's proxy unique is its design. Only the client-facing side of the proxy needs to be HTTP; the transport underlying the roundtrip with the backend can be fulfilled with any protocol!
</p>
<p>
Moreover, our proxy can be programmed with highly dynamic upstreams. That is, the available upstreams can change during in-flight requests! If no backends are available, Caddy can hold onto the request until one is.
</p>
<h3 class="blue">High-level proxy features</h3>
<div class="feature-list">
<div class="feature-row">
<h4>Transports</h4>
<div class="benefits">
Transports are how Caddy gets the response from the backend. Caddy's proxy can be a front for protocols other than HTTP by using alternate transport modules. This allows Caddy to generate HTTP responses from backends that don't even speak HTTP!
</div>
<ul class="detail">
<li>HTTP</li>
<li>FastCGI</li>
<li class="nonstandard">NTLM</li>
</ul>
</div>
<div class="feature-row">
<h4>Load balancing</h4>
<div class="benefits">
Selecting upstreams is a crucial function of any modern reverse proxy. Caddy has a variety of built-in load balancing policies to choose from to suit any production services. Some policies are extremely fast and lightweight; others provide upstream affinity based on properties of the client or request; others strive for even distribution by counting connections or using randomness and weights.
</div>
<ul class="detail">
<li>Random</li>
<li>Random Choose-N</li>
<li>Least connections</li>
<li>Round robin</li>
<li>Weighted round robin</li>
<li>First available</li>
<li>Remote IP hash</li>
<li>Client IP hash</li>
<li>URI hash</li>
<li>Query hash</li>
<li>Header hash</li>
<li>Cookie hash</li>
</ul>
</div>
<div class="feature-row">
<h4>Circuit breaking</h4>
<div class="benefits">
A circuit breaker module can temporarily mark a backend as down before it actually goes down, to keep it up.
</div>
<div class="detail nonstandard">
Latency-based
</div>
</div>
<div class="feature-row">
<h4>Health checking</h4>
<div class="benefits">
Health checks detect when upstreams are unavailable. Passive health checks infer status from actual requests. Active health checks work in the background, out-of-band of client requests.
</div>
<ul class="detail">
<li>Active</li>
<li>Passive</li>
</ul>
</div>
<div class="feature-row">
<h4>Observability</h4>
<div class="benefits">
The admin API exposes an endpoint to retrieve the traffic count and health status of the proxy upstreams.
</div>
</div>
<div class="feature-row">
<h4>Upstream sources</h4>
<div class="benefits">
Caddy can get the list of upstreams in various ways. The most common is to write them into the configuration (static). Other ways are dynamic, by which a list of upstreams are returned for each request (these utilize configurable caching to enhance performance).
</div>
<ul class="detail">
<li>Static</li>
<li>Dynamic: A records</li>
<li>Dynamic: SRV records</li>
<li>Dynamic: Multiple sources combined</li>
</ul>
</div>
<div class="feature-row">
<h4>Streaming</h4>
<div class="benefits">
Responses can be streamed directly to the client, or for better wire performance, buffered slightly and flushed periodically.
</div>
</div>
<div class="feature-row">
<h4>Trusted proxies</h4>
<div class="benefits">
In order to use proxy-related headers like X-Forwarded-For, you can specify a list of IP ranges of proxies you trust. By default Caddy doesn't trust the clients.
</div>
</div>
<div class="feature-row">
<h4>Header manipulation</h4>
<div class="benefits">
Headers can be modified in the request going up to the backend and the response coming back down from the backend.
</div>
<ul class="detail">
<li>Add</li>
<li>Set (overwrite)</li>
<li>Delete</li>
<li>Substring replace</li>
</ul>
</div>
<div class="feature-row">
<h4>Buffering</h4>
<div class="benefits">
The proxy can read the entire body before flushing it. This uses more memory but can be required by some backend applications or clients in some cases.
</div>
<ul class="detail">
<li>Requests</li>
<li>Responses</li>
</ul>
</div>
<div class="feature-row">
<h4>Request rewriting</h4>
<div class="benefits">
Rewriting is a different concern from proxying and is normally handled separately, but sometimes you need to rewrite requests using information from the proxy like the chosen upstream. Caddy's proxy lets you do this.
</div>
</div>
<div class="feature-row">
<h4>Response interception</h4>
<div class="benefits">
By default, Caddy's proxy simply writes responses to the client. However, you can intercept the upstream's response and handle it in other ways. This includes matching only certain responses and invoking a custom handler chain you specify.
</div>
</div>
</div>
<h3 class="green">Active health checks</h3>
<p>
Active health checks assume a backend is down by default until that is confirmed otherwise by a health check.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>HTTP request parameters</h4>
<div class="benefits">
Active health checks are performed against an HTTP endpoint on the upstream. You can customize the parameters for these HTTP requests to work for you.
</div>
<ul class="detail">
<li>Path &amp; query string</li>
<li>Port</li>
<li>Headers</li>
</ul>
</div>
<div class="feature-row">
<h4>Timing</h4>
<div class="benefits">
You can customize the interval at which active health checks are performed.
</div>
</div>
<div class="feature-row">
<h4>Success criteria</h4>
<div class="benefits">
Each active health check can be customized with a set of criteria to determine healthy or unhealthy status.
</div>
<ul class="detail">
<li>Response timeout</li>
<li>HTTP status code</li>
<li>Regular expression match on body</li>
</ul>
</div>
<div class="feature-row">
<h4>Failure safety</h4>
<div class="benefits">
Backends that are experiencing bugs and difficulties may sometimes respond with unexpectedly large response bodies. Caddy lets you limit this to preserve proxy resources.
</div>
<div class="detail">
Limit response size
</div>
</div>
</div>
<h3 class="purple">Passive health checks</h3>
<p>
Passive health checks assume a backend is up by default until failure criteria are met in the course of proxying requests.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>Failure criteria</h4>
<div class="benefits">
All passive health checks count connection failures. In addition, you can set more criteria needed to deem a backend as healthy during a request.
</div>
<ul class="detail">
<li>Concurrent request limit exceeded</li>
<li>HTTP Status</li>
<li>Latency</li>
</ul>
</div>
<div class="feature-row">
<h4>Failure memory</h4>
<div class="benefits">
You can customize how long to remember failures and how many failures need to be in memory to consider a backend to be down.
</div>
</div>
</div>
<h3 class="blue">HTTP transport</h3>
<p>
This is the default transport module. It crafts a proxied HTTP request to obtain an HTTP response from the backend.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>DNS resolvers</h4>
<div class="benefits">
The system resolvers are used by default, but you can specify custom DNS resolvers per proxy handler.
</div>
</div>
<div class="feature-row">
<h4>TLS</h4>
<div class="benefits">
Caddy can be configured to support TLS (formerly known as SSL) to the upstream.
</div>
<ul class="detail">
<li>Custom root CA pool</li>
<li>Client authentication to backend</li>
<li>Custom handshake timeout</li>
<li>Server Name Indicator (SNI)</li>
<li>Renegotiation level</li>
<li>Exempt certain ports from TLS</li>
</ul>
</div>
<div class="feature-row">
<h4>Connection pooling</h4>
<div class="benefits">
Connections to backends are pooled for maximum efficiency and minimal latency.
</div>
<ul class="detail">
<li>HTTP Keep-Alive</li>
<li>Custom probe interval</li>
<li>Maximum idle connections (total and per-host)</li>
<li>Idle connection timeout</li>
</ul>
</div>
<div class="feature-row">
<h4>Compression</h4>
<div class="benefits">
Caddy can compress requests for the roundtrip with the backend.
</div>
<div class="detail">
Gzip
</div>
</div>
<div class="feature-row">
<h4>Connection limit</h4>
<div class="benefits">
You can limit the number of connections per host.
</div>
</div>
<div class="feature-row">
<h4>PROXY Protocol</h4>
<div class="benefits">
The PROXY Protocol v1 and v2 are both supported when connecting to upstreams.
</div>
</div>
<div class="feature-row">
<h4>Timeouts</h4>
<div class="benefits">
Various timeouts can be configured; some have sensible default values.
</div>
<ul class="detail">
<li>Connection (dial)</li>
<li>RFC 6555 fallback</li>
<li>Reading response headers</li>
<li>Expect continue</li>
<li>Read</li>
<li>Write</li>
</ul>
</div>
<div class="feature-row">
<h4>Custom buffer sizes</h4>
<div class="benefits">
Tune the size of read/write buffers if you find that your application performs better with certain settings.
</div>
<ul class="detail">
<li>Read buffers</li>
<li>Write buffers</li>
</ul>
</div>
<div class="feature-row">
<h4>HTTP versions</h4>
<div class="benefits">
Caddy's proxy supports multiple HTTP versions with the backend. By default, HTTP/1.1 and HTTP/2 are supported.
</div>
<ul class="detail">
<li>HTTP/1.1</li>
<li>HTTP/2</li>
<li>H2C (HTTP/2 over cleartext)</li>
</ul>
</div>
</div>
</div>
</section>
<section class="feature">
<div class="wrapper">
<h2>
Static file server
</h2>
<p>
Caddy's file server is the preferred way of serving static files for your website.
</p>
<p>
The way it works is simple: specify a root directory from which to serve the files, then request paths are automatically inferred as file paths relative to that root and the file is sent to the client.
</p>
<div class="feature-list">
<div class="feature-row">
<h4>TODO...</h4>
<div class="benefits">
TODO...
</div>
<div class="detail">
TODO...
</div>
</div>
</div>
</div>
</section>
</main>
{{include "/includes/footer.html"}}
</body>
</html>