Reverse Proxy with SSL

birdhighway

Stephen Hyde

Posted on August 14, 2022

Reverse Proxy with SSL

Introduction

It's often useful to make an application under local development available temporarily on the public internet. At Blended Edge, for example, our work with automating integrations means we run a lot of OAuth2 flows, and using OAuth2 for an application under development (usually) requires a publicly available endpoint with an SSL certificate.

Ngrok is a reverse proxy tool that makes this very simple, but it's also fairly easy to set up a reverse proxy using an AWS EC2 instance, certbot from Let's Encrypt, and the ssh command.

This walk-through assumes some basic knowledge of DNS, ssh, and EC2 instances (or similar cloud compute resources).

Create a Remote Server

We're going to start by launching an EC2 instance on AWS to act as our remote server. This could of course be done with any cloud provider, such as Azure or Digital Ocean.

Set the instance name as "Reverse-Proxy", and under "Application and OS Images" select Ubuntu. At the time of writing this the default Ubuntu image is 22.04.

Create a new Ubuntu instance

I have an existing SSH key pair that I will use for accessing this instance, but if you don't have one or would like to use a new key pair simply click "Create new key pair".

Create a new security group that allows HTTP/HTTPS traffic from any IP address, but limit the SSH traffic to just your own IP.

Set up the security group

Click "Launch Instance", navigate to the instances overview page, and wait for the status checks on the new instance to pass. This may take a couple of minutes.

Once the instance is ready, go to the overview page for the instance and click "Connect" in the upper right hand corner. Follow the steps to connect.

# if this is a new ssh key you may need to specify the path to the key
# ssh -i /path/to/key.pem <user>@<address>
ssh ubuntu@ec2-13-59-152-133.us-east-2.compute.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

Install Nginx

Update the package list, upgrade the packages, and install nginx.

sudo apt update
sudo apt upgrade -y
sudo apt install nginx
Enter fullscreen mode Exit fullscreen mode

If you send a GET request to your instance's IP address via a web browser or curl you should get the default nginx page.

curl http://13.59.152.133
Enter fullscreen mode Exit fullscreen mode
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
Enter fullscreen mode Exit fullscreen mode

Domain and SSL

Point a domain to the public IP address of your instance. The nameservers for my domain hydewd.com are with DigitalOcean, so in my case updating the A record to point the domain at my instance looks like this:

DNS A Record

Note: If you are doing any kind of serious or long term work with this setup you should allocate an elastic IP address, associate it with your instance, and point your domain to the elastic IP.

Install certbot from Let's Encrypt to handle automatically setting up the SSL certificate.

sudo apt install certbot python3-certbot-nginx -y
Enter fullscreen mode Exit fullscreen mode

Now run have certbot make the request for an SSL certificate. Here I am specifying that I want to cover both the naked domain hydewd.com as well as www.hydewd.com. You will be prompted to provide an email address for renewal notices, asked to agree to the terms of service, and asked if you are willing to share your email with the Electronic Frontier Foundation (this last one is optional).

sudo certbot --nginx -d hydewd.com -d www.hydewd.com
Enter fullscreen mode Exit fullscreen mode

If everything has worked you should now be able to load the default nginx page at https://<your-domain>.

Nginx Configuration

The next step is to tell nginx to redirect http/https requests to another port (8080). In the following step we will tunnel that port to our local machine.

sudo vi /etc/nginx/sites-available/default
Enter fullscreen mode Exit fullscreen mode

Within the location block of the default server block, add a line to forward traffic to http://127.0.0.1:8080.

We will be changing the contents of the location block. Here is what it looks like to begin with:

    location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
    }
Enter fullscreen mode Exit fullscreen mode

Change the contents of the location block to:

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:8080/;
    }
Enter fullscreen mode Exit fullscreen mode

Save the file, and ensure that the configuration is valid with the command sudo nginx -t. If there is no error, go ahead and restart nginx to apply the latest changes.

sudo nginx -s reload
Enter fullscreen mode Exit fullscreen mode

SSH Remote Forwarding

The final step is to tunnel the requests from the server's port 8080 to the local machine. This can be done with the ssh remote forwarding command.

ssh -R 8080:localhost:8080 ubuntu@ec2-18-217-182-155.us-east-2.compute.amazonaws.com
Enter fullscreen mode Exit fullscreen mode

Now run a server locally on port 8080. Here, for example, is a simple node server using the express framework that will reply to all GET requests with some information about what the server sees in the incoming request.

mkdir local-server && cd local-server
npm init -y
npm install express
Enter fullscreen mode Exit fullscreen mode

Create a file called index.js with the following contents:

const express = require('express')
const app = express()
const port = 8080;

app.get('*', (req, res) => {
  const { baseUrl, headers, hostname, ip, method, originalUrl, path, protocol } = req;
  res.json({
    baseUrl,
    headers,
    hostname,
    ip,
    method,
    originalUrl,
    path,
    protocol
  })
});

app.listen(port, () => console.log(`App listening at http://localhost:${port}`));
Enter fullscreen mode Exit fullscreen mode
node index.js
Enter fullscreen mode Exit fullscreen mode

Sending a query to your domain will now return a response from your local express server.

curl -s 'https://www.hydewd.com/some/path?some=query&another=123'
Enter fullscreen mode Exit fullscreen mode

Example response:

{
  "baseUrl": "",
  "headers": {
    "host": "www.hydewd.com",
    "x-real-ip": "104.1.2.3",
    "connection": "close",
    "user-agent": "curl/7.68.0",
    "accept": "*/*"
  },
  "hostname": "www.hydewd.com",
  "ip": "::ffff:127.0.0.1",
  "method": "GET",
  "originalUrl": "/some/path?some=query&another=123",
  "path": "/some/path",
  "protocol": "http"
}
Enter fullscreen mode Exit fullscreen mode

Note that the header x-real-ip displays the IP address of the end user making the request, not the IP of the instance. This is a result of the configuration line proxy_set_header X-Real-IP $remote_addr;.

Conclusion

This approach will work for some basic use cases, but is overall quite limiting. In a future post we will look at using Traefik to provide a more feature rich setup.

Helpful Links

Digital Ocean Guides: How to Install Nginx

Nginx Docs: Reverse Proxy Documentation

Nginx Docs: Installing Lets Encrypt

💖 💪 🙅 🚩
birdhighway
Stephen Hyde

Posted on August 14, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

What was your win this week?
weeklyretro What was your win this week?

November 29, 2024

Where GitOps Meets ClickOps
devops Where GitOps Meets ClickOps

November 29, 2024

How to Use KitOps with MLflow
beginners How to Use KitOps with MLflow

November 29, 2024