How to Build a White-Label SaaS — Custom Domains, Branding and Subdomain Routing


How to Build a White-Label SaaS — Custom Domains, Branding and Subdomain Routing
There comes a moment in every SaaS journey when you receive that enterprise client email: "Hey, we love your platform, but we're getting pushback from our customers because they see 'Powered by X Company' everywhere. Can you deliver this under our brand?"
Every SaaS gets there eventually. The ones that succeed have a complete white-label system. The ones that don't either lose the deal or burn countless hours trying to hack it together with URL rewriting and CSS overrides.
This guide covers the only architecture that works at scale: the one where you treat each client as a "tenant" with its own domain, branding, and complete separation.
The Two White-Label Approaches: Subdomains vs Custom Domains
Every white-label system starts with the same question: how do we expose this software?
Subdomain approach (tenant.yoursaas.com)
What it is: Each client gets their own subdomain pointing to your application.
When it wins:
- You want SEO benefits — each client can rank independently
- You want to keep client data completely separate
- You're comfortable with managing a large number of subdomains
- Your DNS configuration is simple (wildcard records)
When it loses:
- Clients expect to use their own domain, not a subdomain
- SEO value goes to the client, not you
- Management overhead gets real when you have 100+ clients
Custom domain approach (client.com → your SaaS)
What it is: Clients point their domain directly to your application via CNAME records.
When it wins:
- Clients get exactly what they want: their own domain
- Full control over SEO, email services, and customer trust
- Professional presentation that matches enterprise expectations
- You can charge more for the setup
When it loses:
- You lose some control over DNS configuration
- SSL certificate management becomes more complex
- DNS issues can take your client's service offline
Reality check: Most successful white-label SaaS products support BOTH. They offer subdomains for quick setup and customization, but let clients upgrade to custom domains as their business grows.
Subdomain Routing: The {tenant}.yoursaas.com Pattern
Let's look at the infrastructure that makes subdomain routing work.
Basic setup:
1# /etc/nginx/sites-available/tenant.conf
2
3server {
4 listen 80;
5 server_name *.yoursaas.com;
6
7 # Tenant identification
8 set $tenant $host;
9 if ($host ~ ^(.+)\.yoursaas\.com$) {
10 set $tenant $1;
11 }
12
13 proxy_pass http://backend;
14 proxy_set_header Host $host;
15 proxy_set_header X-Real-IP $remote_addr;
16 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
17 proxy_set_header X-Tenant $tenant;
18}What this actually does:
- Listens for all subdomains under your domain
- Extracts the tenant name from the host header
- Passes it as a header to the backend application
- The application knows which client is making the request
Custom Domain Setup: Getting client.com to point to your SaaS

The best way to set up custom domains is with CNAME records and wildcard SSL certificates.
DNS configuration:
1# In your client's DNS manager:
2# Type: CNAME
3# Name: client.com
4# Target: your-saas-domain.comBut there's a problem: browsers show security warnings if you use CNAME records with SSL. The solution is wildcard certificates or manual URL rewriting.
Better approach for custom domains:
Option 1: CNAME + HTTP (Then rewrite to HTTPS)
1# /etc/nginx/sites-available/custom-domain.conf
2
3server {
4 listen 80;
5 server_name client.com;
6
7 # Rewrite to HTTPS
8 return 301 https://$host$request_uri;
9}
10
11server {
12 listen 443 ssl http2;
13 server_name client.com;
14
15 ssl_certificate /path/to/client.com.crt;
16 ssl_certificate_key /path/to/client.com.key;
17
18 location / {
19 proxy_pass http://backend;
20 proxy_set_header X-Tenant client;
21 }
22}Option 2: A record (More stable but requires IP management)
For enterprise clients, we use A records pointing to your load balancer, giving you more control:
1# In client's DNS:
2# Type: A
3# Name: client.com
4# Target: 1.2.3.4Wildcard SSL Certificate Setup
Wildcard SSL certificates solve the Subdomain HTTPS problem elegantly:
1# Using Certbot (Let's Encrypt)
2sudo certbot --nginx -d '*.yoursaas.com'What this gives you:
- SSL for all subdomains: client1, client2, client3, etc.
- Auto-renewal handling
- Free of course
The cert automatically updates as new clients sign up — no manual intervention required.
Custom Domain SSL with Let's Encrypt + Certbot
For custom domains, we use Certbot with HTTP-01 challenge validation:
1# Install Certbot
2sudo apt update
3sudo apt install certbot python3-certbot-nginx
4
5# Get certificate for client.com
6sudo certbot --nginx -d client.com
7
8# Setup automated renewal
9crontab -e
10*/month * * * * /usr/bin/certbot renew --quietThe automation handles certificate renewal and cross-platform deployment, so you never have to worry about SSL expiring.
Branding Per Tenant: Making client.com Look Like client.com
The hardest part of white-label SaaS is making it look like your client's product.
Here is what we do:
Tenant database configuration:
1-- tenants table
2CREATE TABLE tenants (
3 id UUID PRIMARY KEY,
4 client_name VARCHAR(255) NOT NULL,
5 domain VARCHAR(255) UNIQUE,
6 subdomain VARCHAR(255) UNIQUE,
7 logo_url TEXT,
8 primary_color VARCHAR(7) DEFAULT '#0066CC',
9 secondary_color VARCHAR(7) DEFAULT '#6C757D',
10 email_sender_name VARCHAR(255) DEFAULT 'Support',
11 email_sender_email VARCHAR(255),
12 created_at TIMESTAMP DEFAULT NOW()
13);Branding configuration service (NestJS):
1@Injectable()
2export class TenantBrandingService {
3 async getBrandingConfig(tenantId: string): Promise<BrandingConfig> {
4 return this.prisma.tenants.findUnique({
5 where: { id: tenantId },
6 select: {
7 clientName: true,
8 logoUrl: true,
9 primaryColor: true,
10 secondaryColor: true,
11 emailSenderName: true,
12 emailSenderEmail: true,
13 },
14 }) as BrandingConfig;
15 }
16}React component for dynamic theming:
1const TenantBranding: React.FC = () => {
2 const { tenant } = useTenantContext();
3 const branding = useBranding();
4
5 return (
6 <ThemeProvider
7 theme={{
8 primary: branding.primaryColor,
9 secondary: branding.secondaryColor,
10 }}
11 >
12 <AppContainer>
13 <Logo src={branding.logoUrl} alt={`${branding.clientName} logo`} />
14 <GlobalStyles />
15 {children}
16 </AppContainer>
17 </ThemeProvider>
18 );
19};Tenant Resolution Middleware in NestJS
Every request needs to know "whose data this is." Here's the middleware that does it:
1// middleware/tenant-resolution.middleware.ts
2import { Injectable, NestMiddleware, MiddlewareConsumer, Module } from '@nestjs/common';
3import { Request, Response, NextFunction } from 'express';
4
5@Injectable()
6export class TenantResolutionMiddleware implements NestMiddleware {
7 use(req: Request, res: Response, next: NextFunction) {
8 // Get tenant from subdomain or custom domain
9 const host = req.hostname;
10 let tenantId: string | null = null;
11
12 if (host.endsWith('.yoursaas.com')) {
13 // Subdomain approach: tenant.yoursaas.com
14 tenantId = host.replace('.yoursaas.com', '');
15 } else {
16 // Custom domain approach: lookup in database
17 tenantId = this.lookupTenantByDomain(host);
18 }
19
20 if (!tenantId) {
21 res.status(404).json({ error: 'Tenant not found' });
22 return;
23 }
24
25 // Attach tenant info to request object
26 (req as any).tenantId = tenantId;
27
28 // You could also load tenant config and attach it
29 (req as any).tenantConfig = this.loadTenantConfig(tenantId);
30
31 next();
32 }
33
34 private lookupTenantByDomain(domain: string): string | null {
35 // Database lookup by domain from tenants table
36 return this.prisma.tenants.findUnique({
37 where: { domain },
38 select: { id: true },
39 })?.id || null;
40 }
41
42 private async loadTenantConfig(tenantId: string): Promise<TenantConfig> {
43 return this.prisma.tenants.findUnique({
44 where: { id: tenantId },
45 select: {
46 id: true,
47 clientName: true,
48 logoUrl: true,
49 primaryColor: true,
50 secondaryColor: true,
51 },
52 }) as TenantConfig;
53 }
54}Usage in NestJS app:
1// main.ts
2import { TenantResolutionMiddleware } from './middleware/tenant-resolution.middleware';
3
4const app = await NestFactory.create(AppModule);
5
6// Apply to all routes
7app.useGlobalMiddleware(new TenantResolutionMiddleware());
8
9// Or apply to specific routes
10const app = await NestFactory.create(AppModule);
11const consumer = app.get(MiddlewareConsumer);
12consumer
13 .apply(TenantResolutionMiddleware)
14 .forRoutes('*.service');DNS Record Instructions for Clients
Setting up DNS correctly makes the difference between "it works immediately" and "I need to call the client".
Subdomain setup (quick, minimal instructions):
1# Client's DNS manager
2Type: CNAME
3Name: client.yoursaas.com
4Target: your-saas-domain.comCustom domain setup (requires SSL):
1# Step 1: Add CNAME to your domain's DNS
2Type: CNAME
3Name: client.com
4Target: your-saas-domain.com
5TTL: 3600
6
7# Step 2: Add A record for SSL (optional)
8Type: A
9Name: client.com
10Target: [your load balancer IP]
11TTL: 3600
12
13# Your client needs these SPF/DKIM/DNS records for email:
14Type: TXT
15Name: client.com
16Value: "v=spf1 include:mail.yoursaas.com ~all"Automate DNS setup for custom domains:
1// api/tenant-setup.service.ts
2@Injectable()
3export class TenantSetupService {
4 async setupCustomDomain(clientEmail: string, domain: string): Promise<TenantSetupResult> {
5 try {
6 // Generate CNAME record
7 const cname = {
8 type: 'CNAME',
9 name: domain,
10 target: this.configService.get('APP_DOMAIN'),
11 ttl: 3600,
12 };
13
14 // Generate DNS manager credentials for client
15 const dnsSetup = {
16 clientEmail,
17 domain,
18 cnameRecord: cname,
19 instructions: this.getSetupInstructions(domain),
20 };
21
22 // Send setup email with complete DNS instructions
23 await this.emailService.sendClientSetupEmail(
24 clientEmail,
25 dnsSetup,
26 'Your White-Label Setup - Step 1'
27 );
28
29 // Create tenant record
30 const tenant = await this.prisma.tenants.create({
31 data: {
32 clientName: this.extractCompanyName(clientEmail),
33 domain,
34 setupEmailSentAt: new Date(),
35 },
36 });
37
38 return {
39 success: true,
40 tenantId: tenant.id,
41 message: 'DNS setup instructions sent to client',
42 };
43 } catch (error) {
44 return {
45 success: false,
46 error: 'Failed to setup tenant domain',
47 };
48 }
49 }
50
51 private getSetupInstructions(domain: string): string {
52 return `
53 # White-Label SaaS Setup for ${domain}
54
55 Step 1: Add this CNAME record to your DNS manager:
56 Type: CNAME
57 Name: ${domain}
58 Target: your-saas-domain.com
59 TTL: 3600
60
61 Step 2: If you want custom SSL email, add this A record:
62 Type: A
63 Name: ${domain}
64 Target: your-lb-ip-address
65
66 Step 3: Wait 24-48 hours for DNS propagation
67
68 You can track setup progress at: dashboard.yoursaas.com/tenant/${domain}
69 `;
70 }
71}Key Takeaways
Building a white-label SaaS is not a simple sprint. It's a marathon that requires careful architectural planning, relentless testing, and genuine empathy for your clients' needs.
When building your white-label system:
- Support both subdomains and custom domains — clients will ask for both eventually
- Automate as much as possible — manual DNS setup will bankrupt your support team
- Make tenant identification your highest priority — it determines everything else
- Choose SSL approach carefully — wildcard certificates simplify management dramatically
- Build tenant branding configuration from day one — you'll need it for every client
- Document everything — white-label setup instructions are customer success
The goal is to make white-label conversion as painless as possible — just point a DNS record and watch your client's brand take over.
** Building white-label SaaS isn't about making clients look like yours. It's about letting them be the heroes while you handle the technical heavy lifting underneath. The best systems disappear completely once they're in your client's hands.**
Frequently Asked Questions
A white-label SaaS removes your brand completely from the interface, replacing it with your client's brand, domain, and customizations. It matters because enterprise clients often require their software delivered under their own brand — no "Powered by CodifySaaS" logos allowed.
Subdomain routing (tenant.yoursaas.com) keeps everything under your domain but makes tenant identification easy. Custom domains (client.com) give clients complete control over their web presence. Each has trade-offs in complexity, SEO, and client perception.
Wildcard SSL certificates cover all subdomains under your domain. For custom domains, use Let's Encrypt's Certbot with automation or a certificate authority like DigiCert for enterprise clients who need brand-compliant SSL.
It's not the Nginx routing — it's tenant identification. You need to identify "whose data belongs to whom" in every single request without sacrificing performance. Most teams try to infer tenant from subdomain and get burned when a client uses a custom domain.
Build separate invitation systems, role assignments, and branding configurations that live outside your normal admin interfaces. Use a separate UI for tenant management that never exposes your own platform's internal tools.
