/ software development

Local development with HTTPS on OSX

A good rule of thumb in software development is that you should try to be as close as possible from your production environment. Doing so helps you avoid common mistakes due to discrepancies between your local dev setup and your prod configuration. Containers are really helpful to mitigate some of the dependencies issues, but one thing that has always been a PITA for me is using HTTPS for local development.

At Squadlytics we have a React app for our UI and a Rails API backend where we force the use of SSL. We've tried many things to find an easy way to use HTTPS for local development, and we finally found a solution using a proxy and dnsmasq. I'll share here how we're doing things, but first, let me present you the alternatives that we explored.

Option 1: Generating certificates ourselves

We started by looking at generating our own certificates for local development. First, you need to create your own Certificate Authority. Then you use that entity to build your development certificates.

There's a great tutorial written by Brad Touesnard that you can read if you want to go down that path. It looked great at the beginning, but it started to become cumbersome when we had to figure out custom starting scripts for the backend and the frontend to use the right certificates.

We had to do a few different takes to get it right, and we ended up looking for a simpler solution where we wouldn't have to manage the certificates ourselves.

Pros: Once it's done it just works.
Cons: A bit hard to setup and manage.

Option 2: Using ngrok

If you're not yet using ngrok chances are that you'll need it sooner or later. It's a neat tool that allows you to create tunnels to expose your local dev machine to the web. The typical use case is to test webhooks, but you can also reserve your domains and generate SSL tunnels to your dev environment with either a subdomain (https://subdomain.ngrok.io) or use your own domain for it (https://api.squadlytics.dedicated dev domain).

It minimized the configuration on our side. You only need to install ngrok to get it working - and you won't be getting certificate errors in your browser.

But problems start if you need to share dedicated URLs. For instance, if your backend API is accessible at https://api.ngrok.io, you can only tunnel this URL to one local dev machine. To my knowledge, you'd have to reserve custom domains for each developer to solve that problem (https://api.john.ngrok.io, https://api.jane.ngrok.io) and probably do some tricks in your code to support that. On top of that, every request needs to go through ngrok which slows down development and prevents you to code while being offline.

So we ditched ngrok as it would not scale for our local SSL dev issue. I want to emphasize here that it's a great tool, and it's mostly our fault for trying to stretch it too far.

Pros: easy to get started, great for testing webhooks
Cons: slow for development, doesn't scale for continuous use

Option 3: local-ssl-proxy + dnsmasq

We looked back at the constraints we had for local development to simplify our problem:

  • We needed to be able to hit specific domains for the API and authentication.
  • We were using subdomains for workspaces (à la Slack) and therefore required to access a wide range of subdomains locally.
  • We needed all the requests to be done via HTTPS.
  • We wanted to have something quick to set up for extra services, and easy to install for new developers.
  • Being able to code offline would have been a big plus.

With these goals in mind, we settled for a simple solution using a proxy server and a custom local DNS server.

local-ssl-proxy

local-ssl-proxy is a npm package that starts a simple SSL HTTP proxy using a self-signed certificate. We created a small configuration file to map the Rails backend and the React frontend to different ports for SSL.

{
  "Rails backend": {
    "source": 3001,
    "target": 3000
  },
  "React UI": {
    "source": 8090,
    "target": 8080
  }
}

With that configuration, we could use https://api.squadlytics.dev:3001 and https://acme.squadlytics.dev:8090 to have access to use the platform via HTTPS. No need to create a custom starting script for Rails or figuring out how to pass a certificate to the React app.

Adding new services would be as simple as adding a new entry in that configuration file.

dnsmasq

The other problem we needed to solve was to support a wildcard domain to avoid having to create multiple entries in /etc/hosts. Thankfully you can use dnsmasq for that and create a local DNS server with your own rule.

You can follow the instructions in this Gist to see how you can set it up on your machine. In our case, we created a bash script to do it all at once.

You should be careful not to set a wildcard domain for all .dev domains as this is a real TLD. This is why we've restricted our config to only map .squadlytics.dev domains to localhost instead of supporting all URLs using .dev.

#!/bin/bash
echo "1. Installing dnsmasq"
brew install dnsmasq
echo "2. Configuring *.squadlytics.dev local domains"
mkdir -pv $(brew --prefix)/etc/
echo 'address=/squadlytics.dev/127.0.0.1' > $(brew --prefix)/etc/dnsmasq.conf
sudo cp -v $(brew --prefix dnsmasq)/homebrew.mxcl.dnsmasq.plist /Library/LaunchDaemons
sudo launchctl load -w /Library/LaunchDaemons/homebrew.mxcl.dnsmasq.plist
sudo mkdir -v /etc/resolver
sudo bash -c 'echo "nameserver 127.0.0.1" > /etc/resolver/dev'

Some people recommend using a fake TLD (like .test), but services like Google require real TLDs for OAuth.

Don't forget to add your localhost as a DNS server in your network settings otherwise it will not work.

Bypassing the SSL certificate errors

The last step for us is to bypass the certificate errors that will be inevitably thrown by your browsers.

If you're using Safari, you can just accept to visit the site in the error screen. Just make sure that you do that not only for your UI but also for any API that is used by the client.

Chrome seems to be much more strict, and to this day I've only managed to bypass the errors by launching Chrome with the --ignore-certificate-errors flag.

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --ignore-certificate-errors &> /dev/null &

I hope this will be helpful to some of you. You can check our config scripts in our repo at https://github.com/squadlytics/local-ssl-development. If you have questions or suggestions, please don't hesitate to write a comment.

Photo by Liam Tucker on Unsplash