Skip to main content

Serve your SaaS customer domains over HTTPS for FREE

4 min read

Back is days at Haltrip, when we started to deploy our SaaS platform for the first time, we were given an estimation of max 10 clients. So we decided to invest less time into our DevOps automation. Business decision was to onboard every client manually. First forward few months, one of my team mate sent me this screenshot:

LetsEncrypt SAN error

So we hit the limit of 100 common names for a single SAN CSR.

I was not sure about the keywords to Google for! I knew people solved this problem earlier, a lot of SaaS model business came online in last few years. So I started googling with random keywords!

I got some service names like CloudFlare Enterprise, qloaked, sslmate, clearalias and etc. Problem is all of them are quite costly and has recurring billing. CF is $5K/month! On the lower side some are $1/month/domain.

My one of primary requirements was keeping the cost down. If it was like 15-20$/month, I would have probably considered.

Another peer of mine suggested to look into AWS CloudFront and use it as proxy. They do provide free SSL with all of their domain. I never thought of CF distribution like this! Thanks to him. Still the cost was a bit issue!

So, how I did it finally? Meet OpenResty with LetsEncrypt! If you haven't ever heard of OpenResty, it's NginX on steroid! It lets you dynamically script NginX config with Lua!

Here how I did it step by step:

Step 1: Disable Nginx and Install OpenResty

Disable NginX

sudo systemctl disable nginx
sudo systemctl stop nginx

Install OpenResty

sudo apt-get -y install --no-install-recommends wget gnupg ca-certificates

# import our GPG key:
wget -O - https://openresty.org/package/pubkey.gpg | sudo apt-key add -

# add the our official APT repository:
echo "deb http://openresty.org/package/debian $(lsb_release -sc) openresty" | sudo tee /etc/apt/sources.list.d/openresty.list

# to update the APT index:
sudo apt-get update && sudo apt-get install openresty

Note: I was using Debian. For other distribution, please find the guide here.

At this stage, I copied over all my existing Nginx config to /etc/openresty and made sure the sites are working properly with existing config and SSL certificates.

Install lua-resty-auto-ssl package

# install the luarocks to manage the packages
sudo apt-get install luarocks

# install the package
sudo luarocks install lua-resty-auto-ssl

Configure OpenResty

Base config: /etc/openresty/nginx.conf

user nginx;
events {
  worker_connections 1024;
}

http {
  lua_shared_dict auto_ssl 1m;
  lua_shared_dict auto_ssl_settings 64k;
  resolver 8.8.8.8 ipv6=off;

  init_by_lua_block {
    auto_ssl = (require "resty.auto-ssl").new()
    auto_ssl:set("allow_domain", function(domain)
      return true
    end)
    auto_ssl:init()
  }

  init_worker_by_lua_block {
    auto_ssl:init_worker()
  }

  server {
    listen 127.0.0.1:8999;
    client_body_buffer_size 128k;
    client_max_body_size 128k;

    location / {
      content_by_lua_block {
        auto_ssl:hook_server()
      }
    }
  }
}

Main vhost: /etc/openresty/sites-enabled/00_app.conf

server {
    listen 443 ssl http2 default_server;
    server_name _;

    ssl_certificate_by_lua_block {
      auto_ssl:ssl_certificate()
    }
    ssl_certificate /root/.acme.sh/fullchain.cer;
    ssl_certificate_key /root/.acme.sh/app.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    include common/react_on_rails.conf;
  }

HTTP 2 HTTPS redirection: /etc/openresty/sites-enabled/00_http2https.conf

server {
  listen 80 default_server;
  listen [::]:80 default_server;
  server_name _;

  # Required to verify domain with LetsEncrypt
  location /.well-known/acme-challenge/ {
    content_by_lua_block {
      auto_ssl:challenge_server()
    }
  }

  location / { return 301 https://$host$request_uri; }
}

All the config done!

Start the server

sudo mkdir /etc/resty-auto-ssl
sudo chown nginx /etc/resty-auto-ssl

Now hit up and of your SaaS URL and see the magic! It'll automatically generate a certificate and serve with HTTPS! For the first request, it'll be bit slow. This time NginX will generate a certificate with LetsEncrypt and store it into a file. From next time it'll be as usually crazy fast!

You won't have to worry about the renew, OpenResty will handle that as well!

If you have ever handled this situation any other way, or have any question, please shout out in the comment bellow!