Custom HTTPS Hostnames, One API Call

Fly announced that our API is now open to the public. One killer feature of the API is the ability to generate custom HTTPS hostnames for your legions of customers. We'll talk about the complexity of the problem this solves then show you how one API call can get you threaded to a global network with an automatically renewing Let's Encrypt TLS certificate. Let's check it out!

Give the People What They Want!

Let's say your Software-as-a-Service enables the creation of APIs, store-fronts, static pages, blogs or web platforms. When a user creates something within your service, you might generate a unique URL for them. Odds are you've seen this all over the place: x.herokuapp.com, x.now.sh, x.amazons3.com, x.surge.sh, x.ghost.io. It's personalized for the user but still nested within their domain.

Your users will want to bring their own custom hostnames. Their hostname is their brand and while they may love your service, a brand doesn't have as much penache coming from me.someone-else.com. Generating a subdomain for them is one thing. Generating an automatically renewing, signed Let's Encrypt certificate for their custom hostname is another. If you have many customers, each with their own custom hostname, this can get complicated in a hurry.

If you're going to use Let's Encrypt on your own, Certbot is relatively slick option.

You install it on your server then provide it with a domain name. It generates a series of certificates...

goodroot@flyio:~ # ls /etc/letsencrypt/live/fly.io/
cert.pem  chain.pem  fullchain.pem  privkey.pem

You then combine them...

DOMAIN='fly.io' sudo -E bash -c 'cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/ssl/$DOMAIN.pem'

... Make them available within a reverse-proxy like haproxy...

frontend www-https
  bind [PUBLIC_IP]:443 ssl crt /etc/haproxy/ssl/fly.io.pem
  reqadd X-Forwarded-Proto:\ https
  default_backend web-backend

... Create a script for automatic renewals...

certbot renew --pre-hook "service haproxy stop" --post-hook "service haproxy start"
DOMAIN='wizardry.io' sudo -E bash -c 'cat /etc/letsencrypt/live/$DOMAIN/fullchain.pem /etc/letsencrypt/live/$DOMAIN/privkey.pem > /etc/haproxy/ssl/$DOMAIN.pem'

... For each and every domain that your users create -- ack! That could get out-of-hand in a hot minute. The Fly API was built to help with this problem.

We've written a guide to show you how to Load Balance HTTPS with Let's Encrypt if you want more insight into the Let's Encrypt experience.

(AP)I Cap'n!

Construct a call to the Fly API. Receive a preview url in reponse. Create a DNS record with the preview URL to confirm ownership. Once confirmed, boom! That's it -- HTTPS for everyone. In addition, their domain now has a fleet of global edge-servers behind it, making it faster. Let's put this in motion.

"The main thing about the API integration code is how little there is, like 15 LOC including the API adapter class and no additional libraries needed." - Posthaven.com, Case Study

First, an organization creates an account within Fly. An organization contains many sites and a site can contain many hostnames. Let's say your organization is "CoolCats" and your site is "FrontEnder.com", a static site generator that provides neat templates and custom, globally load balanced HTTPS to its users.

Once you've created your organization, head to settings to generate a Personal Access Token.

It looks like this:


The token is key -- the key is the token! With it we can ignite the API. Of course, one would want to deploy much better token obfuscation then is being presented within this article. We're making it visual for clarity; keep it secret, keep it safe!


Let's head to your trusty command-line. We can use curl to breakdown the call we'd write into our applicaitions...

curl -H "Authorization: Bearer [FLY_PERSONAL_ACCESS_TOKEN]" -H "Content-Type: application/json" -X POST -d '{"data": { "attributes": { "hostname": "[NEW_HOSTNAME]" } } }' https://fly.io/api/v1/sites/[SITE_NAME]/hostnames

First, we authorize against Fly. Next, we POST a JSON object to the API.

Our variables within the call are:

  • [FLY_PERSONAL_ACCESS_TOKEN], your unique account identifier
  • [NEW_HOSTNAME], any new hostname you'd like to serve through SSL
  • [SITE_NAME], the Fly site you're adding the new hostname under

All filled out, it looks like this:

curl -H "Authorization: Bearer 9f5e850b7d5fac5566aa35e5a3836df03eac4b3c79115e1f19d43d979ddada48"-H "Content-Type: application/json" -X POST -d '{"data": { "attributes": { "hostname": "example.com" } } }' https://fly.io/api/v1/sites/frontender-com/hostnames`

After a few milliseconds of churning and bubbling out pops:


Voila! Our preview hostname with HTTPS is ready: jk4prgkli383q063.shw.io. The shw.io URI pattern is the Fly URI pattern. Given that you could change services at some point, you might not want to send all of your user's domains generated by a third-party. If your DNS has an API, you can place an intermediate step to create your own preview domain.

When Fly responds with a shw.io domain, create a CNAME record for that domain within your DNS. That way, you can provide your user with a domain that's under your umbrella: jk4prgkli383q063.example.com instead of jk4prgkli383q063.shw.io. With this approach,if you change services you need only change your own set of records; your users create a CNAME for their custom domain using your domain instead of Fly's.

Once the preview hostname has been provided to your end-user, you can instruct them to check it out, make sure it looks good, then create a DNS record with it. For example, the following record types would be supported by the shw.io hostname or your own custom one:

ALIAS: If their provider offers ALIAS records, it will look similar to this:

Record Name Content
ALIAS example.com or @ jk4prgkli383q063.shw.io

ANAME: If their provider offers ANAME records, it will look similar to this:

Record Name Content
ANAME example.com or @ jk4prgkli383q063.shw.io

Flattened CNAME: If they use CloudFlare, NameCheap, or 123-Reg, they can setup a flattened CNAME record. If they provider does not offer CNAME flattening, you may need to convert the URI into a IP address to set it at as an A Record.

Record Name Target
CNAME example.com or @ jk4prgkli383q063.shw.io

That's it, that's all! Renewal, patching, load balancing -- all taken care of. With minimal effort, you can offer an exciting feature set to your customers: custom HTTPS hostnames and faster pages served from a global network over HTTP/2.

Fly is a platform that helps you build and launch dynamic applications to users around the world. It's a bit like a global load balancer, a smart reverse proxy, a CDN, a library of powerful Middleware; woven together, it's a fast, powerful, and intuitive network that creators like you control. It's free to sign-up, as are your first 2,000,000 requests and 100GB of data transfer every month.

Kellen Evan Person


Kellen Evan Person

A polite, forest-dwelling Canadian who enjoys coding and writing. He's spent near two decades building web applications and strives to keep development fun and light-hearted.

North Vancouver, Canada