Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Reverse proxy, TLS and OAuth callbacks

6. Reverse proxy and TLS

The server speaks plain HTTP and has no TLS listener. Terminate TLS at Caddy, Nginx, Traefik or Cloudflare. FRONTEND_URL must be the public HTTPS origin, with no path prefix: the app assumes it owns /, /public/v1, /mcp, /oauth, /.well-known and /uploads.

6.1 Caddy

social.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:5000 {
        flush_interval -1
        header_up X-Forwarded-Proto {scheme}
    }
}

Caddy obtains and renews certificates automatically and appends the client address to X-Forwarded-For, so set TRUSTED_PROXY_HOPS=1 when Rustiz is reachable only through Caddy.

flush_interval -1 disables response buffering, which matters for subscriptions/listen and long MCP responses.

6.2 Nginx

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

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

    client_max_body_size 1024m;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_http_version 1.1;

        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_request_buffering off;
        proxy_buffering off;
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

$proxy_add_x_forwarded_for appends the peer address; one Nginx hop means TRUSTED_PROXY_HOPS=1. If Nginx is itself behind Cloudflare, see §6.3.

6.3 Cloudflare

  • Use SSL/TLS → Full (strict) with a Cloudflare Origin Certificate or a valid public certificate on the origin.
  • Cloudflare’s proxy has a 100-second origin response timeout on most plans. Normal API calls and most MCP tool calls fit, but a long-lived subscriptions/listen stream may be cut. Options: give MCP a DNS-only hostname, keep the stream short and let the client reconnect, or use an Enterprise plan with a longer timeout.
  • Cloudflare’s request-body limit depends on plan (commonly 100 MB). Set MAX_UPLOAD_MB and the origin proxy limit to match; otherwise large video uploads fail at the edge before reaching Rustiz.
  • Do not enable “Under Attack” or a challenge on /mcp; agents cannot solve it. Use a WAF skip rule for /mcp, /oauth/* and /.well-known/* if needed.
  • With Cloudflare → Nginx → Rustiz there are two appended XFF entries. Set TRUSTED_PROXY_HOPS=2 and firewall the origin so it is reachable only from Cloudflare. With Cloudflare connecting directly to Rustiz, 1 is correct. The limiter does not verify the peer address; the hop count is only safe when the origin cannot be reached directly.

6.4 OAuth callback URLs

Every provider’s redirect URI is built as:

{FRONTEND_URL}/integrations/social/{identifier}

FRONTEND_URL is trimmed of trailing slashes, so register the URI without a trailing slash unless the provider’s table explicitly shows one. The exact paths and exception cases are in the master provider table; the important ones:

  • tiktok-business ends with / (.../tiktok-business/); the platform requires it and the token exchange sends the same value.
  • mastodon-custom reuses /integrations/social/mastodon.
  • x uses /integrations/social/x, unless X_URL overrides the base.
  • threads, slack, vk, tiktok, tiktok-business and instagram-standalone wrap the URI as https://redirectmeto.com/<FRONTEND_URL>/... when FRONTEND_URL does not contain the substring https. In production this should never happen; use HTTPS.
  • facebook, instagram, pinterest, reddit, linkedin, youtube, gmb, tumblr, twitch, discord, dribbble, whop and mewe are registered directly; several of them refuse plain http:// redirects even on localhost.

The callback route is GET /integrations/social/{provider}. It reads code and state from the query, consumes the one-time state row, exchanges the code and stores the channel (access tokens in plain text; customFields credentials and custom-instance client data encrypted). OAuth state lives for one hour; Telegram/Moltbook connection ids also one hour; Farcaster signers 30 minutes. A second use of the same state fails with This connection link expired, please start again.

The server does not render a dashboard. The callback renders a minimal HTML “Channel connected” / “Could not connect the channel” page.

6.5 Local OAuth without a public server

rustiz channels connect <provider> and rustiz mcp can receive the platform callback themselves, but only when FRONTEND_URL is plain HTTP on localhost or 127.0.0.1. They bind 127.0.0.1:<port> and wait. If the port is already in use by another rustiz using the same database, the command warns and waits for that process to finish the login.

If FRONTEND_URL is a public HTTPS URL or a non-loopback host, the CLI cannot receive the callback; it prints the login URL and tells you the server must handle it.