diff --git a/fern/products/dashboard/pages/domains.mdx b/fern/products/dashboard/pages/domains.mdx
index 2fb777b52e..c07112ae06 100644
--- a/fern/products/dashboard/pages/domains.mdx
+++ b/fern/products/dashboard/pages/domains.mdx
@@ -46,6 +46,11 @@ instances:
Log in to your domain registrar and add the DNS records shown in the Fern Dashboard. The specific records depend on your domain type (subdomain, subpath, or root domain).
+
+
+Subpath hosting needs more than DNS — your infrastructure has to forward requests from the subpath to Fern's origin with the `x-fern-host` header set to your bare domain. Follow the [reverse proxy setup instructions](/learn/docs/preview-publish/reverse-proxy) for your provider (Cloudflare Workers, AWS CloudFront, Netlify, Vercel, Nginx, Akamai, or Caddy). Skip this step for subdomain or root domain hosting.
+
+
Once you've added the DNS records, return to the Fern Dashboard to verify your domain. SSL is automatically provisioned for your domain, but it may take a few minutes to propagate globally.
diff --git a/fern/products/docs/docs.yml b/fern/products/docs/docs.yml
index 41cd6ddac9..34b173b6a6 100644
--- a/fern/products/docs/docs.yml
+++ b/fern/products/docs/docs.yml
@@ -248,6 +248,9 @@ navigation:
path: ./pages/preview-publish/publishing-your-docs.mdx
- page: Setting up your domain
path: ./pages/preview-publish/setting-up-your-domain.mdx
+ - page: Reverse proxy setup
+ path: ./pages/preview-publish/reverse-proxy.mdx
+ slug: reverse-proxy
- page: Multi-source docs
path: ./pages/preview-publish/multi-source.mdx
- section: Customization
diff --git a/fern/products/docs/pages/preview-publish/reverse-proxy.mdx b/fern/products/docs/pages/preview-publish/reverse-proxy.mdx
new file mode 100644
index 0000000000..3ebc444eaf
--- /dev/null
+++ b/fern/products/docs/pages/preview-publish/reverse-proxy.mdx
@@ -0,0 +1,270 @@
+---
+title: Reverse proxy setup
+description: Configure a reverse proxy to serve Fern docs from a subpath on your domain, with provider-specific instructions for routing and caching.
+---
+
+When you host Fern docs on a [subpath](/learn/docs/preview-publish/setting-up-your-domain) like `mydomain.com/docs`, your infrastructure must proxy requests from that path to Fern's origin. Subdomain setups (`docs.mydomain.com`) use a CNAME record instead and don't require a reverse proxy.
+
+## How it works
+
+A working reverse proxy does two things:
+
+1. **Routes requests** from your subpath to Fern's origin at `app.buildwithfern.com`, preserving the original path. The `x-fern-host` header tells Fern which docs site to serve.
+2. **Disables caching** of HTML responses so visitors always receive the current deployment.
+
+Every proxied request needs two headers:
+
+| Header | Value | Purpose |
+|---|---|---|
+| `x-fern-host` | Your bare domain without the subpath (e.g. `mydomain.com`, not `mydomain.com/docs`) | Tells Fern which docs site to serve |
+| `Host` | `app.buildwithfern.com` | Routes the request to Fern's origin |
+
+Fern sets `Cache-Control: public, max-age=0, must-revalidate` on HTML responses, which most providers respect by default. If yours overrides origin cache headers or applies its own time-to-live, explicitly disable caching for the proxied path.
+
+
+Don't cache HTML responses from Fern. Cached HTML references JavaScript and CSS from older deployments — those files no longer exist, so pages fail to load. Static assets (`/_next/static/*`) are served directly by Fern's CDN and don't pass through your proxy.
+
+
+## Set up your provider
+
+
+
+
+
+
+
+
+Create a [Cloudflare Worker](https://developers.cloudflare.com/workers/) that intercepts requests to your docs subpath and proxies them to Fern:
+
+```js
+const SUBPATH = "/docs";
+const FERN_HOST = "mydomain.com";
+
+export default {
+ async fetch(request) {
+ const url = new URL(request.url);
+
+ if (
+ url.pathname !== SUBPATH &&
+ !url.pathname.startsWith(`${SUBPATH}/`)
+ ) {
+ return new Response("Not found", { status: 404 });
+ }
+
+ const upstreamUrl = new URL(request.url);
+ upstreamUrl.protocol = "https:";
+ upstreamUrl.hostname = "app.buildwithfern.com";
+ upstreamUrl.port = "";
+
+ const proxiedRequest = new Request(upstreamUrl, request);
+ proxiedRequest.headers.set("x-fern-host", FERN_HOST);
+
+ return fetch(proxiedRequest);
+ },
+};
+```
+
+Replace `SUBPATH` and `FERN_HOST` with your subpath and bare domain.
+
+
+
+In the Cloudflare dashboard under **Workers Routes**, attach the Worker to a route pattern like `mydomain.com/docs/*`.
+
+
+
+The Worker forwards Fern's `Cache-Control` header, so caching works automatically. In the Cloudflare dashboard, confirm that no Page Rule or Cache Rule sets "Cache Level: Cache Everything" for `/docs*` — if one exists, remove it or override it with "Cache Level: Bypass."
+
+
+
+
+
+
+
+
+
+Add an origin to your CloudFront distribution pointing to `app.buildwithfern.com` (HTTPS only, port 443). Add a custom origin header:
+
+| Header name | Value |
+|---|---|
+| `x-fern-host` | `mydomain.com` |
+
+CloudFront automatically sets the `Host` header to the origin domain.
+
+
+
+Set the behavior to:
+
+- **Origin**: the Fern origin you just created
+- **Cache policy**: `CachingDisabled` (AWS managed policy)
+- **Origin request policy**: `AllViewer` (forwards all headers, query strings, and cookies)
+
+To use a custom cache policy instead of `CachingDisabled`, set min, max, and default TTL to `0` and forward `Host` and `x-fern-host`.
+
+
+
+
+CloudFront ignores `CDN-Cache-Control` and `Surrogate-Control` — only the standard `Cache-Control` header is read. A custom cache policy with a non-zero default TTL caches responses regardless of Fern's `Cache-Control: max-age=0` directive.
+
+
+
+
+
+
+
+
+In `netlify.toml` (or `_redirects`):
+
+```toml netlify.toml
+[[redirects]]
+ from = "/docs/*"
+ to = "https://app.buildwithfern.com/docs/:splat"
+ status = 200
+ force = true
+
+ [redirects.headers]
+ x-fern-host = "mydomain.com"
+```
+
+
+
+
+Netlify rewrites with `status = 200` act as a reverse proxy — the visitor's browser sees your domain while the content comes from Fern. Netlify respects the origin's `Cache-Control` header, so no extra caching configuration is needed.
+
+
+
+
+
+
+
+
+Use a [route with a transform rule](https://vercel.com/changelog/transform-rules-are-now-available-in-vercel-json) to rewrite the path and set `x-fern-host` in one step:
+
+```json vercel.json
+{
+ "$schema": "https://openapi.vercel.sh/vercel.json",
+ "routes": [
+ {
+ "src": "/docs(/.*)?",
+ "dest": "https://app.buildwithfern.com/docs$1",
+ "transforms": [
+ {
+ "type": "request.headers",
+ "op": "set",
+ "target": { "key": "x-fern-host" },
+ "args": "mydomain.com"
+ }
+ ]
+ }
+ ]
+}
+```
+
+
+
+
+Vercel respects the origin's `Cache-Control` header for rewrite destinations, so no extra caching configuration is needed.
+
+
+
+
+
+
+
+
+```nginx
+location /docs {
+ proxy_pass https://app.buildwithfern.com;
+ proxy_set_header Host app.buildwithfern.com;
+ proxy_set_header x-fern-host mydomain.com;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_ssl_server_name on;
+
+ # Prevent encoding issues with Brotli responses
+ proxy_set_header Accept-Encoding "gzip, deflate";
+
+ # Do not cache HTML
+ proxy_no_cache 1;
+ proxy_cache_bypass 1;
+ add_header Cache-Control "public, max-age=0, must-revalidate" always;
+}
+```
+
+
+
+
+Nginx doesn't natively support [Brotli](https://github.com/google/brotli) decompression. The `Accept-Encoding` override above prevents HTTP/2 transfer errors caused by Fern's default Brotli-compressed responses.
+
+
+
+
+
+
+
+
+In a **Site Delivery** or **Ion** property, add a rule matching path `/docs*`:
+
+- **Origin Server**: `app.buildwithfern.com`
+- **Forward Host Header**: Origin Hostname
+
+Add a **Modify Outgoing Request Header** behavior:
+
+| Action | Header name | Value |
+|---|---|---|
+| Add | `x-fern-host` | `mydomain.com` |
+
+
+
+Add a **Caching** behavior to the same rule:
+
+| Setting | Value |
+|---|---|
+| Caching Option | No Store |
+
+Alternatively, set **Honor Origin Cache-Control** to **Yes** so Akamai respects Fern's `Cache-Control: public, max-age=0, must-revalidate` header.
+
+
+
+
+
+
+
+
+
+```caddyfile
+mydomain.com {
+ handle /docs* {
+ reverse_proxy https://app.buildwithfern.com {
+ header_up Host app.buildwithfern.com
+ header_up x-fern-host mydomain.com
+ }
+ }
+}
+```
+
+
+
+
+Caddy doesn't cache responses by default, so no extra caching configuration is needed unless you've explicitly enabled caching with a `cache` directive or external module.
+
+
+
+
+## Verify your setup
+
+Run these checks against your live subpath:
+
+```bash
+# Check that the page loads and x-fern-host is recognized
+curl -sI https://mydomain.com/docs | head -20
+
+# Verify Cache-Control on the HTML response
+curl -sI https://mydomain.com/docs | grep -i cache-control
+# Expected: cache-control: public, max-age=0, must-revalidate
+
+# Verify the page is not cached by your proxy (age should be 0 or absent)
+curl -sI https://mydomain.com/docs | grep -i "^age:"
+```
+
+If the `age` header is present and non-zero, your proxy is serving a cached response. Revisit your provider's configuration to ensure HTML caching is disabled.
diff --git a/fern/products/docs/pages/preview-publish/setting-up-your-domain.mdx b/fern/products/docs/pages/preview-publish/setting-up-your-domain.mdx
index fca3d3fe11..933947a19f 100644
--- a/fern/products/docs/pages/preview-publish/setting-up-your-domain.mdx
+++ b/fern/products/docs/pages/preview-publish/setting-up-your-domain.mdx
@@ -78,7 +78,7 @@ Once Fern has completed your setup, you'll be able to access your documentation
-To host your documentation on a subpath like `mydomain.com/docs`, you need to edit your `docs.yml` configuration and then get provider-specific instructions for setting up the subpath. Common providers include Cloudflare, AWS Route53 and Cloudfront, Netlify, and Vercel.
+To host your documentation on a subpath like `mydomain.com/docs`, you need to edit your `docs.yml` configuration and set up a reverse proxy on your infrastructure.
@@ -108,6 +108,11 @@ instances:
[Here's an example.](https://github.com/fern-api/fern/blob/7d8631c6119787a8aaccb4ba49837e73c985db28/fern/docs.yml#L1-L3)
+
+
+A subpath can't be routed with DNS alone — your infrastructure has to forward requests from `mydomain.com/docs` to Fern's origin with the `x-fern-host` header set to your bare domain. Follow the [reverse proxy setup instructions](/learn/docs/preview-publish/reverse-proxy) for your provider (Cloudflare Workers, AWS CloudFront, Netlify, Vercel, Nginx, Akamai, or Caddy).
+
+
Contact Fern via your dedicated Slack channel or [email](mailto:support@buildwithfern.com) to set up your custom subpath.
@@ -133,7 +138,7 @@ proxy_set_header Accept-Encoding "gzip,deflate";
-To host your documentation on a root domain like `mydomain.com`, you need to edit your `docs.yml` configuration and then get provider-specific instructions for setting up the domain. Common providers include Cloudflare, AWS Route53 and Cloudfront, Netlify, and Vercel.
+To host your documentation on a root domain like `mydomain.com`, you need to edit your `docs.yml` configuration and configure DNS records.