GenerateSaaS

Reverse Proxy

Front the long-running apps with a proxy that routes by host and path, terminates TLS, and strips the docs prefix.

On the node target a reverse proxy sits in front of your long-running apps and routes each request to the right port by host and path. It also terminates TLS and forwards the client IP so the backend can rate-limit correctly.

Ports

Each app listens on its own port, set by an env var:

AppEnvDefaultWhen
FrontendWEB_PORT3000Always
BackendAPI_PORT3010separate architecture only
DocsDOCS_PORT3030When config.docs.enabled

Routing rules

Route the root domain (/) to the frontend on WEB_PORT.
On separate, route the backend on API_PORT via either a subdomain (api.yourdomain.com → forward root) or a path (yourdomain.com/api/ → forward /api). The backend's base path is derived from API_URL: a path like …/api makes it serve under /api, a bare subdomain makes it serve at root. See Separate backend.
When docs are enabled, route /docs to the docs app on DOCS_PORT - and strip the /docs prefix before forwarding (see below).

Stripping the /docs prefix

The docs app serves its pages at root (/), not under /docs - the public prefix is added by the proxy, not the app. The proxy must drop the /docs prefix before forwarding, or every page 404s.

ProxyHow to strip
NginxTrailing slash on both location /docs/ and proxy_pass …:3030/
Dokploy / Traefik / managedAdd a strip-path rule for /docs

Keep config.docs.url (default /docs) pointed at the public docs URL so generated links resolve. Skip this section entirely when config.docs.enabled is false - the docs app is not deployed.

TLS and client IP

  • Terminate TLS at the proxy; the apps speak plain HTTP behind it.
  • Forward the client IP - the backend rate-limits by it, which is otherwise the proxy's address. It reads x-forwarded-for, then cf-connecting-ip, then x-real-ip (first match wins), so any one of these headers works.

Example: Nginx

A minimal config routing all three apps and stripping the docs prefix:

server {
  listen 443 ssl;
  server_name yourdomain.com;

  # TLS termination
  ssl_certificate     /etc/ssl/certs/yourdomain.crt;
  ssl_certificate_key /etc/ssl/private/yourdomain.key;

  # Frontend (root)
  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
  }

  # Docs - trailing slashes strip the /docs prefix
  location /docs/ {
    proxy_pass http://127.0.0.1:3030/;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }

  # Backend (separate architecture only) - path-based; no trailing slash
  # keeps the /api prefix, so set API_URL to https://yourdomain.com/api
  location /api/ {
    proxy_pass http://127.0.0.1:3010;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

On this page