Approximated Custom Domains and SSL for SAAS Documentation

Guides and API documentation to get you up and running.

How Approximated Works

Approximated helps you connect web domains to your application(s). These are often called custom domains when provided by your users.

This is accomplished by relaying internet traffic to your app(s) and back again for each domain that you configure, using a gloablly distributed set of machines called a Proxy Cluster. This Proxy Cluster also provides and manages SSL certificates for each domain it's responsible for.

What is a Proxy Cluster?

Approximated's Proxy Clusters are groups of machines distributed globally that serve traffic for you. When you create one in Approximated, these machines are set aside and dedicated to you, along with your own dedicated IPv4 address.

You can create Virtual Hosts on your Proxy Cluster to tell it which domains to accept traffic from, and where to send it. You can have as many Proxy Clusters as you need, and each one can scale to as many custom domains as you want.

What is a Virtual Host?

Virtual hosts are entities that you create in Approximated. They tell Approximated how to route requests for custom domains. Each virtual host has an incoming address field that matches the custom domain you want to route. It also has a target address field that tells Approximated where to send requests for that custom domain.

What role does DNS play?

Every custom domain you want to connect needs to be pointed with DNS at your Approximated proxy cluster, so that it can route them for you. This can be done with an A record pointed at your cluster's IPv4 address, or through an intermediary domain/subdomain with a CNAME record.

If a DNS record is pointed at your cluster without a matching virtual host, it will be ignored so that you control what custom domains are routed by your cluster.

Choosing A records or CNAME records
Apex domains
An apex domain is a plain domain without www or any subdomains prepended.
Example apex domains: mydomain.com, google.com
These are not apex domains: www.mydomain.com, docs.google.com
DNS A records
The simplest way to point a domain at your cluster is a DNS A record, but you should be aware of a few things:
  • If you ever choose to change to a different IP address, you will need to have all custom domains change their A records individually.
  • A records are the only broadly supported record type that allow pointing an apex domain (no subdomain or www prepending it). CNAME records do not.
  • Example: customdomain.com (A record) your cluster IP address
Using an intermediary with CNAMEs
Another option is to point an intermediary domain or subdomain that you control at your cluster with an A record, and then have custom domains point at the intermediary with a CNAME.
There are a few things to know about this method:
  • Requests to custom domains will follow the CNAME and point wherever your intermediary is pointed.
  • You can re-point all custom domains pointed this way at once by changing the intermediary domain/subdomain.
  • Public DNS spec only allows CNAME records to point a subdomain at another domain or subdomain. You can't point an apex record using a CNAME, so it will need to have a subdomain or www prepending it.
  • This will work:
    www.customdomain.com (CNAME) domains.myapp.com (A record) your cluster IP address
  • This will not work because it points an apex domain with a CNAME:
    customdomain.com (CNAME) domains.myapp.com (A record) your cluster IP address
TL;DR
If you're okay with your custom domains all having www or having another subdomain, use an intermediary domain with CNAMEs. Otherwise, use A records.

Using API Keys

Approximated requires an private API key to accept API requests from your application. This API key should never be made public or added to client-side javascript code.

You can create or get an existing API key from the dashboard, and add it to all API requests under the request header "api-key".

Virtual Hosts API

The virtual hosts API is how your application will interface directly with Approximated. You can use it to automate custom domains as needed. There are four endpoints to create, read, update, and delete a virtual host.

Note:
Make sure that you include headers for Content-Type and Accept set to 'application/json' for all requests to the API.

Create Virtual Host

Creating a virtual host is done with a JSON POST request to the Approximated API. It can be created at any time before or after the custom domain is pointed at the cluster with a DNS record.

POST
https://cloud.approximated.app/api/vhosts
Returns
201 - Successfully created
{
  "data": {
    "id": 445922,
    "incoming_address": "acustomdomain.com",
    "target_address": "myapp.com",
    "target_ports": "443",
    "user_message": "In order to connect your domain, you'll need to have a DNS A record that points acustomdomain.com at 213.188.210.168. If you already have an A record for that address, please change it to point at 213.188.210.168 and remove any other A records for that exact address. It may take a few minutes for your SSL certificate to take effect once you've pointed your DNS A record."
  }
}
        
401 - The API key used does not exist
Fields
incoming_address
Required
String

The custom domain that you'd like to route.

Example: acustomdomain.com
target_address
Required
String

The address that you'd like requests for the custom domain to be routed to. Typically another domain or domain with a sub-page.

Example: myapp.com, myapp.com/some/page
target_ports
Optional
String
Default: "443"

This sets the port that you'd like requests to arrive at on the target address. By default it is port 443 as that is the port that web traffic secured by SSL is typically served from.

Example: 443, 80, 8080 (string)
redirect
Optional
Boolean
Default: false

Set this to true if you'd like to have requests be 301 redirected to the target address instead of proxied.

Note: redirects need to have the protocol (http:// or https://) included in the target_address, or they will be appended to the incoming address.

Example: true or false (boolean)
exact_match
Optional
Boolean
Default: false

Set this to true if you'd like to have requests that exactly match the incoming address, including paths, be overridden and routed somewhere specific. Typically this is used in combination with another virtual host configured for the base custom domain.

Note: this will ignore any extra user-added paths or queries if it matches, and will override any other virtual hosts for the same domain that don't exactly match.

Example: true or false (boolean)
redirect_www
Optional
Boolean
Default: false

For convenience, when set to true, Approximated will create a second virtual host as well that will 301 redirect the www version of the incoming address to this address.

Example: true or false (boolean)

Read Virtual Host

Use a GET request with the incoming_address at the end of the URL to retrieve the details of a single Virtual Host. If that Virtual Host was created with the API Key included in the header, it's details will be returned.

GET
https://cloud.approximated.app/api/vhosts/by/incoming/:incoming_address
Returns
200 - Successfully returned Virtual Host
{
  "data": {
    "apx_hit": true, // requests are reaching the cluster
    "created_at": "2023-04-03T17:59:28", // UTC timezone
    "dns_pointed_at": "213.188.210.168", // DNS for the incoming_address
    "has_ssl": true,
    "id": 405455,
    "incoming_address": "acustomdomain.com",
    "is_resolving": true, // is this returning a response
    "last_monitored_humanized": "1 hour ago",
    "last_monitored_unix": 1687194590,
    "ssl_active_from": "2023-06-02T20:19:15", // UTC timezone
    "ssl_active_until": "2023-08-31T20:19:14", // UTC timezone, auto-renews
    "status": "ACTIVE_SSL",
    "status_message": "Active with SSL",
    "target_address": "myapp.com",
    "target_ports": "443"
  }
}
        
404- Could not find Virtual Host with that API key
401 - The API key used does not exist

Update Virtual Host

Updating a virtual host is done with a JSON POST request to the Approximated API. It can be updated at any time before or after the custom domain is pointed at the cluster with a DNS record. Any optional fields not submitted will remain the same as they were previously.

POST
https://cloud.approximated.app/api/vhosts/update/by/incoming
Returns
200 - Successfully updated
{
  "data": {
    "apx_hit": true, // requests are reaching the cluster
    "created_at": "2023-04-03T17:59:28", // UTC timezone
    "dns_pointed_at": "213.188.210.168", // DNS for the incoming_address
    "has_ssl": true,
    "id": 405455,
    "incoming_address": "adifferentcustomdomain.com",
    "is_resolving": true, // is this returning a response
    "last_monitored_humanized": "1 hour ago",
    "last_monitored_unix": 1687194590,
    "ssl_active_from": "2023-06-02T20:19:15", // UTC timezone
    "ssl_active_until": "2023-08-31T20:19:14", // UTC timezone, auto-renews
    "status": "ACTIVE_SSL",
    "status_message": "Active with SSL",
    "target_address": "myapp.com",
    "target_ports": "443"
  }
}
        
404 - Could not find an existing Virtual Host with that incoming address
401 - The API key used does not exist
Fields
current_incoming_address
Required
String

The custom domain for an existing Virtual Host.

Example: acustomdomain.com
incoming_address
Optional
String

A new custom domain you would like to change the existing Virtual Host to.

Example: adifferentcustomdomain.com
target_address
Optional
String

The address that you'd like requests for the custom domain to be routed to. Typically another domain or domain with a sub-page.

Example: myapp.com, myapp.com/some/page
target_ports
Optional
String
Default: "443"

This sets the port that you'd like requests to arrive at on the target address. By default it is port 443 as that is the port that web traffic secured by SSL is typically served from.

Example: 443, 80, 8080 (string)
redirect
Optional
Boolean
Default: false

Set this to true if you'd like to have requests be 301 redirected to the target address instead of proxied.

Note: redirects need to have the protocol (http:// or https://) included in the target_address, or they will be appended to the incoming address.

Example: true or false (boolean)
exact_match
Optional
Boolean
Default: false

Set this to true if you'd like to have requests that exactly match the incoming address, including paths, be overridden and routed somewhere specific. Typically this is used in combination with another virtual host configured for the base custom domain.

Note: this will ignore any extra user-added paths or queries if it matches, and will override any other virtual hosts for the same domain that don't exactly match.

Example: true or false (boolean)
redirect_www
Optional
Boolean
Default: false

For convenience, when set to true, Approximated will create a second virtual host as well that will 301 redirect the www version of the incoming address to this address.

Example: true or false (boolean)

Delete Virtual Host

Use a DELETE request with the incoming_address at the end of the URL to remove a single Virtual Host.

DELETE
https://cloud.approximated.app/api/vhosts/by/incoming/:incoming_address
Returns
200 - Successfully deleted Virtual Host
Deleting customdomain.com
404- Could not find Virtual Host with that API key
401 - The API key used does not exist

Integrating With Your App

When a request for a custom domain reaches your application, you likely want to return content specific to that custom domain. For example, if your app hosts blogs, the custom domain should return content for that particular blog.

Depending on how your application works, you may need to make some changes to your code base. Below are some of the more common scenarios.

Statically Generated Content

With statically generated content, you probably have folders and files sitting on a server for each custom domain. For example, a request to mybloghost.com/some-blog will load the content directly from the /some-blog folder on your server.

In this situation, there's probably no code being run before that content is loaded directly from the files. You might have caching, but the end result is the same.

For statically generated sites, the easiest way to integrate custom domains is likely to target that user's folder URL directly. With Approximated, you can accomplish this by adding a path to your virtual host target address field.

For example:

Incoming Address:
someblog.com

Target Address:
https://mybloghost.com/some-blog

* Note the https:// in the target address is required when using a path

There are some obstacles to this approach, however:

  1. Additional appended paths may not exist

    All paths that are appended to to the custom domain will also be appended to the end of the target, which may cause issues in some cases.

    Example #1
    incoming_address: someblog.com
    target_address: https://mybloghost.com/some-blog
    Going to this URL:
    someblog.com/some-blog/some-post
    Will reach this URL on your server:
    https://mybloghost.com/some-blog/some-post
    This is probably what you want, so there's likely no issue here.
    Example #2
    Incoming Address: someblog.com
    Target Address: https://mybloghost.com/some-blog
    Going to this URL:
    someblog.com/assets/app.css
    Will reach this URL on your server:
    https://mybloghost.com/some-blog/assets/app.css

    If your application is using shared assets like app.css or app.js for all custom domains, then your code is probably expecting to find them at:

    mybloghost.com/assets/app.css
    Instead of at:
    mybloghost.com/some-blog/assets.app.css

    In that case, this URL will 404 on the custom domain because app.css is not located there.

    Solutions:
    • Symlink folders like assets in each statically generated folder. Typically your server will return requests to anything within as if they were actually there.
    • Set URLs for things like assets to be absolute in the generated HTML code.

      For example:

      href="https://mybloghost.com/assets/app.css"

      Instead of:

      href="/assets/app.css"

      Note: you may run into CORS policy issues with this approach. See below for more information.

  2. Assets on your primary domain may be restricted by CORS for custom domains

    If you make a request to your main app, for instance to get assets, you may get a CORS error if you have a CORS policy restricting other domains.

    Example:
    Custom domain: someblog.com
    Your app domain: mybloghost.com
    Linking an asset like this:
    <link rel="stylesheet" href="https://mybloghost.com/assets/app.css" />
    From a custom domain may result in a CORS error if you have a CORS policy restricting other domains from loading your app's content.
    Solutions:
    • Allow all origins in your CORS policy for those URLs by setting it to the wildcard "*".

      Note: this could have security implications for your application, please consider how loading this content on other domains might impact you first.

    • Provide those assets relative to your custom domain as well, either by generating them in each user folder or symlinking.

    • Use a CDN for assets that will allow your custom domains with CORS.

Laravel

Laravel is one of the best frameworks out there, and can easily use custom domains with Approximated. The best way to learn how to integrate Approximated with your Laravel app is to look at our Laravel custom domains example repo.

That repo is a working example that demonstrates setting a custom domain for a user, routing and middleware for custom domains, and custom domain specific controllers/content.

Ruby on Rails

Ruby on Rails makes integrating with Approximated easy. The best way to learn how to integrate Approximated with your Rails app is to look at our Ruby on Rails custom domains example repo.

That repo is a working example that demonstrates creating pages that can be tied to a custom domain, the routing required, and a class that interfaces with the Approximated API for you.

Elixir Phoenix

Apps using Elixir's Phoenix framework can integrate with Approximated easily, including websockets for liveview. We've created an example repo here for you to explore, so that you can see for yourself with a working example.

The example repo is a simple blog hosting platform, where you can create blogs and tie them to a custom domain. It should serve as a reference for how your Phoenix app can handle routing, liveviews/websockets, security features, and more for custom domains.