How can I support dynamic custom subdomains (CNAMEs) pointing to my Railway app?
manojnaidu619
PROOP

5 months ago

I’m building a multi-tenant SaaS app, and I need help figuring out how to handle dynamic custom subdomains.

Here’s the situation:

  • My main web app is hosted on a custom domain connected to Railway, for example:

    app.myapp.com
    
  • I’d like my users to be able to connect their own subdomains to it, like:

    dashboard.customer.com → CNAME → app.myapp.com
    
  • When someone visits dashboard.customer.com, I want Railway to serve the same app (my backend detects the tenant based on the Host header).

Right now, if I try this, I get the “Train has not arrived at the station” message — which I understand means Railway doesn’t recognize the domain until it’s manually added under Settings → Networking → Custom Domains.

That works for testing, but in production this won’t scale. I need a setup where customers can connect their own subdomains automatically (like other SaaS platforms that use wildcard or dynamic domain mapping).

So my questions are:

  1. Does Railway support dynamic custom domains - so any CNAME pointing to my base domain is accepted automatically?

  2. If not, is there an API or programmatic way to register new custom domains from my app when a customer adds theirs?

I’m basically trying to let customers point subdomain.theirdomain.comapp.myapp.com and have it just work, without manually adding every new domain inside Railway.

Thanks so much for any clarification or suggestions! pray emoji

Solved$30 Bounty

Pinned Solution

manojnaidu619
PROOP

22 days ago

Hello, This is a very late follow-up :)

I ended up solving this a while back and completely forgot to share the solution. Posting it now in case it helps others.

Also, shoutout to the previous reply that pointed me in the right direction (Cloudflare), and my solution is very similar in spirit.

white_check_mark emoji The Approach That Worked for Me

Instead of managing domains in Railway, I moved all domain handling to Cloudflare.

customer-domain.com

   → (CNAME)

proxy.yourapp.com  [Cloudflare]

   → Worker (intercepts + rewrites request)

myapp.com   [Railway - only domain configured]

gear emoji How It Works

1. User adds a custom domain

- When a user adds their domain in my app, I call:

  Cloudflare Custom Hostnames API

- This attaches the domain to my Cloudflare zone

- Cloudflare automatically provisions SSL

2. Attach a Worker to the zone

- I added a Cloudflare Worker

- Route is set to: */*

- This is important - it ensures ALL incoming requests are intercepted

3. Worker rewrites the request

- The Worker:

  - Takes the incoming request (e.g. customer-domain.com)

  - Rewrites it to my Railway domain (myapp.com)

  - Preserves the original host in headers

4. Railway only sees ONE domain

- Railway only knows about: myapp.com

- So:

  - No need to register customer domains in Railway

  - No domain limits

  - No manual intervention

bulb emoji Worker Code Preview

export default {

  async fetch(request) {

    const url = new URL(request.url)

    const originalUrl = request.url

    const originalHost = url.host

    const targetHost = "myapp.com"

    url.hostname = targetHost

    url.protocol = "https:"

    const headers = new Headers(request.headers)

    // Preserve original request info

    headers.set("X-Original-URL", originalUrl)

    headers.set("X-Original-Host", originalHost)

    // Override for upstream (Railway)

    headers.set("Host", targetHost)

    headers.set("Origin", https://${targetHost})

    headers.set("X-Forwarded-Host", targetHost)

    headers.set("X-Forwarded-Proto", "https")

    return fetch(url.toString(), {

      method: request.method,

      headers,

      body: request.body,

      redirect: "manual",

    })

  }

}

This setup has been working well for me and completely avoids the limitations on Railway.

Hope this helps +1 emoji

9 Replies

Railway
BOT

5 months ago

Hey there! We've found the following might help you get unblocked faster:

If you find the answer from one of these, please let us know by solving the thread!


manojnaidu619
PROOP

4 months ago

Any update on this please? I'm stuck.


callmeal3x
PRO

4 months ago

Hey! I found out your question was interesting, so here is my answer :

Option 1: Wildcard Subdomains (What I Use)

Just add *.myapp.com in Railway → Settings → Custom Domains

DNS Setup:

*.myapp.com  →  CNAME  →  <your-railway-domain>.up.railway.app
*.myapp.com  →  CNAME  →  authorize.railwaydns.net

Important: If you're using Cloudflare, make sure authorize.railwaydns.net is DNS-only (gray cloud, not proxied).

In my app:

const host = req.headers.host; // "customer1.myapp.com"
const tenant = host.split('.')[0]; // "customer1"

That's it! Now customer1.myapp.com, customer2.myapp.com, etc. all work without touching Railway again.

Option 2: True Custom Domains (Their Own Domain)

If customers want dashboard.customer.com pointing to your app, you'll need to use Railway's API:

const addCustomDomain = async (domain) => {
  await fetch('https://backboard.railway.app/graphql/v2', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.RAILWAY_API_TOKEN}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      query: `mutation customDomainCreate($input: CustomDomainCreateInput!) {
        customDomainCreate(input: $input) { id domain status }
      }`,
      variables: {
        input: {
          domain,
          serviceId: process.env.RAILWAY_SERVICE_ID,
          environmentId: process.env.RAILWAY_ENVIRONMENT_ID
        }
      }
    })
  });
};

The flow: customer enters their domain → my backend calls Railway API → customer adds the CNAME → Railway handles SSL automatically.

My recommendation: Start with wildcards. It's zero maintenance and scales perfectly. Only add the API integration if customers really need their own branded domains.

Check out the docs here: https://docs.railway.com/guides/public-networking#wildcard-domains

Hope this helps! pray emoji


callmeal3x

Hey! I found out your question was interesting, so here is my answer :Option 1: Wildcard Subdomains (What I Use)Just add *.myapp.com in Railway → Settings → Custom DomainsDNS Setup:*.myapp.com → CNAME → <your-railway-domain>.up.railway.app *.myapp.com → CNAME → authorize.railwaydns.netImportant: If you're using Cloudflare, make sure authorize.railwaydns.net is DNS-only (gray cloud, not proxied).In my app:const host = req.headers.host; // "customer1.myapp.com" const tenant = host.split('.')[0]; // "customer1"That's it! Now customer1.myapp.com, customer2.myapp.com, etc. all work without touching Railway again.Option 2: True Custom Domains (Their Own Domain)If customers want dashboard.customer.com pointing to your app, you'll need to use Railway's API:const addCustomDomain = async (domain) => { await fetch('https://backboard.railway.app/graphql/v2', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.RAILWAY_API_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: `mutation customDomainCreate($input: CustomDomainCreateInput!) { customDomainCreate(input: $input) { id domain status } }`, variables: { input: { domain, serviceId: process.env.RAILWAY_SERVICE_ID, environmentId: process.env.RAILWAY_ENVIRONMENT_ID } } }) }); };The flow: customer enters their domain → my backend calls Railway API → customer adds the CNAME → Railway handles SSL automatically.My recommendation: Start with wildcards. It's zero maintenance and scales perfectly. Only add the API integration if customers really need their own branded domains.Check out the docs here: https://docs.railway.com/guides/public-networking#wildcard-domainsHope this helps!

manojnaidu619
PROOP

4 months ago

hey, thanks for the detailed response. But how many entries can I add through railway API? I'm sure there's a very small limit.


bananacy
PRO

23 days ago

Did you ever figure it out? I'm in same situation, need to know before I commit..


ahmad0001
FREETop 10% Contributor

22 days ago

Sorry I am late to the party but bottom line is this,railway cannot viably support dynamic custom domains for multi tenant SaaS.

Even with the api approach mentioned , Railway caps you at 1 domain on Free/Trial, 2 on Hobby, and 20 on Pro per service. The Pro limit can be raised on request, but it requires manually reaching out to Railway. That's not a scalable foundation,you'd be filing support requests every time you onboard a batch of customers, and there's no guarantee of how high they'll raise it.

So forget the Railway API for this use case. The right tool is Cloudflare custom hostnames

This is what Vercel, Netlify, and Render actually use internally. The idea is simple: Railway only ever sees one domain (app.myapp.com). Cloudflare handles every customer's hostname in front of it ,at unlimited scale.

dashboard.customer.com  →  CNAME  →  proxy.yoursaas.com (Cloudflare)
                                              ↓
                                       app.myapp.com (Railway)

When a customer adds their domain in your UI, you call the Cloudflare API instead of Railway's:

javascript

await fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/custom_hostnames`, {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${CF_TOKEN}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    hostname: 'dashboard.customer.com',
    ssl: { method: 'http', type: 'dv', settings: { min_tls_version: '1.2' } }
  })
});

Cloudflare provisions SSL for the customer's domain automatically, and your app reads the Host header to identify the tenant exactly as before. Railway never needs another domain registered


manojnaidu619
PROOP

22 days ago

Hello, This is a very late follow-up :)

I ended up solving this a while back and completely forgot to share the solution. Posting it now in case it helps others.

Also, shoutout to the previous reply that pointed me in the right direction (Cloudflare), and my solution is very similar in spirit.

white_check_mark emoji The Approach That Worked for Me

Instead of managing domains in Railway, I moved all domain handling to Cloudflare.

customer-domain.com

   → (CNAME)

proxy.yourapp.com  [Cloudflare]

   → Worker (intercepts + rewrites request)

myapp.com   [Railway - only domain configured]

gear emoji How It Works

1. User adds a custom domain

- When a user adds their domain in my app, I call:

  Cloudflare Custom Hostnames API

- This attaches the domain to my Cloudflare zone

- Cloudflare automatically provisions SSL

2. Attach a Worker to the zone

- I added a Cloudflare Worker

- Route is set to: */*

- This is important - it ensures ALL incoming requests are intercepted

3. Worker rewrites the request

- The Worker:

  - Takes the incoming request (e.g. customer-domain.com)

  - Rewrites it to my Railway domain (myapp.com)

  - Preserves the original host in headers

4. Railway only sees ONE domain

- Railway only knows about: myapp.com

- So:

  - No need to register customer domains in Railway

  - No domain limits

  - No manual intervention

bulb emoji Worker Code Preview

export default {

  async fetch(request) {

    const url = new URL(request.url)

    const originalUrl = request.url

    const originalHost = url.host

    const targetHost = "myapp.com"

    url.hostname = targetHost

    url.protocol = "https:"

    const headers = new Headers(request.headers)

    // Preserve original request info

    headers.set("X-Original-URL", originalUrl)

    headers.set("X-Original-Host", originalHost)

    // Override for upstream (Railway)

    headers.set("Host", targetHost)

    headers.set("Origin", https://${targetHost})

    headers.set("X-Forwarded-Host", targetHost)

    headers.set("X-Forwarded-Proto", "https")

    return fetch(url.toString(), {

      method: request.method,

      headers,

      body: request.body,

      redirect: "manual",

    })

  }

}

This setup has been working well for me and completely avoids the limitations on Railway.

Hope this helps +1 emoji


bananacy
PRO

22 days ago

wow was honestly not expecting a response! Perfect, i'll look into that now. many thanks!


ahmad0001

Sorry I am late to the party but bottom line is this,railway cannot viably support dynamic custom domains for multi tenant SaaS.Even with the api approach mentioned , Railway caps you at 1 domain on Free/Trial, 2 on Hobby, and 20 on Pro per service. The Pro limit can be raised on request, but it requires manually reaching out to Railway. That's not a scalable foundation,you'd be filing support requests every time you onboard a batch of customers, and there's no guarantee of how high they'll raise it.So forget the Railway API for this use case. The right tool is Cloudflare custom hostnamesThis is what Vercel, Netlify, and Render actually use internally. The idea is simple: Railway only ever sees one domain (app.myapp.com). Cloudflare handles every customer's hostname in front of it ,at unlimited scale.dashboard.customer.com → CNAME → proxy.yoursaas.com (Cloudflare) ↓ app.myapp.com (Railway)When a customer adds their domain in your UI, you call the Cloudflare API instead of Railway's:javascriptawait fetch(`https://api.cloudflare.com/client/v4/zones/${ZONE_ID}/custom_hostnames`, { method: 'POST', headers: { 'Authorization': `Bearer ${CF_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ hostname: 'dashboard.customer.com', ssl: { method: 'http', type: 'dv', settings: { min_tls_version: '1.2' } } }) });Cloudflare provisions SSL for the customer's domain automatically, and your app reads the Host header to identify the tenant exactly as before. Railway never needs another domain registered

bananacy
PRO

22 days ago

Awesome it worked!


Status changed to Solved brody 21 days ago


Welcome!

Sign in to your Railway account to join the conversation.

Loading...