Skip to content
mhupfauer edited this page May 31, 2026 · 1 revision

FAQ / Gotchas

Caddyfile path-matcher gotcha

markdown_for_agents /var/www/site     # WRONG — silently broken
markdown_for_agents { root /var/www/site }   # right

Any first argument starting with / is consumed by Caddy as a path matcher (/var/www/site), not as a positional argument. The directive would only fire for requests to literally /var/www/site, with an empty root. The module won't error — it'll just never run.

Symptom: you curl ... -H "Accept: text/markdown" and get HTML back with no Vary: Accept, no ETag. Fix is always to use the block form.

Authorization placement

The static-first path serves matched files directly without calling the next handler. That means any basicauth, forward_auth, jwtauth, or similar middleware that comes after markdown_for_agents in the Caddyfile chain will not run for Markdown responses.

In practice Caddy automatically orders this directive immediately before file_server, and the default placement of auth middleware (which is before file_server) is safe. Auth runs first; Markdown serving runs after. ✓

You'd only have an ordering problem if:

  • You explicitly put auth in a route block after markdown_for_agents
  • You use a handle block with non-default ordering
  • You apply path matchers that only target *.html (also match the URL suffix you've configured for Markdown, e.g. path *.html *.md)

When in doubt, wrap both in one handle:

handle {
    basicauth { ... }
    markdown_for_agents { root /srv }
    file_server
}

"My URL /foo.md returns 404 but /foo works"

Check that the matched route includes the directive. A common mistake:

example.com {
    @html path *.html
    route @html {
        markdown_for_agents { root /srv }
        file_server
    }
}

/foo.md doesn't match @html, so it falls through to nothing. Add *.md to the matcher:

@html path *.html *.md

"My JSON/CSS endpoint started returning Markdown"

It shouldn't — the module only converts responses with Content-Type: text/html. If you're seeing this, you probably have a backend that mislabels JSON as HTML. Check the upstream response headers.

"ETag has a -gzip suffix in production"

That's Cloudflare (or any compressing reverse-proxy) appending -gzip to indicate the entity body was transformed by transparent compression. The strong ETag this module emits is still the source of truth; CF strips the suffix on the If-None-Match comparison server-side.

"Cold start is slow"

Two knobs:

  1. pregenerate — warm the cache at startup (paid at provision time)
  2. Persistent cache_dir — survives restarts, so a deploy with the same content has a warm sidecar cache from the first request

Combining both is fine.

"I get 503s under load"

convert_timeout (default 5s) returned. You can:

  • Raise convert_timeout if your HTML is genuinely large
  • Raise max_concurrent if conversions queue
  • Lower max_body_bytes to short-circuit converting giant pages

The 503 is intentional — the alternative is unbounded goroutine growth under thundering-herd conditions.

"Why isn't my Authorization-bearing request cached?"

That's a feature, not a bug. The shared cache is, well, shared, so authenticated responses are bypassed by default. If your content is genuinely identical for all users at this URL, set allow_authenticated. Otherwise, leave the safe default alone.

"Where does the disk sidecar actually live?"

Default: <caddy-data-dir>/md4agents/<sha256-prefix-of-root>/. The SHA-prefix segment avoids ever writing the original path basename into the data dir (information-leak hygiene). Override with cache_dir.

"Can I run two instances on different paths in the same site?"

Yes, with explicit routes:

example.com {
    handle /docs/* {
        markdown_for_agents { root /srv/docs main_selector article }
        file_server { root /srv/docs }
    }
    handle /blog/* {
        markdown_for_agents { root /srv/blog main_selector .post }
        file_server { root /srv/blog }
    }
}

Each instance has independent caches, configs, and selectors. The cache dirs are derived from root so they don't collide.

"Can I disable caching entirely?"

markdown_for_agents {
    cache_size 0
    cache_ttl  never
}

…but you really shouldn't. The cache is the entire performance story. A cache_size 0 config converts every request from scratch.

"Does it work with HTTP/3?"

Yes. The module is transport-agnostic — it operates on the request and response at the handler level. Caddy's QUIC/H3 listener is unaffected.

"Does the conversion preserve code blocks / tables / etc.?"

Yes — the converter is html-to-markdown/v2 which handles tables, fenced code (<pre><code>```), inline code, blockquotes, ordered/unordered lists, footnotes via <sup>, definition lists, and basic GFM extensions. Things it does not handle: math (MathML/KaTeX rendered output), embedded SVG (stripped by default via strip_tags), highly custom shortcode renderings.

"How do I report a security issue?"

Use GitHub Security Advisories (private): https://github.com/mhupfauer/caddy-md4agents/security/advisories/new. Don't open a public issue for security findings.

See also