Skip to main content

NGINX Load Balancing

For production deployments, you'll often want to use NGINX as a reverse proxy and load balancer for Browserless Docker containers. It provides essential benefits including SSL termination, traffic distribution across multiple instances, and enhanced reliability through health monitoring and automatic failover.

How NGINX Works as a Load Balancer

NGINX acts as a reverse proxy that sits between your clients and Browserless instances. When a request arrives, NGINX:

  1. Receives the incoming connection - Accepts HTTP/HTTPS requests and WebSocket connections from clients
  2. Routes to a backend - Selects one of the healthy Browserless instances based on the configured load balancing algorithm (e.g., least connections, round-robin)
  3. Forwards the request - Proxies the request to the selected Browserless container, maintaining WebSocket connections for browser automation
  4. Handles failures - If a backend instance fails or returns an error, automatically retries the request on another healthy instance
  5. Returns the response - Sends the backend's response back to the client

This architecture allows you to scale horizontally by adding more Browserless containers without changing your client code, while NGINX ensures traffic is distributed efficiently and failures are handled gracefully.

Why Use NGINX with Browserless?

  • Load Balancing: Distribute traffic across multiple browserless containers
  • SSL Termination: Handle HTTPS certificates at the proxy level
  • Health Checks: Automatically retry failed requests to healthy instances
  • WebSocket Support: Properly handle WebSocket connections for Puppeteer and Playwright
  • High Availability: Prevent single points of failure

Docker Compose Setup

Here's a complete example using Docker Compose with NGINX load balancing two browserless instances:

docker-compose.yml

version: '3.8'

services:
nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
ports:
- "80:80"
depends_on:
- browserless_one
- browserless_two
restart: unless-stopped

browserless_one:

image: registry.browserless.io/browserless/browserless/enterprise:latest
environment:
- TOKEN=your-token-here
- CONCURRENT=10
restart: unless-stopped

browserless_two:
image: registry.browserless.io/browserless/browserless/enterprise:latest
environment:
- TOKEN=your-token-here
- CONCURRENT=10
restart: unless-stopped

nginx.conf

upstream browserless {
least_conn;
server browserless_one:3000;
server browserless_two:3000;
}

server {
listen 80;

location / {
proxy_pass http://browserless;
proxy_next_upstream error timeout http_500 http_503 http_429 non_idempotent;

# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;

# Extended timeouts for long-running browser operations
proxy_connect_timeout 900;
proxy_send_timeout 900;
proxy_read_timeout 900;
send_timeout 900;
}
}

Configuration Explanation

Upstream Block

The upstream block defines the backend browserless instances:

  • least_conn: Routes requests to the server with the fewest active connections
  • server browserless_one:3000: References the Docker service name and internal port
  • Multiple servers provide redundancy and load distribution

Server and Location Blocks

The server and location blocks handle incoming requests:

  • listen 80: Listens for HTTP traffic on port 80
  • proxy_pass http://browserless: Routes requests to the upstream backend pool
  • proxy_next_upstream: Automatically retries failed requests (errors, timeouts, 500/503/429 responses) on other servers
  • proxy_http_version 1.1: Required for WebSocket support
  • Upgrade and Connection headers: Essential for WebSocket connections used by Puppeteer and Playwright
  • Extended timeouts (900 seconds): Accommodate long-running browser operations like scraping or PDF generation

Starting the Setup

  1. Create the nginx.conf file with the configuration above
  2. Create the docker-compose.yml file
  3. Start the services:
docker-compose up

Your browserless instances will be available at http://localhost with automatic load balancing.

Advanced Configuration

Health Checks

Enable health checks in Browserless to make load balancing more intelligent. When CPU or memory usage is high, Browserless will reject requests, causing NGINX to automatically route traffic to healthier instances:

# docker-compose.yml
browserless_one:
image: registry.browserless.io/browserless/browserless/enterprise:latest
environment:
- TOKEN=your-token-here
- CONCURRENT=10
- HEALTH=true

browserless_two:
image: registry.browserless.io/browserless/browserless/enterprise:latest
environment:
- TOKEN=your-token-here
- CONCURRENT=10
- HEALTH=true

SSL/HTTPS Configuration

For production deployments, add SSL support. Update your nginx.conf to listen on port 443 and include SSL certificates:

upstream browserless {
least_conn;
server browserless_one:3000;
server browserless_two:3000;
}

server {
listen 443 ssl;
server_name your-domain.com;

ssl_certificate /path/to/your/certificate.pem;
ssl_certificate_key /path/to/your/private-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;

location / {
proxy_pass http://browserless;
proxy_next_upstream error timeout http_500 http_503 http_429 non_idempotent;

# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;

# Extended timeouts
proxy_connect_timeout 900;
proxy_send_timeout 900;
proxy_read_timeout 900;
send_timeout 900;
}
}

# Optional: Redirect HTTP to HTTPS
server {
listen 80;
server_name your-domain.com;
return 301 https://$server_name$request_uri;
}

Also update your docker-compose.yml to expose port 443:

nginx:
image: nginx:alpine
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- /path/to/certificates:/etc/nginx/certs:ro
ports:
- "80:80"
- "443:443"
depends_on:
- browserless_one
- browserless_two
restart: unless-stopped

Browserless Proxy Awareness

When using NGINX in front of Browserless, configure Browserless to be aware of the proxy for proper link generation:

# docker-compose.yml
browserless_one:
image: registry.browserless.io/browserless/browserless/enterprise:latest
environment:
- TOKEN=your-token-here
- CONCURRENT=10
- EXTERNAL=https://your-domain.com

The EXTERNAL variable should be set to the fully-qualified URL (including protocol and port if non-standard) where your Browserless instances are accessible externally. See the Docker Configuration documentation for more details.

Scaling to More Instances

To add more Browserless instances, simply add more services to your docker-compose.yml and update the nginx upstream block:

# docker-compose.yml
services:
nginx:
# ... nginx config
depends_on:
- browserless_one
- browserless_two
- browserless_three
- browserless_four

browserless_one:
# ... config

browserless_two:
# ... config

browserless_three:
# ... config

browserless_four:
# ... config
# nginx.conf
upstream browserless {
least_conn;
server browserless_one:3000;
server browserless_two:3000;
server browserless_three:3000;
server browserless_four:3000;
}

Testing Your Setup

You can test your load-balanced setup by connecting with Puppeteer:

const puppeteer = require('puppeteer');

const browser = await puppeteer.connect({
browserWSEndpoint: 'ws://localhost:80?token=your-token-here'
});

const page = await browser.newPage();
await page.goto('https://example.com');
const title = await page.title();
console.log(title);

await browser.close();

The requests will be automatically distributed across your browserless instances by NGINX.