Byte Ebi's Logo

Byte Ebi 🍀

A Bit everyday A Byte every week

Reverse Proxy Setup in Docker Environment

You may know about reverse proxies, but have you tried setting them up in Docker?

Ray

This article provides a basic explanation of reverse proxies and how to set them up in a Docker container environment.

Reverse Proxy Overview

System Design - Forward Proxy vs Reverse Proxy

Different Physical Hosts

Using nginx proxy-pass

Objective

  • Users visit a specified web page path /new_path or the root /
  • Configure the test machine’s nginx proxy_pass to redirect to another host’s project under the same path
  • Ensure that the user’s IP address is not replaced by the proxy server’s IP
  • Ensure that the project on the target server can read cookies
  • Ensure that the payload of POST requests is forwarded

Configure nginx config

Configuration on the First Host’s nginx

# Forward all requests to domain/new_path to another host
location /new_path {
    # Proxy to the specified URL
    proxy_pass http://laravel55.test.com;

    # Send the user's real IP to the target host
    proxy_set_header X-Real-IP $remote_addr;

    # Set the server as the original server before the proxy, otherwise HTTP_HOST will become the address of the proxy server
    proxy_set_header Host $host;

    # Set cookie forwarding
    proxy_set_header Cookie $http_cookie;

    # Identify the original client's IP
    proxy_set_header X-Forwarded-Host $host;

    # Record each proxy passed through by the client
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    # Identify the network connection protocol
    proxy_set_header X-Forwarded-Proto $scheme;

    # Hide server information
    proxy_hide_header Server;
    proxy_hide_header X-Powered-By;

    # Increase space due to additional headers
    proxy_headers_hash_bucket_size 128;
    proxy_headers_hash_max_size 512;
}

Configuration on the Second Host’s nginx

server {
    listen 80;
    server_name ray200;
    root /home/toc/www/laravel55/public;

    access_log /var/log/nginx/www/laravel55.access.log;
    error_log /var/log/nginx/www/laravel55.error.log;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~* \.(jpg|jpeg|gif|css|png|js|ico|html|svg|ttf|woff|woff2|eotD)$ {
        access_log off;
        expires max;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass 127.0.0.1:9000; 
    }
}

Note

  • server_name must match the domain to which proxy_pass redirects.
    Otherwise, the request will go to the default server_name.
  • Modify the method of determining the real IP address in the program to read x-real-ip from the header.

If the nginx configuration includes proxy_set_header Cookie $http_cookie;, dump($_COOKIE); will capture cookies, but Laravel’s request()->cookie(); won’t.

This is because Laravel’s default web middleware \App\Http\Middleware\EncryptCookies::class, requires encrypted cookies.
Either add the cookie name to $except in the EncryptCookies class or comment out the middleware to resolve the issue.


Using Docker

This section focuses on setting up reverse proxies in a Docker environment.

Architecture Diagram

Infrastructure

Reference: How to set up NGINX Docker Reverse Proxy?

Steps

1. Create a network manually

The reverse proxy redirects requests based on the loaded configuration file to the specified container names. Therefore, the target containers for proxy must be in the same network to use container names directly.

Manually create a network to avoid adding prefixes and dependencies:

docker network create nginx-network

Since this scenario involves an external nginx-proxy, an additional external network is needed for connections.
All services that need to connect to this network must be configured:

networks:
  default:
    external:
      name: nginx-network

This allows services from different docker-compose.yml files to communicate with each other. Official documentation

2. Set up a test service

To simplify, use an existing service like “portainer” as the proxy target. This service is launched on port 8080.
After starting the service, check if portainer is accessible via localhost:8080.

version: '3'

networks:
  default:
    external:
      name: nginx-network

services:
  portainer:
    container_name: nginx_test_portainer
    image: portainer/portainer-ce
    command: -H unix:///var/run/docker.sock
    restart: always
    ports:
      - 8080:9000
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data

volumes:
  portainer_data:

3. Configure Nginx proxy

3.1 Configure docker-compose.yml

version: '3'

networks:
  default:
    external:
      name: nginx-network

services:
  nginx:
    container_name: test_nginx
    image: nginx:1.18
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./conf.d/:/etc/nginx/conf.d/
      - ./ssl/:/ssl/

3.2 Mount the nginx config for use by the reverse proxy server

In the configuration, all requests to “test.example” are redirected to http://nginx_test_portainer:9000. The format used is:

http://{container_name}:{port}

server {
    listen 80;
    listen [::]:80;
    server_name test.example;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name test.example;

    ssl_certificate /ssl/ssl.crt;
    ssl_certificate_key /ssl/ssl.key;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        proxy_set_header Host $host;
        proxy_set_header Cookie $http_cookie;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_pass http://nginx_test_portainer:9000;
    }
}

Ensure to include http:// in the proxy_pass target URL.
If the redirected URL has its own HTTPS certificate validation (e.g., another nginx environment), it must start with https://.

Reference: What value should I use for Nginx proxy_pass running in Docker?

3.3 Edit etc/hosts

127.0.0.1       test.example

Start the nginx service.

If configured correctly, the nginx service listening on port 80 should forward all requests to “test.example” to the portainer container’s specified port, displaying the portainer service’s interface.

Start the reverse proxy service after all services are running, or it will throw errors indicating that the target container for proxy doesn’t exist.

Notes

If you want to handle all unmatched server_name requests uniformly, add default_server to the specified nginx config.

If default_server is not defined, the first server will be set as the default server by default.

server {
    listen 8080 ssl default_server;
    listen [::]:8080 ssl default_server;

    ssl_certificate /ssl/ssl.crt;
    ssl_certificate_key /ssl/ssl.key;

    # Virtual Host Domain
    server_name _;

    # where code is
    root /var/project/dev/www/my_backend/public;
    index index.php index.html index.htm;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        try_files $uri /index.php =404;
        fastcgi_pass dev_php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }
}

This scenario occurs when the frontend’s API configuration file uses a path address specified by the backend container name.
Since the request is sent directly to the container without passing through the reverse proxy, and there are multiple server config settings in the nginx container, nginx does not know which config to use if there is no server_name.
Therefore, “dev_backend” is set as default_server to handle such requests.

Recent Posts

Categories

Tags