How to Self-Host Multica with a Custom Domain and Reverse Proxy: Complete Setup Guide
Self-host Multica by deploying its three-core service stack behind an HTTPS-terminating reverse proxy, configuring environment variables for your custom domains, and rebuilding the frontend to bake in the public API URLs.
Multica is an open-source AI agent platform written in Go and Next.js. When you self-host the multica-ai/multica repository, you run PostgreSQL, a Go backend API, and a Next.js frontend on your own infrastructure. Exposing these services through a custom domain requires specific environment configuration and a reverse proxy to handle TLS termination, CORS headers, and WebSocket upgrades.
Deploy the Core Services
Start by cloning the repository and launching the pre-configured Docker Compose stack. The project ships with docker-compose.selfhost.yml, which defines services for PostgreSQL 17 (with pgvector), the Go backend, and the Next.js frontend.
git clone https://github.com/multica-ai/multica.git
cd multica
cp .env.example .env
# Edit .env to set your custom domains (see next section)
docker compose -f docker-compose.selfhost.yml up -d
By default, the backend listens on port 8080 and the frontend on port 3000. These internal ports remain inaccessible to the internet until you place a reverse proxy in front of them.
Configure Environment Variables for Custom Domains
Before starting the stack, edit the .env file to inform the backend and frontend about your public URLs. According to SELF_HOSTING.md (lines 66-77 and 45-53), you must set the following variables:
# Public origins for CORS and URL generation
FRONTEND_ORIGIN=https://app.example.com
CORS_ALLOWED_ORIGINS=https://app.example.com
# URLs advertised to the daemon and CLI clients
MULTICA_APP_URL=https://app.example.com
MULTICA_SERVER_URL=wss://api.example.com/ws
# Frontend build-time configuration
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_WS_URL=wss://api.example.com/ws
REMOTE_API_URL=https://api.example.com
Critical distinction: NEXT_PUBLIC_* variables are embedded during the build process, while REMOTE_API_URL is consumed by Dockerfile.web as a build argument (lines 60-64 of docker-compose.selfhost.yml). If you change these after the first build, you must rebuild the frontend image for the changes to take effect.
Set Up the Reverse Proxy
Place an HTTPS-terminating reverse proxy in front of ports 3000 (frontend) and 8080 (backend). The proxy must forward HTTP traffic and handle WebSocket upgrades on the /ws path for daemon connections.
Option 1: Caddy (Automatic TLS)
Create a Caddyfile at /etc/caddy/Caddyfile:
app.example.com {
reverse_proxy localhost:3000
}
api.example.com {
reverse_proxy localhost:8080
}
Caddy automatically provisions TLS certificates via Let's Encrypt. No additional configuration is required for WebSocket support, as Caddy handles the Upgrade header transparently.
Option 2: Nginx (Explicit Control)
For manual certificate management, use this configuration adapted from SELF_HOSTING.md (lines 81-140):
# Frontend server block
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/ssl/certs/your-cert.pem;
ssl_certificate_key /etc/ssl/private/your-key.key;
location / {
proxy_pass http://localhost:3000;
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;
}
}
# Backend API server block
server {
listen 443 ssl;
server_name api.example.com;
ssl_certificate /etc/ssl/certs/your-cert.pem;
ssl_certificate_key /etc/ssl/private/your-key.key;
location / {
proxy_pass http://localhost:8080;
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;
}
# WebSocket support for persistent daemon connections
location /ws {
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400;
}
}
The /ws location is critical: it enables the Go backend's WebSocket handler (located in server/internal/handler) to maintain long-lived connections with CLI agents.
Build and Deploy the Frontend
If you modified REMOTE_API_URL or any NEXT_PUBLIC_* variables, you must rebuild the frontend image to embed the new API endpoints into the static Next.js bundle:
docker compose -f docker-compose.selfhost.yml build frontend
docker compose -f docker-compose.selfhost.yml up -d frontend
The build process reads REMOTE_API_URL from the environment and bakes it into the client-side JavaScript, allowing the browser to reach your custom API domain.
Verify the Installation
Test your self-hosted Multica instance using these validation steps:
- Browser verification: Navigate to
https://app.example.com. The Multica UI should load without CORS errors (permitted byFRONTEND_ORIGIN). - API health check: Request
https://api.example.com/healthto receive{"status":"ok"}. The Go backend exposes this endpoint in the handler package for load balancer health checks. - CLI configuration: Configure the Multica CLI to point to your custom domains:
export MULTICA_APP_URL=https://app.example.com
export MULTICA_SERVER_URL=wss://api.example.com/ws
multica login
The login command will open your custom domain for authentication, and daemon start will establish a WebSocket connection through your reverse proxy to wss://api.example.com/ws.
Upgrading Your Instance
When updating to the latest version, pull the repository changes and rebuild:
git pull
docker compose -f docker-compose.selfhost.yml up -d --build
Database migrations run automatically when the backend container starts, as implemented in the server initialization code referenced in SELF_HOSTING.md.
Summary
- Clone and configure: Use
docker-compose.selfhost.ymlfrom themultica-ai/multicarepository and copy.env.exampleto.env. - Set public URLs: Define
FRONTEND_ORIGIN,CORS_ALLOWED_ORIGINS,MULTICA_APP_URL,MULTICA_SERVER_URL, andNEXT_PUBLIC_*variables to match your custom domains. - Proxy traffic: Deploy Caddy or Nginx to terminate TLS and forward traffic to localhost ports
3000and8080, ensuring the/wspath supports WebSocket upgrades. - Rebuild frontend: Run
docker compose build frontendwhenever you changeREMOTE_API_URLor public API variables. - Verify: Check the
/healthendpoint and authenticate via the CLI to confirm end-to-end connectivity.
Frequently Asked Questions
Do I need to rebuild the frontend every time I change the API URL?
Yes. The NEXT_PUBLIC_API_URL and REMOTE_API_URL values are baked into the Next.js static bundle at build time. If you change these environment variables after the initial deployment, you must run docker compose -f docker-compose.selfhost.yml build frontend and restart the container for the browser to use the new endpoints.
Which reverse proxy is recommended for Multica?
Caddy is simplest for most users because it handles TLS provisioning and WebSocket upgrades automatically. However, if you require specific security headers, custom access controls, or already run Nginx in your infrastructure, the explicit Nginx configuration provided in SELF_HOSTING.md (lines 81-140) gives you finer control over the proxy behavior.
Why does my daemon fail to connect with a WebSocket error?
Check your reverse proxy configuration. The backend's WebSocket handler expects the proxy to forward the Upgrade and Connection headers on the /ws path. In Nginx, you must include proxy_http_version 1.1 and the Upgrade header directives. Caddy handles this automatically. Also verify that MULTICA_SERVER_URL uses wss:// (secure WebSocket) when HTTPS is enabled.
Can I run Multica without Docker?
Yes, but it requires manual setup. You would need to run PostgreSQL 17 with the pgvector extension, build the Go binary from Dockerfile, and build the Next.js frontend from Dockerfile.web while injecting the REMOTE_API_URL build argument. The Docker Compose stack is the officially supported and tested method for self-hosting.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →