Skip to content

Reverse Proxy

PegaProx works behind nginx, Apache, Caddy, or any other reverse proxy. A production-ready nginx configuration is included at examples/nginx.conf.

Setup

  1. Enable Settings → Server → Behind Reverse Proxy in PegaProx. This disables built-in SSL and binds to 127.0.0.1 only.

  2. Copy examples/nginx.conf to /etc/nginx/sites-available/pegaprox and adjust server_name and SSL certificate paths.

  3. Enable the site: ln -s /etc/nginx/sites-available/pegaprox /etc/nginx/sites-enabled/

  4. Test and reload: nginx -t && systemctl reload nginx

Docker: also set PEGAPROX_BEHIND_PROXY=true

In a container deployment, set the environment variable PEGAPROX_BEHIND_PROXY=true in addition to (or instead of) the Settings toggle above.

  • The container healthcheck reads this variable to choose its probe protocol. If you enable reverse-proxy mode only via the UI toggle without the env var, the healthcheck still probes https:// against the now-plain-HTTP socket - harmless (it falls back to HTTP and the container stays healthy), but it spams TLS-handshake noise (Invalid HTTP method '\x16\x03\x01…') into the container logs.
  • The env var also routes the console (VNC/SSH) WebSockets through the proxy port, so consoles work without separately flipping the UI toggle.

nginx Configuration

The included config handles all PegaProx components: main app, SSE live updates, VNC console WebSocket, and SSH console WebSocket.

upstream pegaprox_app {
    server 127.0.0.1:5000;
}
upstream pegaprox_vnc {
    server 127.0.0.1:5001;
}
upstream pegaprox_ssh {
    server 127.0.0.1:5002;
}

server {
    listen 80;
    server_name pegaprox.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name pegaprox.example.com;

    ssl_certificate     /etc/ssl/certs/pegaprox.pem;
    ssl_certificate_key /etc/ssl/private/pegaprox.key;
    ssl_protocols       TLSv1.2 TLSv1.3;

    client_max_body_size 100g;   # match PegaProx upload limit for ISOs
    proxy_connect_timeout 10;
    proxy_read_timeout    3600;
    proxy_send_timeout    3600;

    # Main app + WebSocket
    location / {
        proxy_pass http://pegaprox_app;
        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_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }

    # SSE - buffering must be off
    location /api/sse/ {
        proxy_pass http://pegaprox_app;
        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_buffering off;
        proxy_cache off;
        proxy_read_timeout 86400;
    }

    # VNC console websocket (port 5001)
    location ~ ^/api/clusters/.*/vncwebsocket {
        proxy_pass http://pegaprox_vnc;
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $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_read_timeout 3600;
    }

    # SSH console websocket (port 5002)
    location ~ ^/api/clusters/.*/shellws {
        proxy_pass http://pegaprox_ssh;
        proxy_http_version 1.1;
        proxy_set_header Upgrade    $http_upgrade;
        proxy_set_header Connection $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_read_timeout 3600;
    }
}

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

PegaProx trusts X-Forwarded-For only from loopback addresses (127.0.0.1 / ::1). If the reverse proxy runs on a different host, add its IP to Settings → Server → Trusted Proxies.

The client_max_body_size must match PegaProx's upload limit (default 100 GB). Without this, nginx rejects large ISO uploads with a 413 error.

Traefik Configuration

Add the following labels to your PegaProx Docker Compose service:

yaml

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.pegaprox.rule=Host(`pegaprox.example.com`)"
  - "traefik.http.routers.pegaprox.entrypoints=websecure"
  - "traefik.http.routers.pegaprox.tls.certresolver=letsencrypt"
  - "traefik.http.services.pegaprox.loadbalancer.server.port=5000"
  - "traefik.http.services.pegaprox.loadbalancer.server.scheme=https"
  - "traefik.http.services.pegaprox.loadbalancer.serversTransport=insecureTransport@file"

Note: Traefik needs serversTransport with insecureSkipVerify: true because PegaProx uses self-signed certs internally.

WebSocket Support for VNC/SSH

VNC and SSH WebSocket connections need to be routed to separate backend ports (5001 and 5002). Add to Traefik dynamic config (file provider):

yaml

http:
  routers:
    pegaprox_vnc:
      entrypoints: ["https"]
      rule: "Host(`pegaprox.example.com`) && PathRegexp(`/api/clusters/[a-z0-9]+/vms/.+/vncwebsocket`)"
      service: pegaprox_vnc
      tls:
        certResolver: letsEncrypt
    pegaprox_ssh:
      entrypoints: ["https"]
      rule: "Host(`pegaprox.example.com`) && PathRegexp(`/api/clusters/[a-z0-9]+/nodes/.+/shellws`)"
      service: pegaprox_ssh
      tls:
        certResolver: letsEncrypt
  services:
    pegaprox_vnc:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:5001"
    pegaprox_ssh:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:5002"

The VNC/SSH routers must be defined before the main catch-all router so Traefik matches them first. Adjust ports if PegaProx runs on non-default ports.

Nginx Proxy Manager (NPM)

NPM does not natively support regex location blocks or SSE tuning through its GUI. You need two extra steps: a Custom Location for SSE and Custom Nginx Configuration for WebSockets.

Step 1: SSE Custom Location

In your proxy host, add a Custom Location for /api/sse/ pointing to port 5000. NPM already injects proxy_set_header Host, X-Forwarded-For and X-Forwarded-Proto into the location block it generates, so this Custom Location only needs the SSE-specific tuning - do not re-add proxy_set_header Host here:

nginx

proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400;

Why no Host line? Adding proxy_set_header Host $host; on top of the one NPM already sets produces a doubled Host header, which the WSGI layer folds into Host: your-domain, your-domain. Builds up to 0.9.15 then reject the body-less SSE-token POST with 403 CSRF validation failed. PegaProx tolerates the folded header from the next release onward (issue #626), but the cleanest setup is simply not to duplicate the directive.

Step 2: Custom Nginx Configuration

In your proxy host's Advanced → Custom Nginx Configuration field, paste the following for WebSocket support (VNC + SSH consoles) and upload limits:

nginx

client_max_body_size 100g;
proxy_connect_timeout 10;
proxy_read_timeout 3600;
proxy_send_timeout 3600;

location ~ ^/api/clusters/.*/vncwebsocket {
    proxy_pass http://YOUR_IP:5001;
    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_read_timeout 3600;
}

location ~ ^/api/clusters/.*/shellws {
    proxy_pass http://YOUR_IP:5002;
    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_read_timeout 3600;
}

Step 3: NPM Settings

  1. Enable "Websockets Support" in the proxy host settings.

  2. Replace YOUR_IP with PegaProx's internal IP address (e.g., 172.17.0.2 for Docker, or 127.0.0.1 if on the same host).

  3. Set the proxy host scheme to https and port to 5000.

Docker users: Do not enable the "Behind Reverse Proxy" option in PegaProx with 0.0.0.0 as trusted proxy - this trusts all IPs. Instead, add your reverse proxy's subnet (e.g., 172.17.0.0/16) to Settings → Server → Trusted Proxies.

Port Reference

PegaProx uses three ports. VNC and SSH are always relative to the main port:

Service Default Port Formula
Web UI + API 5000 main_port
VNC Console 5001 main_port + 1
SSH Console 5002 main_port + 2

If you changed the main port (e.g., to 443), adjust VNC (444) and SSH (445) accordingly in your reverse proxy config.