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

  1. Set the relay base URL env var on the Vercel project so getRelayBaseUrl() returns a value
  2. Verify the relay is alive: curl -fsS "$RELAY_BASE_URL/health" or curl its /rss?url=... endpoint with a known feed
  3. Check Railway logs for the failing /rss request and redeploy the relay if it is down
  4. 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

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)