Dark mode
Deploy on a public server

Deploy on a public server

This guide takes a Helpin installation from localhost to your own domains with HTTPS. By default, Helpin runs its own Caddy proxy, which gets and renews certificates automatically, so you only need DNS and two open ports. If you already run nginx, Traefik, or a load balancer, you can use that instead.

What you'll set up

Hostname (example)

Serves

Upstream

app.example.com

Staff dashboard, API, and chat widget

127.0.0.1:8085

help.example.com

Public help center

127.0.0.1:8086

files.example.com

Attachments and images

127.0.0.1:9005

The widget shares the dashboard hostname by default.

1. Point DNS at your server

Create A records for all three hostnames with your server's IP address. Add AAAA records only if IPv6 actually works on the server. A CNAME must point to a hostname, not a URL.

Open ports 80 and 443 at your firewall or cloud security group. Nothing else needs to be public.

2. Configure Helpin for server mode

helpin configure --dir /srv/helpin --mode server

The wizard asks for:

  • The three hostnames above.

  • How HTTPS is handled. Choose builtin (the default) and Helpin runs Caddy on ports 80 and 443. It trusts only that proxy's fixed address, so there's nothing to look up. Optionally pass --acme-email you@example.com for certificate expiry notices. Choose external to use your own proxy (see step 3).

Using your own proxy? The wizard also asks for the exact IP or CIDR your proxy appears from, as seen by Helpin's ingress container. For a proxy on the same host, that's usually the Docker network's gateway, not 127.0.0.1. Find it with:

docker network inspect "$(docker network ls -q --filter name=helpin | head -1)" \
  --format '{{range .IPAM.Config}}{{.Gateway}}{{end}}'

Never use 0.0.0.0/0. Helpin trusts forwarded client IPs and protocol only from this address. That protects rate limits and HTTPS detection.

This writes the public URLs to .env and generates community/Caddyfile. With built-in HTTPS, skip to step 4. Ports 80 and 443 must be free on the server, so stop any existing web server first.

3. Using your own proxy (optional)

Caddy on the host

Caddy obtains and renews certificates automatically. Install the generated file as your Caddy configuration, or merge it into an existing one:

app.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8085
}

help.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:8086
}

files.example.com {
    # Keep the Host header: signed file URLs depend on it
    reverse_proxy 127.0.0.1:9005
}

Reload Caddy, and make sure its data directory is persistent so certificates survive restarts.

nginx

nginx works too. For each hostname, proxy to the same upstreams and:

  • Forward WebSockets: proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade";

  • Disable buffering for streaming responses: proxy_buffering off;

  • Allow long-lived connections: proxy_read_timeout 3600s;

  • Preserve the Host header, and set X-Forwarded-Proto. Replace, don't append, any incoming forwarded headers.

server {
  listen 443 ssl http2;
  server_name app.example.com;
  # ssl_certificate ... ssl_certificate_key ...

  location / {
    proxy_pass http://127.0.0.1:8085;
    proxy_http_version 1.1;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_buffering off;
    proxy_read_timeout 3600s;
  }
}

4. Apply and verify

helpin restart --dir /srv/helpin
helpin doctor --dir /srv/helpin

With built-in HTTPS, Caddy requests certificates on first start. That only succeeds once your DNS records point at this server and ports 80 and 443 are reachable from the internet. Watch progress with helpin logs caddy.

Doctor checks public HTTPS with normal certificate validation and confirms the public API matches this installation. Then test the parts doctor can't:

  1. Sign in at https://app.example.com.

  2. Add a real website's origin in Settings → Chat widget, embed the snippet there, and exchange messages live.

  3. Send an attachment in both directions.

  4. Confirm a site that isn't on the allowed list receives 403.

5. Finish setup

Never expose these

PostgreSQL, Redis, NATS, Temporal, Agent Runtime, the storage console, and /api/internal/ routes must stay private. The published ports bind to 127.0.0.1. Keep it that way, and put a containerized proxy on an explicitly private network.

Troubleshooting

Problem

Check

Certificate errors

DNS A/AAAA records, ports 80/443 reachable, and Caddy's persistent storage

Dashboard loads, but live updates don't

WebSocket headers, buffering, and read timeout on the proxy

Wrong client IPs or HTTP links

COMMUNITY_TRUSTED_PROXY_CIDR matches your proxy's real source address

Attachments fail

files.example.com is public over HTTPS, and your site's CSP allows it

Can't reach the dashboard

helpin status, then the helpin-api and helpin-migrate logs

With built-in HTTPS:

  • "Port 80 (or 443) is already in use": another web server is running. Stop it, or choose --proxy external and put Helpin behind it.

  • Certificates aren't issued: check helpin logs caddy. Let's Encrypt must reach port 80 or 443 on every hostname, so check DNS and your cloud firewall.

  • Network address conflict on start: the bundled proxy network uses 172.30.255.0/28. If that collides with an existing network, change EDGE_SUBNET, EDGE_IP_RANGE, and EDGE_PROXY_IP in .env. The proxy address must be inside the subnet but outside the range. Then run helpin configure and helpin restart.

Was this article helpful?