Multi-tenant SaaS with Caddy and Domain Mapping Not Working

224 views Asked by At

I am trying to setup a multi-tenant application with Caddy server. The application works perfectly fine on my local setup with Laravel valet; allowing me to create subdomains with https. The following configuration works on production, allowing me to create subdomains.

However, the system fails when I try to map a subdomain from client to a subdomain on my application.

I am unable to figure out if there's an error in my application OR the way I am configuring DNS.

I am replicating the entire setup on my test-domain: layoff.wtf

My Caddyfile Configuration:

{
        on_demand_tls {
                ask http://layoff.wtf/caddy/ask
                interval 2m
                burst 5
        }
}

https:// {
        tls {
                on_demand
        }

        root * /home/forge/layoff.wtf/public
        file_server
        php_fastcgi unix//run/php/php8.2-fpm.sock
}

This setup works perfectly to serve:

  1. My main domain: layoff.wtf with HTTPS
  2. Any subdomain: <subdomain>.layoff.wtf with HTTPS

Problem

My customer has created following subdomain on my SaaS: waitlist.layoff.wtf They want to serve it via their subdomain: support.waitlist.guru

Here's how the DNS has been configured for support.waitlist.guru:

CNAME | support | waitlist.layoff.wtf. | 600 seconds

That way, I thought when the user types support.waitlist.guru, they will be served my SaaS application from waitlist.layoff.wtf.

However, they are being served the homepage on layoff.wtf and not the appropriate subdomain. You can actually type those names in browser and check.

DNS configuration for my SaaS domain: layoff.wtf is as follows:- A | @ | 13.233.62.52 | 600 seconds CNAME | * | layoff.wtf. | 600 seconds

How do I ensure that my customers can create their subdomains and map to their domain using a simple CNAME configuration; which I have seen on multiple SaaS offerings?

Update

After multiple attempts at fixing this; I think the issue is with the way my Laravel code handles the mapped domain. Here's what I found:

Route::middleware('appendSubdomainInfo')->domain('{subdomain_slug}' . '.'. config('app.primary_domain'))->group(function () {
    
    Route::middleware('subdomainAccessChecker')->group(function() {

        // All my subdomain routes go here. 
        Route::get('/check', function($subdomain_slug) {
            dd($subdomain_slug);
        });

    });
});

The above code throws correct output subdomain when accessed on subdomain.my.app/check; but throws 404 error when run on subdomain.customer.app/check

In summary; Laravel does receive the request; but fails to deliver correct output for a mapped domain. Works fine for a regular subdomain.

1

There are 1 answers

0
Jeyhun Rashidov On

In a multi-tenant SaaS application, when you want a customer's custom subdomain to resolve to a tenant-specific subdomain on your domain, you must consider about both on the DNS level within your application and web server configuration. If your customer has set up a CNAME record for support.waitlist.guru pointing to waitlist.layoff.wtf, then the DNS should resolve support.waitlist.guru to your server's IP address.

Caddy is configured to handle requests for any domain, not just subdomains of layoff.wtf:

{
    on_demand_tls {
        ask http://layoff.wtf/caddy/ask
        interval 2m
        burst 5
    }
}

https:// {
    tls {
        on_demand
    }

    @customer_subdomains host *.*.layoff.wtf support.*.guru
    handle @customer_subdomains {
        root * /home/forge/layoff.wtf/public
        file_server
        php_fastcgi unix//run/php/php8.2-fpm.sock
    }

    handle {
        root * /home/forge/layoff.wtf/public
        file_server
        php_fastcgi unix//run/php/php8.2-fpm.sock
    }
}

In this setup, Caddy uses a wildcard matcher for hostnames (..layoff.wtf support.*.guru) to match any subdomain of layoff.wtf and support.*.guru. You'll need to adjust the matchers based on your needs.