openchangelog logo

Openchangelog

Login

Custom Domains for your SaaS with fly.io

How I built the Openchangelog custom domain feature with fly.io

Jonas

Oct 5, 2024

technical
guide
The domains "yourcustomer.com" and "customer.saas.com" are connected with two arrows

Table of Contents

Say you’re building a SaaS app and want to offer a custom domain integration for your customers. This is a common request for growing SaaS applications and typically done manually through Let’s Encrypt.

Since Openchangelog was already running on fly.io, I discovered that we can use it directly to handle Let’s Encrypt certificates for us.

Importance of Custom Domains

Through custom domains your customers can mask the usage of a SaaS and make it seem like it’s part of their organization. This is crucial for a lot of products:

  1. Website Builders: Customers need their websites on their own domains
  2. eCommerce Platforms: Online shops require branded, trustworthy domains
  3. White-Label Solutions: Enables customers to rebrand and resell your product as their own

How it works

Manually setting up a custom domain integration involves three key steps:

  1. Reverse Proxy: Setup a reverse proxy to handle TLS termination before the application
  2. Application: Adjust the application to map the incoming domain to the corresponding customer
  3. DNS: Your Customer configures DNS records to point their domain to your application

Manual Reverse Proxy vs fly.io

Many reverse proxies like Nginx, Caddy and Traefik have a native integration with Let’s Encrypt to automatically obtain SSL/TLS certificates.

However, the configuration can be complex (except Caddy😅) and you need to implement global certificate caching. Else any instance of your app would request a new certificate from Let’s Encrypt, potentially hitting their strict rate limits.

With Fly’s custom domain feature, they handle the issuance and renewal of certificates for $0.10/mo per hostname, which is the industry standard and appropriate, in my opinion.

Using fly.io

We will be using Fly’s graphql api through their go client to programmatically create certificates when a new domain is created in our application.

First create an access token and instantiate the client with it.

import "github.com/superfly/fly-go"
fly.SetBaseURL("https://api.fly.io")
fc := fly.NewClient(
cfg.FlyAccessToken,
"fly-go",
"0.1.32",
nil,
)

Now create the certificate whenever a new domain is added in our application.

cert, _, err := fc.AddCertificate(
ctx,
cfg.FlyAppName,
domain,
)
if err != nil {
return err
}

Make sure to store the associated customer for each domain in your database, so you can retrieve the correct customer based on the request domain. Otherwise, it would be impossible to link the domain to the right customer.

To verify the certificate and direct traffic through the domain your customer will have to create a CNAME or A/AAAA record with their DNS provider.

Option I: CNAME record

In most cases, you can use a CNAME record, which points your custom domain at your .fly.dev host. If your DNS provider doesn’t allow Apex, or root, hostnames to have CNAME records, then you can use Option II: A/AAAA record.

For example, if your customer’s domain is example.com and your Fly app is openchangelog, they can set up a CNAME record like this:

Record TypeHostnameValue
CNAME@openchangelog.fly.dev

Option II: A/AAAA record

A and AAAA records use the app’s IP addresses rather than the domain name. You will first need to list your Fly ip addresses

Terminal window
fly ips list

Now your customers need to create A and AAAA records pointing to your IPv4 and IPv6 addresses.

Record TypeHostnameValue
A@[IPv4 address]
AAAA@[IPv6 address]

Check Status

You can check the certificate status as shown below, and if the status is not Ready, you can prompt the customer to update their DNS records.

cert, _, err := fc.CheckAppCertificate(
ctx,
cfg.FlyAppName,
domain,
)
if err != nil {
return err
}
switch cert.ClientStatus {
case "Ready":
// https ready
case "Awaiting configuration":
// waiting for DNS records
case "Awaiting certificates":
// waiting for let's encrypt certificate
}

Handle HTTPS Requests

Your application can now handle HTTPS requests via custom domains 🎉. Simply use the request’s Host header to identify the domain and retrieve the corresponding customer from your database.

Conclusion

That’s it! We have successfully set up scalable and secure custom domains with fly.io.
With minimal cost and effort, your customers can now brand your service under their own domain, while you benefit from a powerful new feature that sets your service apart from competitors.

I hope this post has encouraged you to give it a try. Until next time, happy building!

Interested in Openchangelog?
Get started right now for free!