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:
- My main domain:
layoff.wtfwith HTTPS - Any subdomain:
<subdomain>.layoff.wtfwith 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.
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
CNAMErecord forsupport.waitlist.gurupointing towaitlist.layoff.wtf, then the DNS should resolvesupport.waitlist.guruto your server's IP address.Caddy is configured to handle requests for any domain, not just subdomains of
layoff.wtf:In this setup, Caddy uses a wildcard matcher for hostnames (..layoff.wtf support.*.guru) to match any subdomain of
layoff.wtfandsupport.*.guru. You'll need to adjust the matchers based on your needs.