Every developer gets into a situation from time to time, where you’re developing a new feature, all the tests pass, you run the application, test everything thoroughly, the result looks great! So you push the code, it gets deployed and… it doesn’t work. What the hell is going on?! It works on my machine!
To prevent these things from happening we try to have our development environment match the production environment as close as possible. But matching it exactly is not always possible.
One thing that’s often different is the development environment is HTTP and the production environment is HTTPS. Browsers do their best to mitigate these issues: most things that require HTTPS in production, like service workers and PWA’s, will also work on localhost. There are a few exceptions though. Things that often cause trouble are secure cookies and mixed content.
Luckily there’s a way to use HTTPS for all your development servers without having to update the code: a HTTPS-enabled reverse proxy. This guide will go through setting up Traefik proxy as an HTTPS-enabled reverse proxy on your development machine.
The code for this guide is also available on GitHub.
mkcert: web.dev (Google) has an excellent guide on using HTTPS in development, so this guide will not go through all of that. There is only one downside to their approach: each server needs HTTPS to be configured separately. This guide aims to fix that.
Follow the guide on web.dev until you’ve installed
mkcert, or follow
mkcert‘s official installation instructions, we will continue from there. You don’t need to generate any certificates for localhost or configure a server.
If you’re using Windows Subsystem for Linux (WSL),
mkcert -install will not install the CA certificate for browsers running on Windows. You will have to let them trust the root CA certificate manually. You can find out where the root CA certificate is stored by running
You will need
docker compose to run the reverse proxy, so make sure you have Docker and Docker Compose installed on your system.
docker compose --help or
docker-compose --help to see if you already have it installed.
There are 3 high-level steps you need to go through:
- Pick a domain name and generate the certificates for it
- Configure and run the reverse proxy
- Make sure your domain name resolves to localhost
You will learn how to configure Traefik proxy to forward URL’s of the form
http://localhost:<port>. That way, you can rely on Traefik to handle encryption for you without making any changes to your development server.
Here’s an overview of the solution:
Pick a domain name and generate the certificates for it
This guide will use
<port>.localhost.test, but you can pick your own domain name if you like. Just make sure there is room for the
<port> subdomain somewhere. We use
.test as the top-level domain because it is intended for testing purposes and won’t interfere with publicly available domains. Since you’re trying to resemble a production environment as closely as possible, avoid domains that would receive special treatment, like domains names ending with
To generate the certificate, run
mkcert "*.localhost.test". You should get 2 files:
_wildcard.localhost.test.pem, the certifcate
_wildcard.localhost.test-key.pem, the certificate’s private key
That’s it 😊
Configure and run the reverse proxy
Create a docker compose file called
docker-compose.yml mounts a Traefik configuration file called
traefik.yml you’ll have to create it:
You just told Traefik to load dynamic configuration from files using the file provider.
This file provider will look for configuration files in the
config directory you mounted in
docker-compose.yml. Create the config directory along with 1 file inside it:
config/https-localhost-test.yml, this is where the magic happens.
It’s a long file, but there’s a lot of repetition. You can find the full version here.
From the configuration file, it should be clear that you’re telling Traefik to listen for requests in the form
https://<port>.localhost.test and forward them to
http://host.docker.internal:<port>. But what’s up with this
host.docker.internal stuff? Why isn’t it
If you think about it, using
localhost doesn’t make sense here. From the container’s perspective,
localhost is anything that’s running inside the container. But only Traefik is running inside the container. Development servers run on the machine that’s running Docker. You need a mechanism to connect to those services.
In Docker terms, "the machine that’s running Docker" is called the host. Docker Desktop has a special DNS name for the host:
host.docker.internal. That’s the DNS name from the configuration file.
There is one gotcha though: it won’t work if you’ve installed Docker without Docker Desktop. Luckily there’s an easy workaround. You can add this
extra_hosts section to the traefik service in
Traefik should now start without any problems by running
docker compose up:
Make sure your domain name resolves to localhost
You’re almost ready to test, but there is still one thing missing. When you type in
https://8080.localhost.test in your browser or run
curl https://8080.localhost.test/products from the command line, your OS will ask a DNS Server "What’s the IP address for "8080.localhost.test"?" and that DNS server will have no idea. Luckily, the hosts file can help you with that.
You will find your hosts file at
/etc/hosts on Unix-based systems and
C:\Windows\System32\drivers\etc\hosts on Windows. Open the file and add the following lines:
Testing the setup
You’re now ready to test!
For a first test, see if you can open https://8099.localhost.test in your favourite browser. You should see Traefik’s dashboard.
Next, try your own development server. If you don’t have an application or an API to test with, you can clone the repository that belongs with this guide. It has a small test application that makes a GET request, opens a WebSocket and uses Server-sent events. The test application is in the
test-app directory, so open a command line window, navigate to the test application directory and run
npm install and then
npm start. If you don’t have the
npm command, you will have to install Node.js first.
Once you see the message "API Server is running on port 8080", open https://8080.localhost.test in your browser. This should start to appear:
It takes some time to set up, but once it’s running, you can use HTTPS and WSS for all your development servers without making any changes to the servers themselves. This guide configures ports 8050 – 8099. If you want to support a wider range of ports, you can add more configuration to
https-localhost-test.yml and to your hosts file.
No more "it works on my machine" for HTTPS-related issues!
Want to learn more? Sign up for the newsletter!
You’ll receive more content like this that will help you grow as a full stack developer.