Setting up a reverse proxy to aid server development

Back to index

There are different use-cases for this. Mine was that I have a monolith server which I want to split up for a rewrite, but I did not want to rewrite it all by once. Instead I want to do it piece-by-piece so that I can have both servers running at once while doing the transition.

Other reasons might include

  • A change of architecture (to microservices or something else)
  • Split a monolith up to make it scale better
  • You want to catch a hard-to-catch bug that only appears on your actual backend and not on your locally running server
  • Any other reason, sky's the limit here

One might wonder how this can be done. When you think about it, it would be obvious that you need to run both servers at once and have both respond to a single client during the transition period. You can do this with a reverse proxy that you can run locally, to which your client will connect to and which will then route the traffic to the correct server by the configuration. Here's how I configured mine.

Nginx as a reverse proxy

Nginx is a nice piece of software that can be pretty easily configured to do this task. Essentially you define the paths that your locally running dev server should respond to, and the rest are catched and sent to the other server.

First you need to define both of your servers as upstream in the nginx config

upstream backend {
    server <backend-address>:<backend-port>;
}

upstream new-backend {
    server host.docker.internal:5000;
}

The address host.docker.internal is a special docker feature that will allow you to access your host machine from inside a docker container. You can also use something else here, if you for example are running the new server in another docker container. I'm not, so I'm using this address.

Then you define the URL pattern that is sent to the new backend and the catch-the-rest pattern that will be sent to the old backend like this

location ~* ^/master/api/v1/(races.*) {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host            <backend-address>;
    proxy_pass http://new-backend/$1$is_args$args;
}

location ~* ^/(.*) {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host            <backend-address>;
    proxy_pass http://backend/$1$is_args$args;
}

In this example I am catching /master/api/v1/races* to be sent to the new backend running on my local machine, and then catching the reset to be forwarded to the old backend. There is also additional config to catch query string and add that to the request the reverse proxy does.

Here's my full nginx configuration file for reference

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;

events {
    worker_connections  16;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$proxy_host" "$upstream_addr"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout 65;

    upstream backend {
        server <backend-address>:80;
    }

    upstream localbox {
        server host.docker.internal:5000;
    }

    server {
        listen 80;
        server_name <backend-address>;

        location ~* ^/master/api/v1/(races.*) {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host            <backend-address>;
            proxy_pass http://localbox/$1$is_args$args;
        }

        location ~* ^/(.*) {
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header Host            <backend-address>;
            proxy_pass http://backend/$1$is_args$args;
        }
    }
}

Run it in a docker container

As I said I'm running the reverse proxy in a docker container to make it easier to manage, and easier for others to adopt. Here's my docker-compose.yml configuration

services:
  reverse-proxy:
    image: nginx:1.21.1
    ports: 
      - "8000:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./logs:/var/log/nginx # To save logs
    networks:
      - inter # to access host PC via host.docker.internal
      - ext   # to access container via localhost from the host PC
    command: [nginx-debug, '-g', 'daemon off;']
networks:
 inter:
  internal: true
 ext:

Then I can run this with

docker-compose up -d

And that's it! Now you should be able to configure your client to target this reverse proxy which will then route the traffic to the correct server.

Back to index