How to Build an App that Supports Wildcard and Custom Domains with SSL on Heroku

Written by chptung | Published 2021/11/13
Tech Story Tags: no-code | heroku | heroku-pipelines | saas | ruby-on-rails | hackernoon-top-story | programming | software-development

TLDRCoffee Chats is a platform that allows anyone to create a beautiful, personalized one-page website that syncs with your Google Calendar for easy chat scheduling. Here is a guide on how the Coffee Chats founder was able to set secure wildcard and custom domains for Coffee Chats.via the TL;DR App

My personal project -- Coffee Chats- - is a platform that allows anyone to create a beautiful, personalized one-page website that syncs with your Google Calendar for easy chat scheduling all in one simple app and without writing any code.

Unlike Calendly or other scheduling apps, one of the biggest benefits of Coffee Chats is that you can have your site hosted on your own, personalized link(e.g. chris.trycoffeechats.com) or you can connect your site to a custom domain like https://www.compoundchoice.co, which is my site for Coffee Chat's parent company and is also powered by Coffee Chats.

In short, Coffee Chats takes the calendaring features in apps like Calendly but combines it with hosting and e-commerce features found in apps like Carrd, Shopify, and Squarespace and combines it all in one app.

But, when I was figuring out how to create an app that supports SSL’d wildcard and custom domains on Heroku, I struggled to find documentation. Specifically, I needed a way to:

  • Have a Heroku app that allowed users to get an SSL'd wildcard domain like chris.trycoffeechats.com
  • Have a Heroku app that allowed users to move their domain from *.trycoffeechats.com over to their own custom domain if they were on the Premium plan and that custom domain needed to be SSL’d
  • Have both apps talk to the same database
  • Make this as cheap as possible
  • Works with Ruby on Rails (Coffee Chats' programming language) and PostgreSQL

After some trial and error, I figured out a way to do this, and so below you’ll find my step-by-step guide to set this up so you can avoid some of the mistakes that I made.

Step 1: Create one app for custom domains that uses Heroku's Automated Certificate Management (ACM)

ACM is one of the best things about Heroku and it's what you'll need to use in order to keep your project costs low. If you're not familiar, Automated Certificate Management (ACM) allows Heroku to "automatically manage TLS certificates for apps with Hobby and Professional dynos on the Common Runtime, and for apps in Private Spaces that enable the feature."

In plain English, it means Heroku will manage SSL certs for users who connect their custom domains to your app for free!

Likely, you'll already have this set up as your primary app but you want to confirm your app looks something like the below and you can see that your certificates are automatically managed:

If you do not have ACM set up, click on "Configure SSL" and then select Automatic Certificate Management (ACM) like below:

Cool! But, in order to support wildcard domains (e.g *.trycoffeechats.com), you'll need to use the Manual Certificate option from Heroku, which immediately conflicts with ACM. So what's the solution?

You'll need to create a second app that uses the Manual Certificate option and upload your own wildcard SSL cert for your wildcard customers and have that second app use the same database as your first app.

Yeah, confusing! But, let's step through this one at a time.

Step 2: Create a second app that is just for wildcard domains and connect it to your first app using Heroku pipelines

To make it easy for us to manage our two apps, we'll want to first create an app pipeline in Heroku.

Go to your app and on the top right, click on "More" then on "Add to pipeline"

If you have a pipeline already created, use that, but assuming this is a brand new thing you're doing, you'll want to click "Create new pipeline"

Then choose your environment preference. Though, assuming you're doing this for your live app, you'll want to choose "Production"

Now that you have your pipeline created, you'll want to make your new app that will just be for wildcard users. To do this, click on "Add app" next to Production (assuming you chose Production on the step above)

Pick your app name and then create your new app. I recommend adding something that signifies this is for your wildcard users.

Now that we have our second app created, go through the normal process of pushing a copy of your app to this new app just for wildcard domains. If you don't know how to deploy code to Heroku, I suggest reading more here.

Step 3: Point both apps to the same database

So, you now have two apps: one for custom domains and one for wildcard domains, but you need them to connect to the same database in order for this to work.

To do that, Heroku has some very convenient commands. So, first, you'll run:

heroku addons:attach my-originating-app::DATABASE --app wildcard-app

Attaching postgresql-addon-name to sushi... done
Setting HEROKU_POSTGRESQL_BRONZE vars and restarting sushi... done, v11

The attached database's URL is assigned to a config var with the name format HEROKU_POSTGRESQL_[COLOR]_URL. In the above example, the config var's name is HEROKU_POSTGRESQL_BRONZE_URL, and "wildcard-app" is the name of the app we're connecting to the original database from your first app (aka my-originating-app).

For the app that just connected to your master database, you will then want to promote this new connected database as the primary one using the command:

heroku pg:promote HEROKU_POSTGRESQL_BRONZE_URL --app wildcard-app

In the above example, HEROKU_POSTGRESQL_BRONZE_URL is the config var for the master database in our connected app and "wildcard-app" is the wildcard app that we just connected to the master database.

Step 4: Get and upload your wildcard SSL cert for your wildcard app

So, we now have:

  • An app that uses Heroku's ACM to handle custom domains
  • An app that uses a Manual Certification for SSL to handle wildcard domains
  • Both apps talk to the same database

But! One thing that's missing for #2 is that we actually need to upload an SSL cert in order for us to get https URLs for our wildcard users.

There are a lot of ways to do this, but I'll link to the official Heroku documentation to get a wildcard SSL.

Once you have your wildcard certificate, you will upload this to your Heroku app by going into Settings for your wildcard app in Heroku and clicking "Add Certificate"

Then, upload your new wildcard certificate:

Hooray! You now have two apps that both have SSL certificates and we now just need to make some updates to our app to create the user's custom domain on the correct Heroku app, store that information in our database, and then render the correct website content when a visitor navigates to the custom domain.

Step 5: Write a function to create a user's custom domain on the custom domain app and store that information

Now that we have our two apps working and both support SSL'd domains -- the custom domain one managed by Heroku's ACM and the wildcard domain managed by our manual SSL certificate - -we need to write a function that talks to and creates a custom domain site when a user wants to connect their own custom domain.

To do this, we'll have a Rails form that allows a user to enter their preferred custom domain. If you need a tutorial on how to do that, check one out here.

Once a user submits the form (see the Coffee Chats example above), we'll create the custom domain on the custom domain app, Heroku will give us the CNAME a user will need to add to their DNS registrar, we'll store that CNAME and then render it to the user.

All this can be done with the below function:

def create 
  # create an instance of Heroku's PlatformAPI 
  heroku = PlatformAPI.connect_oauth(< your heroku_oauth_token >)
  
  # set the user's custom domain value from the form into the variable @custom_domain
  @custom_domain = params[:account]["custom_domain"]
# Create a custom domain using the user's provided value on the Heroku app for custom domains
  heroku.domain.create(< your custom domain app from heroku >, body = {
    "hostname": @custom_domain
  })
# Once created, retrieve the custom domain info for the user's custom domain
  @domain = heroku.domain.info(< your custom domain app from heroku >, @custom_domain)
# Store the CNAME value for this custom domain from Heroku in your database
  @account.update(:heroku_dns_target => @domain["cname"])
# redirect back to the previous form once completed
  redirect_back(fallback_location: root_path, notice: 'Custom domain saved.')
end

Step 6: Update Rails routes to point to a wildcard domain

We are almost done! The last thing we need to do is update our Rails routes so it knows when to pull a website using the subdomain value (e.g. chris.trycoffeechats.com), the custom domain value (e.g. www.compoundchoice.co, or route to the index page of our app (e.g. www.trycoffeechats.com)

To do this, we add the below function to config/routes.rb:

get '/', to: 'subdomain#show', constraints: lambda { |r| !r.host.include? ("trycoffeechats") }

constraints( lambda { |r| r.subdomains.first != "www" } ) do.  
   get '/', to: 'subdomain#show'
end

root 'pages#index'

There are two lines of magic happening here:

  • The first says to send the user to the Show action for the Subdomain Controller if the provided URL does not include "trycoffeechats" which is my app name. For yours, you would change "trycoffeechats" to your app's name. This is important as it will handle cases where a user's custom domain is www.theirwebsite.com.
  • The second line says that we should also route users to the Show action for the Subdomain Controller if the URL does not contain www. This handles all the instances where a user has a wildcard domain (e.g. *.trycoffeechats.com), but because of how Rails Routes works, the priority of logic is from top to bottom so it will attempt to send users to subdomain#show if the link doesn't contain "trycoffeechats" (aka a custom domain app) and then if the link does contain "trycoffeechats" but does not contain www it will also take users to subdomain#show and lastly, if the link does contain both trycoffeechats and www it'll send them to root 'pages#index' which is the homepage for Coffee Chats

But, now we're sending all these users to subdomain#show but what does that actually look like?

class SubdomainController < ApplicationController
def show 
  begin
    if request.host.include? "trycoffeechats"
      @account = Account.where(:username => request.subdomain).first
    else
      @account = Account.where(:custom_domain => request.host).first
    end
if !@account
      return redirect_to root_url(subdomain: "www"), alert: "This site does not exist."
    end
  rescue => e
    puts "error"
    puts e
    return redirect_to root_url(subdomain: "www", alert: "This user does not exist"
  end
end
end

So what is going on above:

  • In our Show action of the controller, we have an if/else to see if the URL the user is visiting ( request.host ) includes our app name (trycoffeechats). If it does, then we'll get the appropriate Account info by looking up the subdomain ( request.subdomain ) against the username column in Account.
  • However, if the request.host does not contain "trycoffeechats" that means the user is visiting a site with a custom domain. In this case, we'll find the Account by looking up the request.host against the custom_domain column in the Account table.
  • Then, if no account is found, we'll send the user back to our original app with the www subdomain ( root_url(subdomain: "www") )
  • And, this whole thing is wrapped in a begin / rescue and if an error happens, we'll also send the user back to the index page for our ( root_url(subdomain: "www") )

You now have an app that allows users to have an SSL'd wildcard domain and the option to have an SSL'd custom domain!

If you made it this far, you should now have an app that supports both wildcard and custom domains while providing an SSL option for BOTH domain types. Because of the success of other no-code products like Squarespace, Shopify, and others, this ability to support wildcard and custom domains is now an expected feature so, if you were like me and struggling to figure out a way to do this, I hope this tutorial helps you out.

And, for those of you looking to try this out in production or if you’re just someone with expertise and looking to host chats (free or paid) with guests, visit Coffee Chats and try it out for free!


Chris is the founder of Coffee Chats: a platform that allows anyone to create a beautiful, personalized one-page website that syncs with your Google Calendar for easy chat scheduling all in one simple app. Coffee Chats is perfect for coaches, mentors, influencers, and experts to host free and/or paid chats with their audience. You can book time with Chris using his Coffee Chats site or follow him on Twitter.


Written by chptung | Making the world better one chat at a time with trycoffeechats.com. Previous: 2x ex-AMZN. Sold 2 SaaS apps.
Published by HackerNoon on 2021/11/13