Skip to main content
Version: Next

Reverse Proxy

Honeyframe expects to sit behind a reverse proxy that terminates TLS and routes per-domain to the backend services (Platform :8001, Cloud :8002, plus the App :8003 if a vertical app is enabled). The reference configuration uses nginx — Caddy and Traefik work but are not shipped pre-configured.

Layout

The default deployment uses one server block per domain. The standard install ships nginx.conf (server blocks) and nginx-security.conf (rate-limit zones).

FileDestination on hostPurpose
nginx.conf/etc/nginx/sites-available/honeyframeper-domain server blocks
nginx-security.conf/etc/nginx/conf.d/honeyframe-security.conflimit_req_zone directives that must live in http {}

nginx-security.conf lands in conf.d/ because the rate-limit zones it defines are referenced inside server blocks via limit_req zone=.... They must be evaluated in http {} context, which only conf.d/*.conf provides; sites-available/ is read inside an existing server block context and cannot host limit_req_zone.

Server-block anatomy

Each public domain has the same shape. The Platform domain (port 8001) is the canonical example:

server {
listen 80;
server_name platform.your-domain.com;

client_max_body_size 200M;
proxy_read_timeout 300;
proxy_connect_timeout 300;
proxy_send_timeout 300;

# Security headers — emitted on HTTP and HTTPS. HSTS is ignored on
# plain HTTP, so it's safe to set unconditionally.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Login + password change — stricter rate limit
location /api/auth/ {
limit_req zone=honeyframe_auth burst=10 nodelay;
proxy_pass http://127.0.0.1:8001/api/auth/;
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;
}

# General API
location /api/ {
limit_req zone=honeyframe_api burst=60 nodelay;
proxy_pass http://127.0.0.1:8001/api/;
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;
}

# Static frontend (built by `vite build`)
location / {
root /opt/honeyframe/paas/frontend/dist;
try_files $uri $uri/ /index.html;
}
}

Cloud and the App service follow the same template with their respective ports (8002, 8003) and dist/ paths.

Rate-limit zones

nginx-security.conf defines two buckets keyed on $binary_remote_addr:

limit_req_zone $binary_remote_addr zone=honeyframe_auth:10m rate=5r/s;
limit_req_zone $binary_remote_addr zone=honeyframe_api:10m rate=30r/s;
  • honeyframe_auth — 5 req/s sustained, burst 10. Applied to /api/auth/. Slows credential stuffing without breaking single-user logins.
  • honeyframe_api — 30 req/s sustained, burst 60. Applied to /api/. Generous enough for legitimately bursty dashboard polling.
  • 10 MB of shared memory ≈ 160k tracked client IPs per zone.

Adjust the rates if you front Honeyframe with a CDN or load balancer that masks $remote_addr — set set_real_ip_from to the trusted upstream so the rate limiter sees real client IPs.

Body size and timeouts

The client_max_body_size 200M allowance is sized for CSV upload via csv_upload connector and APK upload to the mobile distribution endpoint. The 300-second proxy timeouts cover long-running dbt runs and large dashboard queries; they do not affect normal API latency.

Content Security Policy

The App domain ships a Content-Security-Policy in report-only mode — violations are POSTed to /api/csp-report for review without breaking the page. To enforce, change Content-Security-Policy-Report-Only to Content-Security-Policy after auditing the report stream for at least 48 hours.

The default policy reflects the surfaces the frontend uses:

  • script-src 'self', plus accounts.google.com (Google Identity), cdn.jsdelivr.net (Monaco editor), cdnjs.cloudflare.com (PDFObject).
  • style-src 'self' 'unsafe-inline' (React inline-style props are unavoidable here).
  • frame-src 'self' https://accounts.google.com (Google sign-in iframe).
  • connect-src 'self' https://accounts.google.com.
  • frame-ancestors 'self' blocks third-party embedding.

If you remove a surface (e.g. you don't use Google sign-in), prune the corresponding source from the directive — narrower CSPs are stronger.

Caddy or Traefik

The same routing works in either:

  • Caddy — three reverse_proxy blocks per domain (one per port). Caddy auto-issues TLS via Let's Encrypt without certbot.
  • Traefik — three Docker labels per service (or three IngressRoutes if running on Kubernetes), each pointing at the matching backend port.

Both lack a direct equivalent to nginx's limit_req_zone. Use Caddy's rate_limit module or Traefik's RateLimit middleware.