6.43 KB
Newer Older
Shane A. Stillwell's avatar
Shane A. Stillwell committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
title: Create a local HTTPS proxy server
date: 2016-10-03 19:42:00
- Docker

In recent months I've been working to add [Apple Pay for Web]( to a major clothing retailer. One of the requirements for Apple Pay for Web is that the connection must be over HTTPS. Most of the time when I'm developing locally, I do not use HTTPS. Local, meaning the application code is running on my laptop. In most cases, HTTPS is just run in staging and production environments and not handled directly by your app code.

So this posed a problem. I didn't want to work on the server where it provides an HTTPS connection, that's a pain. Also, if you're not familiar with HTTPS servers, you need to have a valid certificate and more. So this solution requires a few steps to get working, but once it does, it works nicely.

## TL;DR
1. Create a fake SSL certificate (e.g.
1. Create a docker container that uses Nginx to proxy localhost:443 => [](/2016/08/26/reach-docker-host-from-container/)
1. Override your local [/etc/hosts]( file
1. Start your app on localhost:3000
1. Hit your app in the browser at [](

## Creating a fake SSL certificate

It's most likely not a good idea to use your production SSL certificate and key when doing local development, so you'll want to create a fake version.

> It's entirely possible to purchase legitimate SSL certifates and use them, but this is not always possible, and it's free to make your own

Update this code and run it on a Mac or Linux machine
sudo openssl req -x509 -sha256 -newkey rsa:2048 -keyout cert.key -out cert.pem -days 1024 -nodes -subj '/'

You now have files called `cert.key` and `cert.pem` for a certificate that is valid for the next 10 years.

> Caveat: Your browser will not like this certifcate, but since you trust it, you can override the browser to accept the certificate and not complain.

**Save those files, we'll use them in just a minute**

## Create a docker container

Now we want to create a docker container that will use our certificate and proxy all requests to our app running on port 3000. We will use docker compose to accomplish the configuration of the container.

### Create a directory
First, you'll want to create a directory where you can hold the docker compose yaml file, the nginx configuration, and the ssl certificates. Mine looks like this:

├── docker-compose.yml
├── proxy.conf
└── ssl
    ├── cert.key
    └── cert.pem

### The Docker Compose file

The `docker-compose.yml` file is really pretty short. Here it is.

version: '2'

    image: nginx:stable-alpine
      - ./proxy.conf:/etc/nginx/conf.d/default.conf
      - ./ssl:/etc/nginx/ssl
      - 443:443

As you can see:
* We pull down the nginx image using the small linux alpine version.
* Then we mount some volumes inside the container, the `proxy.conf` file and the `ssl` folder that holds our certs.
* Next the configuration tells the container to listen on port 443 and forward internal to port 443
Shane A. Stillwell's avatar
Shane A. Stillwell committed
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89

### The Nginx configuration

Now lets take a look at our `proxy.conf` file.

server {
    listen                     443 ssl;
    server_name                localhost;

    ssl                        on;
    ssl_protocols              TLSv1 TLSv1.1 TLSv1.2;
    ssl_certificate            ssl/cert.pem;
    ssl_certificate_key        ssl/cert.key;

    location / {
        proxy_pass          http://host.docker.internal:3000;
Shane A. Stillwell's avatar
Shane A. Stillwell committed
91 92 93 94 95 96 97 98 99
        proxy_set_header    Host      $host;
        proxy_set_header    X-Real-IP $remote_addr;
        proxy_set_header    X-HTTPS   'True';

In here you'll notice a few items of interest:
* We specify our cert and key as living in the `ssl/*` folder we mounted in our docker-compose.yml file
* We are proxy passing all traffic to `host.docker.internal:3000`, this will resolve to your localhost:3000 on your machine.
Shane A. Stillwell's avatar
Shane A. Stillwell committed
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
* The rest is just standard configuration for setting up Nginx as a proxy.

### Start your docker container

Inside your folder that has the `docker-compose.yml` file, all you need to do is run `docker-compose up -d`. That will start your HTTPS proxy and put it in the background.

Check if it started successfully by running `docker-compose ps`
       Name                Command          State              Ports
ssltesting_nginx_1   nginx -g daemon off;   Up>443/tcp, 80/tcp

## Override your local /etc/hosts file

Now we need to tell our computer that `` is not something it should ask DNS about, but can get it's answer right from `/etc/hosts`. Just so you understand, when your browser needs to resolve an address to a domain name, it will first look in `/etc/hosts`, then ask DNS.

So we just need to add this to our `/etc/hosts` file. 


This pretty easy, if you are going to be flipping this on/off I would suggest an app like [Gas Mask]( or [HostBuddy](

## Start your application on port 3000

This is something you'll have to figure out yourself.

## Hit you application in the browser.

Now it's time to open the application in the browser and test the whole thing. Go ahead and open up in the browser. If we've done everything right, we should see a warning

> What the?!

![Chrome warning](images/Screen Shot 2016-10-03 at 9.00.31 PM.png)
Shane A. Stillwell's avatar
Shane A. Stillwell committed
137 138 139 140 141 142 143 144 145

This is a good thing, Chrome doesn't trust our self signed certification, so now we just tell Chrome to trust it by clicking **Advanced > Proceed to (unsafe))**

If you get a **502 Bad Gateway** it means that Nginx cannot reach your app on port 3000. Most likely because there is a problem with your container talking to your host. Remember I said it's not as easy as it seems :(

## Conclusion

So now you have a local HTTPS server that imitates a production one. It's now possible to do any type of development locally in your app that might require an HTTPS connection, like Apple Pay for the Web.

Shane A. Stillwell's avatar
Shane A. Stillwell committed
> If your company needs help implementing Apple Pay for the Web, please contact me. I contract out my services and can have your site accepting Apple Pay payments in short order.
Shane A. Stillwell's avatar
Shane A. Stillwell committed