Reverse Proxy Setup in Docker Environment
You may know about reverse proxies, but have you tried setting them up in Docker?
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.
Verify Cookie Reading
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
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.