| name | fastly-wildcard-subdomain-compute-edge |
| description | Fix wildcard subdomain routing for Fastly Compute@Edge services when subdomains resolve
but return 500 "Domain Not Found" or empty responses. Use when: (1) TLS subscription
"issued" but edge serves default certificate (CN=j.sni-644-default), (2) Fastly domain-v1
create succeeds but subdomain returns 500, (3) Static publisher/PublisherServer returns
empty content for subdomain requests, (4) DNS wildcard CNAME resolves to wrong target.
Covers TLS configuration SNI endpoint selection (x.sni vs j.sni vs w.sni), DNS trailing
dot issues, Fastly domain-v1 activation, and reading from KV store directly when
PublisherServer fails for subdomains.
|
| author | Claude Code |
| version | 1.1.0 |
| date | "2026-02-02T00:00:00.000Z" |
Fastly Wildcard Subdomain Routing for Compute@Edge
Problem
Wildcard subdomains (*.example.com) don't work on Fastly Compute@Edge services even after:
- Creating the domain with
fastly domain-v1 create
- Setting up DNS records
- The static publisher working fine for the apex domain
Symptoms include:
- 500 errors with "Fastly error: unknown domain: subdomain.example.com"
- PublisherServer returning empty responses (content-length: 0) for subdomain requests
- TLS subscriptions stuck in "pending" state
- DNS resolving to wrong targets
Context / Trigger Conditions
-
Wrong SNI endpoint: TLS certificate is "issued" but edge serves default certificate
(CN=j.sni-644-default.ssl.fastly.net instead of your domain).
CRITICAL: The TLS configuration name tells you which SNI endpoint to use:
- "HTTP/3 & TLS v1.3 + 0RTT (x.sni)" → DNS to
x.sni.global.fastly.net.
- "HTTP/3 & TLS v1.3 (w.sni)" → DNS to
w.sni.global.fastly.net.
- Default/legacy → DNS to
j.sni.global.fastly.net.
Check your TLS configuration:
FASTLY_KEY=$(fastly profile token) && curl -s -H "Fastly-Key: $FASTLY_KEY" \
"https://api.fastly.com/tls/configurations" | jq '.data[] | {id: .id, name: .attributes.name}'
Verify by connecting directly to the correct endpoint:
echo | openssl s_client -servername subdomain.yourdomain.com \
-connect x.sni.global.fastly.net:443 2>/dev/null | openssl x509 -noout -subject
-
DNS trailing dot issue: CNAME records resolve to target.com.yourdomain.com instead
of target.com because the DNS provider appends the zone name without a trailing dot.
Check with: dig @ns-server *.yourdomain.com CNAME +short
Bad: x.sni.global.fastly.net.yourdomain.com.
Good: x.sni.global.fastly.net.
-
Domain not activated: Fastly domain-v1 shows "activated": false, "verified": false
even after DNS is set up.
Check with:
curl -s -H "Fastly-Key: $(fastly profile token)" \
"https://api.fastly.com/domain-management/v1/domains/DOMAIN_ID" | jq
-
PublisherServer returns empty for subdomains: The @fastly/compute-js-static-publish
PublisherServer returns null/empty responses when the request hostname is a subdomain,
even though it works for the apex domain and edgecompute.app URL.
Solution
Part 1: Fix DNS Configuration
-
Wildcard CNAME with trailing dot (check your TLS config for correct SNI endpoint):
Name: *
Type: CNAME
Value: x.sni.global.fastly.net. <- Use endpoint from your TLS config name (x.sni, w.sni, or j.sni)
-
ACME challenge CNAME with trailing dot (for TLS validation):
Name: _acme-challenge
Type: CNAME
Value: CHALLENGE_VALUE.fastly-validations.com. <- MUST include trailing dot
-
Alternative: Use A records instead of CNAME (avoids trailing dot issues):
Name: *
Type: A
Values: 151.101.1.242, 151.101.65.242, 151.101.129.242, 151.101.193.242
Part 2: Recreate Fastly Domain (if stuck)
If domain shows activated: false even after DNS is correct:
fastly tls-subscription delete --id SUBSCRIPTION_ID --force
fastly domain-v1 delete --domain-id DOMAIN_ID
fastly domain-v1 create --fqdn "*.yourdomain.com" --service-id SERVICE_ID
fastly tls-subscription create --domain "*.yourdomain.com"
Part 3: Fix PublisherServer for Subdomains
The PublisherServer from @fastly/compute-js-static-publish doesn't serve content for
subdomain hostnames. You must read from the KV store directly:
if (subdomain) {
const contentStore = new KVStore('your-content-store-name');
const indexEntry = await contentStore.get('default_index_undefined');
const indexData = await indexEntry.json();
const indexHtmlInfo = indexData['/index.html'];
const contentHash = indexHtmlInfo.key.replace('sha256:', '');
const contentKey = `default_files_sha256_${contentHash}`;
const htmlEntry = await contentStore.get(contentKey);
const html = await htmlEntry.text();
const modifiedHtml = html.replace('<head>', `<head><script>window.SUBDOMAIN_DATA = {...}</script>`);
return new Response(modifiedHtml, {
headers: { 'Content-Type': 'text/html; charset=utf-8' }
});
}
Part 4: Key Format Discovery
The static publisher uses these KV key formats:
- Index:
${publishId}_index_${collectionName} (e.g., default_index_undefined)
- Settings:
${publishId}_settings_${collectionName}
- Files:
${publishId}_files_sha256_${hash}
To discover your actual key names:
curl -s -H "Fastly-Key: $(fastly profile token)" \
"https://api.fastly.com/resources/stores/kv/STORE_ID/keys?limit=50" | jq '.data[]' | grep index
Verification
dig @8.8.8.8 subdomain.yourdomain.com A +short
curl -s -H "Fastly-Key: $(fastly profile token)" \
"https://api.fastly.com/domain-management/v1/domains/DOMAIN_ID" | jq '{activated, verified}'
fastly tls-subscription list | grep yourdomain
curl -sI "https://subdomain.yourdomain.com"
Example
Complete fix for *.divine.space subdomain routing:
if (subdomain && namesStore) {
const entry = await namesStore.get(`name:${subdomain}`);
const contentStore = new KVStore('divine-space-content');
const indexEntry = await contentStore.get('default_index_undefined');
const indexData = await indexEntry.json();
const indexHtmlInfo = indexData['/index.html'];
const contentHash = indexHtmlInfo.key.replace('sha256:', '');
const htmlEntry = await contentStore.get(`default_files_sha256_${contentHash}`);
const html = await htmlEntry.text();
if (entry) {
const data = await entry.json();
const injectedHtml = html.replace('<head>', `<head>
<script>window.__DIVINE_SPACE_USER__ = {
subdomain: "${subdomain}",
pubkey: "${data.pubkey}"
};</script>`);
return new Response(injectedHtml, {
headers: { 'Content-Type': }
});
}
}
Notes
- The
collectionName in KV keys is often undefined (literal string) rather than
default due to how the static publisher is configured. Always check actual keys.
- DNS changes can take up to 3 hours to propagate due to TTL settings.
- Fastly edge cache may serve stale responses—use
fastly purge --all after changes.
- Static assets (/assets/*, .js, .css files) should be served BEFORE subdomain handling
using the normal PublisherServer.
- The wildcard TLS cert (*.domain.com) requires the ACME challenge DNS record to be
correct before it will issue.
References