Serve previews on their own hostnames
Last updated Oct 12, 2026
Serve each sandbox preview from its own hostname, such as ada.example-previews.com. Code in a preview runs in the visitor's browser, and each preview hostname is a separate origin. Code in one preview cannot read your application pages or the storage of another preview. Paths reach the server unchanged, so applications do not need a base path.
<dl>
<dt>.example-previews.comhttps://ada.example-previews.com/app/</dt> <dd>A wildcard DNS record and the route <code>.example-previews.com/</code> send every preview hostname to your Worker.</dd>
<dt>adahttps://ada.example-previews.com/app/</dt> <dd>Your Worker reads the sandbox name and calls <code>getByName("ada")</code>. A name that is not a valid DNS label gets a <code>404</code>.</dd>
<dt>/app/https://ada.example-previews.com/app/</dt> <dd>The path reaches the web server in the container unchanged, with the preview hostname in the <code>Host</code> header.</dd></dl>
Prerequisites
- A Durable Object whose
fetch()handler forwards requests to a web server in its container. To build one, refer to Preview a web application. - A domain for previews on Cloudflare, such as
example-previews.com, with Cloudflare managing its DNS. Use a domain that your application does not use.
Route preview hostnames to your Worker
- In the DNS settings of the preview domain, add a proxied wildcard record. For example, add an
AAAArecord with the name*and the content100::, with the proxy status Proxied.
The record lets Cloudflare receive requests for every preview hostname. The Worker route handles those requests, so Cloudflare never contacts the address in the record.
2. In wrangler.jsonc, add the preview domain as a variable and route its subdomains to your Worker. Replace example-previews.com with your preview domain:
jsonc
{
"vars": {
"PREVIEW_DOMAIN": "example-previews.com",
},
"routes": [
{
"pattern": "*.example-previews.com/*",
"zone_name": "example-previews.com",
},
],
"workers_dev": true,
}
```toml workers_dev = true
[vars] PREVIEW_DOMAIN = "example-previews.com"
[[routes]] pattern = ".example-previews.com/" zone_name = "example-previews.com" ```
Wrangler turns off the workers.dev URL when routes is set. workers_dev: true keeps it for requests that manage previews.
3. Generate types for the new variable:npmyarnpnpm
npx wrangler types
yarn wrangler types
pnpm wrangler types
- In the
fetch()handler of your Worker, send each preview hostname to the Durable Object with the same name:
src/index.jsjs
``js
export default {
async fetch(request, env) {
const url = new URL(request.url);
const suffix =.${env.PREVIEW_DOMAIN}`;
// A preview hostname reaches only the sandbox with the same name.
if (url.hostname.endsWith(suffix)) {
const name = url.hostname.slice(0, -suffix.length);
// Accept only sandbox names that are valid DNS labels
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name)) {
return new Response("Not found", { status: 404 });
}
// Forward with the `http:` scheme, because `getTcpPort().fetch()`
// does not accept `https:` URLs
url.protocol = "http:";
return env.MY_CONTAINER.getByName(name).fetch(new Request(url, request));
}
// Other hostnames manage previews.
return new Response("Not found", { status: 404 });
},
}; ```
src/index.tsts
``ts
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
const suffix =.${env.PREVIEW_DOMAIN}`;
// A preview hostname reaches only the sandbox with the same name.
if (url.hostname.endsWith(suffix)) {
const name = url.hostname.slice(0, -suffix.length);
// Accept only sandbox names that are valid DNS labels
if (!/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(name)) {
return new Response("Not found", { status: 404 });
}
// Forward with the `http:` scheme, because `getTcpPort().fetch()`
// does not accept `https:` URLs
url.protocol = "http:";
return env.MY_CONTAINER.getByName(name).fetch(new Request(url, request));
}
// Other hostnames manage previews.
return new Response("Not found", { status: 404 });
},
} satisfies ExportedHandler
The server receives the full path and the preview hostname in the Host header.
Every request to a preview hostname goes to the preview. Keep routes that manage previews, such as stopping one, on other hostnames, so code in a preview cannot call them from its own origin. Authenticate preview visitors on the preview hostname, and do not send your application session cookie there. 5. Deploy your Worker:npmyarnpnpm
npx wrangler deploy
yarn wrangler deploy
pnpm wrangler deploy
- Open the preview named
adain your browser, and store a value in its browser console. Replaceexample-previews.comwith your preview domain:
txt
https://ada.example-previews.com/
js
localStorage.setItem("name", "ada");
- Open the preview named
grace, and read the value in its browser console:
txt
https://grace.example-previews.com/
js
localStorage.getItem("name");
The console prints null. Each preview hostname is a separate origin, so grace cannot read the storage of ada.
Serve previews under a subdomain
To serve previews under a subdomain of a domain you already use, such as previews.example.com, name the wildcard record and the route after that subdomain:
- In the DNS settings of
example.com, add a proxiedAAAArecord with the name*.previewsand the content100::. A wildcard record named*does not apply belowpreviews.example.comif that hostname has its own record, and it also matches every other unused subdomain ofexample.com. A record named*.previewsmatches only preview hostnames. - In
wrangler.jsonc, setPREVIEW_DOMAINto the subdomain and route its subdomains to your Worker:
jsonc
{
"vars": {
"PREVIEW_DOMAIN": "previews.example.com",
},
"routes": [
{
"pattern": "*.previews.example.com/*",
"zone_name": "example.com",
},
],
"workers_dev": true,
}
```toml workers_dev = true
[vars] PREVIEW_DOMAIN = "previews.example.com"
[[routes]] pattern = ".previews.example.com/" zone_name = "example.com" ```
Certificates for deeper hostnames
Universal SSL covers a domain and its first-level subdomains, such as ada.example-previews.com. It does not cover deeper hostnames such as ada.previews.example.com. To serve previews under a subdomain, add Total TLS or an advanced certificate for it. Without one, browsers show a certificate error on preview hostnames.
Previews under a domain that your application also uses share cookies with your application. Browsers send a cookie set with Domain=example.com to every subdomain, including every preview under previews.example.com.
Set cookies on preview hostnames
Code in one preview can set a cookie with Domain=example-previews.com, and every other preview then receives that cookie. Set cookies on preview hostnames without a Domain attribute, and do not trust a cookie because it arrived on a preview hostname.
Troubleshooting
The server in the container uses HTTPS: Serve plain HTTP from the container. Cloudflare handles TLS for the preview hostname, and your Worker forwards each request to the container over HTTP. A server that only accepts HTTPS, such as one started with https.createServer() in Node.js or with a framework's HTTPS flag, cannot handle these requests. Start the server with HTTP instead.
Related resources
- Preview a web application
- Preview workspace example ↗︎: a deployable Worker that runs a Vite development server on a separate hostname for each sandbox.
- Sandbox security
- Routes
Was this helpful?
YesNo
On this page
{"@context":"https://schema.org","@type":"TechArticle","@id":"https://developers.cloudflare.com/sandbox/previews/serve-previews-on-their-own-hostnames/#page","headline":"Serve previews on their own hostnames","description":"Give each sandbox preview its own hostname, so code in one preview cannot use your application cookies or the storage of another preview.","url":"https://developers.cloudflare.com/sandbox/previews/serve-previews-on-their-own-hostnames/","inLanguage":"en","image":"https://developers.cloudflare.com/sandbox/previews/serve-previews-on-their-own-hostnames/og.png?v=052e5363148687ab","dateModified":"2026-10-12","publisher":{"@type":"Organization","name":"Cloudflare","description":"One platform for your apps, agents, and workforce. Build, secure, and scale without managing infrastructure","url":"https://www.cloudflare.com/","sameAs":["https://github.com/cloudflare","https://www.linkedin.com/company/cloudflare","https://x.com/cloudflare"],"logo":{"@type":"ImageObject","url":"https://developers.cloudflare.com/logo.svg"},"address":{"@type":"PostalAddress","streetAddress":"101 Townsend St","addressLocality":"San Francisco","addressRegion":"CA","postalCode":"94107","addressCountry":"US"},"contactPoint":[{"@type":"ContactPoint","contactType":"Customer Support","url":"https://support.cloudflare.com/","availableLanguage":["English"]},{"@type":"ContactPoint","contactType":"Sales","url":"https://www.cloudflare.com/contact/","availableLanguage":["English"]}]},"isPartOf":{"@type":"WebSite","@id":"https://developers.cloudflare.com/#website","name":"Cloudflare Docs","url":"https://developers.cloudflare.com/"}}
description: Add credentials in your Worker so the sandbox never holds them, and decide which services code in a sandbox can reach. title: Credentials and network access image: https://developers.cloudflare.com/sandbox/network/og.png?v=8280ee4acbda7ce9
Documentation Index
Fetch the complete documentation index at: https://developers.cloudflare.com/sandbox/llms.txt
Use this file to discover all available pages before exploring further.
Docs