-
Notifications
You must be signed in to change notification settings - Fork 1
Configuration
The directive is markdown_for_agents. It accepts an optional inline
argument (a shortcut for root) and an optional block.
Always use the block form for
root. A baremarkdown_for_agents /var/www/siteis parsed by Caddy as a path matcher (/var/www/site), not a positional. See FAQ.
example.com {
root * /var/www/site
markdown_for_agents {
root /var/www/site
}
file_server
}markdown_for_agents {
root /var/www/site
cache_dir /var/cache/md4agents
url_suffix .md
query_param format
cache_size 8192
cache_bytes 268435456
cache_entry_bytes 1048576
cache_ttl 24h
max_body_bytes 8388608
convert_timeout 5s
max_concurrent 16
pregenerate
janitor_interval 1h
allow_authenticated
main_selector article
strip_tags script style noscript nav footer aside
strip_selectors .ads "#cookie-banner"
}| Directive | Default | What it does |
|---|---|---|
root |
— | Static file root. Setting this enables the static-first path: requests resolve to disk before falling through to the next handler. Omit it to force the dynamic capture-and-convert path (for reverse_proxy, templates, etc.). |
cache_dir |
<caddy-data-dir>/md4agents/<hash> |
Where generated *.html.md sidecars are written. Lives outside root; safe to delete at any time. Persist this across restarts for warm cache. |
| Directive | Default | What it does |
|---|---|---|
url_suffix |
.md |
URL suffix that requests Markdown (e.g. /page.md). Set to "" to disable. Stripped before the upstream sees the request. |
query_param |
format |
Query parameter checked for the value md or markdown. Set to "" to disable. Stripped before the upstream sees the request. |
The Accept header is always honored — Accept: text/markdown (with
proper q-value handling vs text/html) triggers conversion regardless of
the above two settings.
| Directive | Default | What it does |
|---|---|---|
cache_size |
4096 |
In-memory LRU entry count. |
cache_bytes |
268435456 (256 MiB) |
Total in-memory cache byte budget. The cache is bounded by entry count and total bytes — whichever fills first triggers eviction. |
cache_entry_bytes |
1048576 (1 MiB) |
Per-entry size cap. Oversized responses are rejected outright (converted-and-served, but not cached). |
cache_ttl |
15m |
TTL for in-memory entries. Use 0 or never to disable TTL eviction entirely (operator must purge manually). Disk sidecars are invalidated via mtime, not TTL. |
max_body_bytes |
4194304 (4 MiB) |
Source HTML size cap (both static disk reads and dynamic captures). Larger responses pass through unconverted. |
| Directive | Default | What it does |
|---|---|---|
convert_timeout |
5s |
Per-conversion timeout. On exceed, returns 503. |
max_concurrent |
max(4, NumCPU) |
Semaphore bounding concurrent conversions (CPU/goroutine ceiling). Excess requests queue. |
main_selector |
— | If set, only this element's subtree is converted. E.g. main_selector article extracts <article>...</article>. |
strip_tags |
script style noscript iframe svg |
Tags removed entirely from output before conversion. |
strip_selectors |
— | Simple tag, .class, #id selectors removed pre-conversion. Quote #id selectors — # is a Caddyfile comment character otherwise. |
| Directive | Default | What it does |
|---|---|---|
pregenerate |
false |
On startup, walk root and warm the cache for every HTML file. Useful for cold-start latency at the cost of provisioning time and RAM. |
janitor_interval |
0 (off) |
Periodic sweep that removes orphaned *.html.md sidecars (i.e., ones whose source HTML no longer exists). |
allow_authenticated |
false |
If true, cache responses for requests carrying Authorization or Cookie headers. Only enable when upstream content is not user-specific — otherwise you'll serve User A's page to User B. |
-
No TTL by default? No — there is a 15-minute TTL. Disk sidecars rely
on source-file
mtime, which is what most operators actually want. -
Authenticated requests bypass cache by default. This is the
conservative-correct choice; flip
allow_authenticatedonly on public content. -
Upstream
Set-Cookie,Cache-Control: private,no-store, or a non-trivialVarymake the response uncacheable even if the request was. The module honors upstream caching hints. -
The cache key for the dynamic path is
path + ?query, so/api/p?id=1and/api/p?id=2don't collide.
Every Markdown response includes:
Content-Type: text/markdown; charset=utf-8
ETag: "<sha256[:16]>"
Vary: Accept
If-None-Match is honored → 304 round-trip. HEAD requests return all
headers (incl. Content-Length) and no body, per RFC 9110 §15.3.
Upstream headers from the dynamic path are whitelist-forwarded:
Cache-Control, Expires, Last-Modified, Content-Language,
Content-Security-Policy, Strict-Transport-Security,
X-Frame-Options, X-Content-Type-Options, Referrer-Policy,
Permissions-Policy. Everything else — notably Set-Cookie, Server,
X-Powered-By — is dropped.
Docs
Reports