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:
| App | Env | Default | When |
|---|---|---|---|
| Frontend | WEB_PORT | 3000 | Always |
| Backend | API_PORT | 3010 | separate architecture only |
| Docs | DOCS_PORT | 3030 | When config.docs.enabled |
Routing rules
/) to the frontend on WEB_PORT.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./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.
| Proxy | How to strip |
|---|---|
| Nginx | Trailing slash on both location /docs/ and proxy_pass …:3030/ |
| Dokploy / Traefik / managed | Add 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, thencf-connecting-ip, thenx-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;
}
}