You're building out a SaaS solution and realize for one reason or another supporting custom domains for your customers is a must. There are some products out there that can do this for you in some way, but they either cost a lot, are a huge maintenance burden or aren't scalable. Here, we'll walk through using AWS to add custom domains to your solution.
What are Custom Domains?
First, let's discuss what a custom domain actually is. A custom domain, let's your customer brand your API, UI, service, or product with their domain on top of your service. This provides a huge amount of value for both parties. For customers they can provide your service to their third parties or hide the implementation detail of what they are using. No one wants to expose their dependencies to customers, it's unnecessary and dangerous.
For example if you have a product @ https://product-service.com and offer custom domains for your customers, they may look like:
This might be fine in a lot of situations, you can give the customer this identifier or url and call it a day. You might even have some logic which would associate this with their user/account to automatically redirect them. That's all good, but in the first case, let's say you want to include a Support Portal as part of your Product Service, you don't want to redirect your customers to
https://customer-A.third-party-website.com, you want them to stay on the same domain.
As a concrete example, when you sign up for our product you get an account ID that looks like
bwdlb5r89jwhy232, so the urls look like this:
So why are we doing this again?
Just like the support portal option, custom domains have two other important uses.
One example is your benefit. We'll use our product Authress as an example. Authress provides an API for access control. That means every customer has their user identities, resources, roles, and permissions managed by the service. Since Authress optimizes permission queries as well as user credential security and caching, segregating each customer to their own subdomain has a lot of value for us. Authress charges by API call, that means tracking of these calls is critical, using the subdomain is a great way to separate calls.
When the service allocates a subdomain to an account, the subdomain matches the accountId:
And while this is easy for developers to use in a service, it isn't pretty. So we provide the capability to mask this domain with one the customer chooses.
Security is improved via:
A) There are lots of restrictions that exist on a per domain basis, CORS/Cookies are one of them. Allowing your users to set their custom domain for your product to match their domain, allows them to consume these resources in a safe way. Having different domains may tempt you to store some things that you shouldn't.
B) User SSO and authentication. For SSO, every business customer will want to have their own login provider (IdP) utilized. That means Account 1 uses IdP A, Account 2 uses IdP B, and so on. There is no way to know before a user logs in which IdP to use, so we need to guess. We could guess by forcing the user to enter their email, and then using the
domain of the email.
This is a terrible solution. Instead, let the customer use a custom domain on top of your subdomain to configure their IdP. Then you can just look at the domain to determine which IdP to you. (Spoiler: this is exactly what Authress does to automatically support SSO for B2B products, you don't need to do any extra work, if implemented through Authress connections).
Setting up a custom domain
So you want to let your customers, tell you which domain they want and then you map
https://custom.domain.com => https://customer-A.product-service.com
This is mostly a DNS record, and you are done. All they have to do is add CNAME added to their Domain Registrar's hosted zone.
Sounds easy doesn't it? So why do all these companies try to charge you a ton for this?
While the CNAME works out of the box, the real problem comes with TLS or HTTPS. In order for that CNAME to work and for their users to correctly have encrypted traffic, YOUR service needs to serve a TLS certificate that matches THEIR domain.
If this was your domain, you would add a DNS verification to your Hosted Zone, and your cert would work. Or You could AWS Certificate Manager and this would work without even any code.
But you don't own this domain, which means somehow you need them to give you a cert, and this cert has to be renewable, that means every 3 months~1 year you would need a new cert...For every customer...
This also means maintaining a database of these encrypted certs and safe-guarding them, because if you:
- lose them, then all your customers can no longer access your site
- expose access to them, then anyone can impersonate your customers
Both of these are really bad.
(A small aside: If you actually wanted to pay for this, there is a discussion about all the different options available on IndieHackers)
As I mentioned there are tons of solutions to this out in the world, but none of them are great, they are costly to maintain, and worse, if there is something that goes wrong, it will go really wrong. So here I'm going to outline how you can do with this AWS Certificate (ACM) and AWS CloudFront (CF) and not have to worry about anything else.
1. Set up your service
Run your service like you would, wherever you would, this could be AWS Lambda or ECS (or if you are a masochist--EC2, or just want to burn a lot of money for no reason--EKS).
2. Support wildcard processing
Your service needs to work with wildcards or genericly specified domains. This means that
https://*.product-service.com needs to be handled correctly by your stack. For APIGW, this means adding a custom domain for AWS APIGW that includes the
*. For a static site running in S3, however, you don't need any extra magic.
3. Enable custom domain requests
Let your customer request "I want to setup a custom domain, my domain is
example.com". Generally speaking, require them to use a subdomain, because you likely aren't hosting their main website. For Authress, we require subdomains lik
4. Deploy Customer Account specific resources
To do this we'll deploy a new CloudFront and a new Certificate, both in the US-EAST-1 region (this is important). Both resources should request resources with the custom domain specified.
- Create a new CloudFront Distribution with No alternate domain name set. (If you try to use the cert before it is validated it will fail, so don't do this). This distribution should point to your existing infrastructure, and have custom headers set for the specific customer accountId/subdomain. The CF distribution will have a domain like:
- Request a new ACM certificate with the custom domain and get back the DNS validation options, they look like this:
Name: _85dd6f5e25429205f5ffa77.example.com. Value: _caee56563101.aocomg.acm-validations.aws.
5. Mask these values so you can make updates later, by creating two CNAME records in your hosted zone:
CNAME: validation-customer-A.product-service.com Value: _caee56563101.aocomg.acm-validations.aws (value from above)
Name: customer-A.product-service.com Value: dmv3o3npogaoea.cloudfront.net
6. Return these two sets of DNS CNAME values:
Name: _85dd6f5e25429205f5ffa77.example.com (Name from above) Value: validation-customer-A.product-service.com (our custom name)
Name: example.com (their custom domain) Value: customer-A.product-service.com
7. Wait for your customer to add these two CNAME records to their domain hosted zone.
Concretely, from our example it would looks like this:
8. Once verified update your CloudFront distribution alternate domain name and certificate that was just verified.
Now you should be successfully running the CloudFront distribution as a TLS proxy for your service, website, app, etc... It will allow the white labeling that your customers need and you don't need to pay a fortunate to managing anything. ACM and ACF are free, and all the traffic you would pay for anyway just gets migrated to a new CF.
Even better? you can attach protections to your infrastructure to monitor the usage of these proxies to know how they are being used. And further, all the tools exist at the CF level for rate limiting, DDoS protection, real-time logging, custom assets, and the list goes on.
Come join our Community and discuss this and other security related topics!
Top comments (11)
Just sharing that CloudFlare also offers something similar within their CloudFlare for SaaS offering. It's now available with all plans (including their free plan). Price is 100 custom domains for free, then $0.10/month per additional custom domain. Definitely a no-brainer coming from such a trusted brand.
Absolutely true, although Cloudflare used to charge $5000/month just to get access to the functionality. There are lots of providers for doing it, this is only one valid way and basically free.
What exact offering of cloudflare are you referencing?
it does seem like it starts from free
It does! Cloudflare for Saas has a free plan which includes up to 100 custom domains
yeah, for some reason it is extremely unclear with their pricing page
I think they're in a hurry and have to spend some time and refactor it a bit
Very nice article! thanks a lot for the clear explanation.
I am considering implementing this but I am concerned about the limit in distributions per account (200), what if I have thousands of customers? I have read about another solution with just one cname pointing to a load balancer which terminates the SSL connection. Any thoughts?
That's just the default limit: docs.aws.amazon.com/AmazonCloudFro...
You don't want to spin up a whole load balancer per customer that is super expensive, and actually the default limit for ALBs is much lower at
Hi Warren, thanks for the answer.
Let me clarify,
I don't mean one ELB per customer, but one for all customers. Here the limit is the ammount of certificates per rule in ELB (which is 25 default) although one could pack many in one (ELB supports SAN).
There is the possibility of doing this with nginx + certbot and have no limit.
Somehow (independenly of the service, Cfront, ELB, whatever) configuring and mantaining one per customer feels a bit too much for me.
May be I am just scared XD
We use bunny.net pullzone, works in same concept. Its little bit easy to handle.
Some comments have been hidden by the post's author - find out more