Reverse Proxy

This recipie aims to explain what a reverse proxy is, why you may want to use one, and your options for setting one up in the HPC Cloud.

Introduction

A reverse proxy acts as intermediary that forwards the client’s requests to one or more different internal services, like a web server or work press blog.

A reverse proxy, is placed near the server serving client requests. Reverse proxies are used to improve servce security and stability. A reverse proxy may balance load across, cache content from, or simply redirect traffic to a number of servers. In addition we encourage projects to use reverse proxies to conserve the limited number of IPv4 addresses we have available.

Setup

Depending on your requirements, we provide two recipies:

  • Define a proxy using the OpenStack LoadBalancer service: cloud-native

  • Manage your own instance running NGINX configured as reverse proxy: classic

Going the cloud native way saves you another VM to manage. The classic route allows you to do TLS termination on the proxy [^1].

Cloud Native

The following steps outline how to test and deploy a reverse proxy using the OpenStack CLI. This setup demonstrates the use of a reverse proxy to expose two hypothetical web servers running in the same private network. Only a single public IP address is required, which can be allocated through the OpenStack dashboard (GUI).

Create private network

Set the following variables to your preferences:

FLOATING_IP="<your-floating-ip>"
DOMAIN_1="www.mysite01.com"
DOMAIN_2="www.mysite02.com"
SUBNET_RANGE="192.168.0.0/28"

Assume that the domain names for the services are registered in DNS.

This is meant to ease the use of the instructions below. You can, of course, enter values in place of using the variables below.

openstack network create priv-net00

openstack subnet create sub-net00 --network priv-net00 \
--subnet-range "${SUBNET_RANGE}" \
--dns-nameserver 130.183.9.32 --dns-nameserver 130.183.1.21      

Create the following route to allow access to public internet for instance updates and installation

openstack router create rout00
openstack router set rout00 --external-gateway cloud-public
openstack router add subnet rout00 sub-net00

Create servers

Launch two virtual machines (VMs) within the previously created private network. Each VM should have Apache HTTP Server installed and running to serve as a basic web server for testing the reverse proxy setup. Using the follwing user data script ensures that Apache is installed and running as soon as the VM boots up.

cat <<EOF > web-init.sh
#!/bin/bash
DEBIAN_FRONTEND=noninteractive apt-get update
DEBIAN_FRONTEND=noninteractive apt-get upgrade -y
DEBIAN_FRONTEND=noninteractive apt-get install -y apache2
systemctl start apache2
systemctl enable apache2
IP=\$(hostname -I | awk '{print \$1}')
echo "Hallo from my web server: \$IP" | tee /var/www/html/index.html
EOF

Using the following commands to create the VMs

openstack server create server01  --image "Ubuntu 24.04" --flavor mpcdf.small \
--network priv-net00 --security-group web\
--security-group default --user-data web-init.sh

openstack server create server02  --image "Ubuntu 24.04" --flavor mpcdf.small \
--network priv-net00 --security-group web\
--security-group default --user-data web-init.sh

Create Loadbalancer

The reverse proxy is implemented using the OpenStack Load Balancer service (Octavia). This allows incoming requests to a single public IP address to be intelligently routed to the appropriate backend Apache web servers. The load balancer acts as a reverse proxy by distributing traffic based on defined listener rules and pool configurations.

pip install python-octaviaclient

openstack loadbalancer create --name load-bal00\
--vip-subnet-id sub-net00\
--vip-address "${FLOATING_IP}"
openstack loadbalancer show load-bal00

Create Listener

Create a listener on the load balancer to handle incoming HTTP requests. The listener defines the protocol and port on which the load balancer will accept traffic—typically HTTP on port 80. This can be changed accoring to the incoming traffic.

openstack loadbalancer listener create --name http-listener01\
--protocol HTTP --protocol-port 80 load-bal01

Create a pool

Normally, pools are used to group multiple backend servers for redundancy and load distribution. However, in this setup, each pool will contain only a single server to simulate different services behind the reverse proxy.

openstack loadbalancer pool create --name web-pool01\
--lb-algorithm ROUND_ROBIN --loadbalancer load-bal01

openstack loadbalancer pool create --name web-pool02\
--lb-algorithm ROUND_ROBIN --loadbalancer load-bal01

L7 policy

To route traffic to the correct backend server based on the requested URL path, create L7 policies and rules. These policies inspect incoming HTTP requests and redirect them to the appropriate pool based on path patterns.

openstack loadbalancer l7policy create --name l7-mysite01\
--action REDIRECT_TO_POOL --redirect-pool web-pool01 http-listener01

openstack loadbalancer l7policy create --name l7-mysite02\
 -action REDIRECT_TO_POOL --redirect-pool web-pool02 http-listener01

openstack loadbalancer l7rule create --type HOST_NAME\
--compare-type EQUAL_TO --value "${DOMAIN_1}" l7-mysite01

openstack loadbalancer l7rule create --type HOST_NAME\
 --compare-type EQUAL_TO --value "${DOMAIN_2}" l7-mysite02

Populate the pool

Now that the pools are created, you need to add your web servers (VMs) as members of each pool. Each member represents one of your backend Apache servers that will serve requests.

VM_IP1=$(openstack server show server01 -f value -c addresses | grep -oP "(?<=\[')[^']+(?='\])")
VM_IP2=$(openstack server show server02 -f value -c addresses | grep -oP "(?<=\[')[^']+(?='\])")

openstack loadbalancer member create --subnet-id sub-net00\ 
--address "${VM_IP1}" --protocol-port 80 web-pool01

openstack loadbalancer member create --subnet-id sub-net00\ 
--address "${VM_IP1}" --protocol-port 80 web-pool02

Testing

To test the reverse proxy setup and verify that the internal IP addresses of the backend web servers are different while the public IP remains the same, you can open a web browser and go to the corresponding web url. The private IP shows up for both and they are different. This means they all point to different internal servers. You can also verify that both domains point to the same publi IP by using the https://www.nslookup.io/website-to-ip-lookup/.

Classic

Network

First set up a private network: https://docs.mpcdf.mpg.de/doc/cloud/technical/network.html#private-networks-and-routers

For the sake of this example we assume the following:

  • $NET_NAME resolves to the network name

  • SUBNET_NAME resolves to the subnet name

  • 192.168.0.0/24 is the subnet ip range

The network section in the cloud documentation has instructions on how to setup a network.

Create security groups and rules

We create two security groups: one to add to the proxy the other to add to the nodes hosting the applications. Then add a rule allowing all tcp traffic from the proxy to the machines hosting the applications. Finally add rules to the proxy to restrict traffic as is appropriate for the application, in this case:

  • Allow HTTP (port 80) access from the internet (0.0.0.0/0) to support certbot certificate verification

  • Allow HTTPS (port 443) from the internet (0.0.0.0/0) to access our web services from anywhere

  • Allow SSH (port 22) from local MPCDF networks (10.0.0.0/8 and 130.183.0.0/16)

# create security groups
openstack security group create apps
openstack security group create proxy

# allow all traffic from proxy to apps
openstack security group rule create apps --remote-group proxy --protocol tcp

# allow access to proxy
openstack security group rule create proxy --remote-ip "0.0.0.0/0" --protocol=tcp --dst-port=80
openstack security group rule create proxy --remote-ip "0.0.0.0/0" --protocol=tcp --dst-port=443
openstack security group rule create proxy --remote-ip "10.0.0.0/8" --protocol=tcp --dst-port=22
openstack security group rule create proxy --remote-ip "130.183.0.0/16" --protocol=tcp --dst-port=22

Network configuration

Now create network ports for your application servers and the proxy and associate the appropriate security groups

openstack port create proxy \
	--network $NET_NAME \
	--fixed-ip "subnet=$SUBNET_NAME,ip-address=192.168.0.3" \
	--security-group=proxy

openstack port create app1.0 \
	--network $NET_NAME \
	--fixed-ip "subnet=$SUBNET_NAME,ip-address=192.168.0.10" \
	--security-group=apps

openstack port create app2.0 \
	--network $NET_NAME \
	--fixed-ip "subnet=$SUBNET_NAME,ip-address=192.168.0.20" \
	--security-group=apps

External access

Get a floating IP from the cloud-public network and associate it with the port of the proxy port:

openstack floating ip create cloud-public --port proxy 

Server Names

You can use the DNS names we provide for your HPC Cloud project [^2]. If you control your own domain add an A record pointing to the floating IP you assigned to the proxy. You can look up the value of the floating ip from the dashboard or using the openstack CLI:

openstack floating ip list --fixed-ip-address 192.168.0.3

Create the servers

Create the proxy server:

openstack server create proxy --flavor=mpcdf.small --image="Ubuntu 24.04" --key-name=fberg --port=proxy
openstack server create app1.0 --flavor=mpcdf.small --image="Debian 12" --key-name=fberg --port=app1.0
openstack server create app2.0 --flavor=mpcdf.small --image="Debian 12" --key-name=fberg --port=app2.0

Configure the Reverse proxy

SSH into your proxy using the floating IP:

PROXY_IP="$(o floating ip list --fixed-ip-address 192.168.0.3 -f value -c 'Floating IP Address')"
ssh "$PROXY_IP" -l root

Install nginx [^3] and certbot [^4]. The instructions on certbot also show you how to run certbot to get a certificate for the domain you setup. It should configure an entry for the domain in the nginx configuration. Look for an entry with server_name set to the doamin(s) name you setup above.

From the proxy you can also SSH into the instances running your applications. The following example assumes that your applications will be available at port 80 on their servers.

Single domain

Support you want you applications to be available at https://examnple.com/app1 and https://examnple.com/app2. When you asked for the certificate certbot should have generated an virtual host entry in the NGINX configuration for server_name example.com, look for it. It should look something like this:

server {
    server_name         example.com; # managed by Certbot
	root                /var/www/html;
	...
    location / {
        ...
    }
    listen              443         ssl; # managed by Certbot
    listen              [::]:443    ssl; # managed by Certbot
    ssl_certificate     /etc/letsencrypt/live/app1.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/app1.example.com/privkey.pem; # managed by Certbot
    ...
}

Now add two new location blocks, so the server entry looks like this:

server {
    server_name         example.com; # managed by Certbot
	root                /var/www/html;
	index               index.html index.htm index.nginx-debian.html;
	....
    location / {
        ...
    }
	location /app1 {
        proxy_set_header    Host        $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_pass          http://192.168.0.10/;
    }
	location /app2 {
        proxy_set_header    Host        $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_pass          http://192.168.0.20/;
    }
    listen              443         ssl; # managed by Certbot
    listen              [::]:443    ssl; # managed by Certbot
    ssl_certificate     /etc/letsencrypt/live/app1.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/app1.example.com/privkey.pem; # managed by Certbot
    ...
}

You may also want to diable buffering on the proxy with proxy_buffering off;. NGINX provides documentation for the many reverse proxy settings[^5][^6]. Don’t touch the lines # managed by Certbot.

Restart nginx:

systemctl restart nginx

Now going to https://example.com/app1 with your browser will get to the nginx proxy and be redirected to the application running on the server app1.0. Similarly, https://example.com/app2 will be directed to the application running on the server app2.0.

Note that this configuration assumed your applications will be available at port 80 on their servers.

Multiple domains

Suppose you have setup A records for your applications called app1.example.com and app2.example.com. Each domain will need a virtual host in the NGINX configuration. You should find an entry in your NGINX configuration looking like this:

server {
    server_name         app1.example.com; # managed by Certbot
	root                /var/www/html;
	index               index.html index.htm index.nginx-debian.html;
    location / {
        ...
    }
    listen              443         ssl; # managed by Certbot
    listen              [::]:443    ssl; # managed by Certbot
    ssl_certificate     /etc/letsencrypt/live/app1.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/app1.example.com/privkey.pem; # managed by Certbot
    ...
}

Edit the location entry for /:

location / {
    proxy_set_header    Host        $host;
    proxy_set_header    X-Real-IP   $remote_addr;
    proxy_pass          http://192.168.0.10/;
}

Where 192.168.0.10 is the IP of the instance running app1. For app2, it would then look like this:

server {
    server_name         app2.example.com; # managed by Certbot
	root                /var/www/html;
	index               index.html index.htm index.nginx-debian.html;
    location / {
        proxy_set_header    Host        $host;
        proxy_set_header    X-Real-IP   $remote_addr;
        proxy_pass          http://192.168.0.20/;
    }
    listen              443 ssl; # managed by Certbot
    listen              [::]:443 ssl; # managed by Certbot
    ssl_certificate     /etc/letsencrypt/live/app2.example.com/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/app2.example.com/privkey.pem; # managed by Certbot
    ...
}

In this setup I assume you grabbed seperate certificates for your domains. certbot lets you get one certificate for multiple domains as well by repeating the -d option. In that case the ssl_certificate and ssl_certificate_key entries will look slightly different.

Restart nginx:

systemctl restart nginx

Now going to https://app1.example.com with your browser will get to the nginx proxy and be redirected to the application running on the server app1.0. Similarly, https://app2.example.com will be directed to the application running on the server app2.0.

Notes

You may also want to diable buffering on the proxy with proxy_buffering off;. NGINX provides documentation for the many reverse proxy settings[^4][^5]. Don’t touch the lines # managed by Certbot.

[^1]: wikipedia.org: TLS termination proxy [^2]: docs.mpcdf.mpg.de Automated domain name service [^3]: nginx.org instlalation instructions [^4]: certbot.eff.org instructions [^5]: docs.nginx.com setting up an NGINX reverse proxy [^6]: nginx.com NGINX proxy module parameter reference