koala73/worldmonitor · error
Railway relay unavailable for relay-only domain
Error message
Railway relay unavailable for relay-only domain: ${hostname} What it means
Hosts in RELAY_ONLY_DOMAINS (known to block Vercel edge IPs, e.g. www.who.int, kyivindependent.com) skip the direct fetch entirely and must be fetched through the Railway relay. This error means fetchViaRailway returned null — getRelayBaseUrl() is unset (it returns null with no env value) or the relay leg produced no response — so for these hosts no fetch path remains.
Solutions
- Set the relay base URL env var on the Vercel project so getRelayBaseUrl() returns a value
- Verify the relay is alive: curl -fsS "$RELAY_BASE_URL/health" or curl its /rss?url=... endpoint with a known feed
- Check Railway logs for the failing /rss request and redeploy the relay if it is down
- Only if the host no longer blocks Vercel IPs, remove it from RELAY_ONLY_DOMAINS so the direct path with relay fallback is used
Example fix
# before — relay env unset, request to a relay-only host fails fast GET /api/rss-proxy?url=https://www.who.int/rss-feeds/news-xml.xml # 500: Railway relay unavailable for relay-only domain: www.who.int # after — configure and verify the relay, then retry $ vercel env set RELAY_BASE_URL https://<railway-service>.up.railway.app # then redeploy $ curl -fsS "$RELAY_BASE_URL/health" $ curl 'https://<edge>/api/rss-proxy?url=https%3A%2F%2Fwww.who.int%2Frss-feeds%2Fnews-xml.xml'
Defensive patterns
Strategy: validation
Validate before calling
// Before requesting a relay-only feed, confirm the relay is configured and alive
const RELAY_ONLY = new Set(['www.who.int', 'kyivindependent.com', /* ... */]);
async function relayReady() {
const base = process.env.RELAY_BASE_URL; // whatever getRelayBaseUrl() reads
if (!base) return false;
try { return (await fetch(`${base}/health`)).ok; } catch { return false; }
}
if (RELAY_ONLY.has(host) && !(await relayReady())) {
return serviceUnavailable('rss relay down'); // fail fast with a clear signal
} Try / catch
try { xml = await fetchFeedViaProxy(feedUrl); }
catch (err) {
if (/Railway relay unavailable/.test(err.message)) {
// infrastructure outage: surface 503-style degradation, queue retry — same params will keep failing
return markDegraded('rss-relay');
}
throw err;
} Prevention
- Monitor the Railway relay with a health check and alert before it matters
- Include the relay env var in deployment checklists and preview-environment templates
- Track per-host success rates: relay-only hosts failing while direct hosts succeed points at the relay, not the origins
When it happens
Trigger: The Edge deployment lacks the relay base URL env var, so getRelayBaseUrl() returns null and the throw is immediate; the Railway relay service is down, sleeping, or undeployed; the relay request for that feed failed before returning a response.
Common situations: Deploying Edge functions without the relay env var; renamed relay configuration keys; Railway service crashed or sleeping; local development where the private relay is unreachable.
Related errors
AI-assisted analysis of koala73/worldmonitor@e586b8b4b8 (2026-08-21).
Data as JSON: /api/errors/9cf9daaea48fdd42.
Report an issue: GitHub.
Appendix: source
Thrown at api/rss-proxy.js:337
if (redirectCount === MAX_DIRECT_REDIRECTS) {
throw new RssProxyPolicyError('Too many redirects', 502);
}
const redirectUrl = new URL(location, currentUrl.href);
assertAllowedRedirect(redirectUrl);
currentUrl = redirectUrl;
}
};
let response;
let usedRelay = false;
if (isRelayOnly) {
// Skip direct fetch entirely — these domains block Vercel IPs
response = await fetchViaRailway(feedUrl, timeout);
usedRelay = !!response;
if (!response) throw new Error(`Railway relay unavailable for relay-only domain: ${hostname}`);
} else {
try {
response = await fetchDirect();
} catch (directError) {
if (directError instanceof RssProxyPolicyError) throw directError;
// A throwing relay leg here must not replace directError — a null or
// non-ok relay response already falls through to it below, so a thrown
// relay error should too, rather than becoming the reported failure.
let relayResponse = null;
try {
relayResponse = await fetchViaRailway(feedUrl, timeout);
} catch (relayError) {
console.error('RSS proxy relay fallback error:', feedUrl, relayError instanceof Error ? relayError.message : String(relayError));
}
response = relayResponse;
usedRelay = !!response;
if (!response) throw directError;
}View on GitHub (pinned to e586b8b4b8)