Skip to content

Reverse proxies

PassBeyond expects a public TLS reverse proxy in front of its private HTTP listener. The edge owns certificates, HTTP-to-HTTPS redirects, request-size and abuse controls, and trusted forwarding headers.

text
Internet → public TLS proxy → private PassBeyond → private backend

The safe design has no alternate public path to PassBeyond or the backend.

Required behavior

The edge proxy must:

  • route every path for the protected hostname to PassBeyond;
  • preserve the original public Host;
  • overwrite X-Forwarded-For, X-Real-IP, and X-Forwarded-Proto;
  • remove or overwrite client-supplied forwarding values;
  • redirect plain HTTP to HTTPS;
  • enforce suitable request-body, header, connection, and rate limits;
  • support SAML POST requests to /saml/acs;
  • avoid caching authentication responses and error pages.

PassBeyond independently overwrites X-Forwarded-For and X-Real-IP before proxying to the backend. Other forwarding headers, including X-Forwarded-Proto, remain the edge proxy's responsibility.

Match trustedProxies

Only list the direct peer addresses that are allowed to supply X-Forwarded-For:

yaml
proxy:
  listenAddress: "127.0.0.1:8123"
  targetURL: "http://127.0.0.1:8888"
  trustedProxies:
    - "127.0.0.1"
    - "::1"

For containers, use the exact proxy container address or a narrow, dedicated network CIDR. Do not trust an entire shared infrastructure range when unrelated workloads can connect to PassBeyond.

nginx

Define the WebSocket connection map once in the http context:

nginx
map $http_upgrade $passbeyond_connection {
    default upgrade;
    ''      close;
}

Then configure the virtual host:

nginx
upstream passbeyond_myapp {
    server 127.0.0.1:8123;
    keepalive 32;
}

server {
    listen 80;
    listen [::]:80;
    server_name myapp.example.com;

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

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

    ssl_certificate     /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;

    # Configure HSTS only after HTTPS is verified for the whole domain.
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass http://passbeyond_myapp;
        proxy_http_version 1.1;

        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $remote_addr;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Proxy "";

        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $passbeyond_connection;

        proxy_buffering off;
        proxy_redirect off;

        client_max_body_size 16m;
    }
}

Choose client_max_body_size for the application, but remember that PassBeyond itself has a fixed 10-second request read timeout. See Runtime limits.

Rate limiting with nginx

Define a conservative zone in the http context and apply it to the public server according to expected traffic:

nginx
limit_req_zone $binary_remote_addr zone=passbeyond_auth:10m rate=5r/s;
nginx
location /saml/ {
    limit_req zone=passbeyond_auth burst=20 nodelay;

    proxy_pass http://passbeyond_myapp;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Real-IP $remote_addr;
}

When using a separate /saml/ location, repeat every required proxy header and transport option. Tune rates to the IdP and user population; an overly strict rule can block a legitimate login burst.

Apache HTTP Server

Enable the required modules:

bash
sudo a2enmod ssl proxy proxy_http headers rewrite
sudo systemctl reload apache2

Example virtual hosts:

apache
<VirtualHost *:80>
    ServerName myapp.example.com
    Redirect permanent / https://myapp.example.com/
</VirtualHost>

<VirtualHost *:443>
    ServerName myapp.example.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/myapp.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/myapp.example.com/privkey.pem

    ProxyPreserveHost On
    ProxyRequests Off

    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-Host "%{HTTP_HOST}s"
    RequestHeader set X-Forwarded-For expr=%{REMOTE_ADDR}
    RequestHeader set X-Real-IP expr=%{REMOTE_ADDR}
    RequestHeader unset Proxy

    ProxyPass        / http://127.0.0.1:8123/ connectiontimeout=5 timeout=35
    ProxyPassReverse / http://127.0.0.1:8123/
</VirtualHost>

The exact RequestHeader expression syntax depends on the Apache 2.4 build. Verify the resulting headers at a controlled backend before production.

For WebSockets, enable proxy_wstunnel where required. PassBeyond's fixed response timeout still applies.

Caddy

Caddy obtains and renews the public certificate by default:

text
myapp.example.com {
    reverse_proxy 127.0.0.1:8123 {
        header_up Host {host}
        header_up X-Forwarded-Host {host}
        header_up X-Forwarded-Proto {scheme}
        header_up X-Forwarded-For {remote_host}
        header_up X-Real-IP {remote_host}
        header_up -Proxy
    }
}

Caddy normally manages X-Forwarded-* headers itself; the explicit directives make the trust behavior visible. Confirm the direct Caddy address matches trustedProxies.

Container edge proxy

On a container network:

  • do not publish PassBeyond or backend ports to all host interfaces;
  • place the edge proxy and PassBeyond on a dedicated network;
  • keep the backend on an internal network;
  • trust only the edge address or narrow subnet;
  • ensure network recreation does not silently widen the trusted range.

See Container deployment for a topology example.

Backend TLS

For an HTTPS targetURL, PassBeyond verifies the backend certificate. Supply the issuing CA in the host or container trust store. Keep targetDisableSSLVerify: false.

The edge TLS certificate and backend TLS certificate protect different connections. A valid public certificate does not make an unverified internal backend connection safe.

Header trust checklist

  • [ ] Clients cannot reach PassBeyond or the backend directly.
  • [ ] The edge overwrites all forwarding headers.
  • [ ] Host remains the expected public application hostname.
  • [ ] trustedProxies contains only direct edge peers.
  • [ ] The backend trusts identity headers only from PassBeyond.
  • [ ] Basic authorization behavior matches useBasicAuth and ignoreAuthorizationHeader.
  • [ ] Request and rate limits have been tested with SAML POSTs and normal application traffic.
  • [ ] WebSocket, upload, and long-response behavior has been tested against PassBeyond's fixed limits.

For header details, see Forwarded headers. For failures, see Troubleshooting.

Released under the MIT License.