Why self-host your customer support
SaaS customer support solutions have a simple business model: you pay per agent and per channel, and your customer data is stored on their servers. For a web agency or an SME, this can quickly amount to several hundred dollars per month once the team exceeds two or three people. Chatwoot reverses this equation: you deploy the platform on your own VPS, invite as many agents as needed, and connect all your channels at no extra cost. Your customer exchanges stay on your infrastructure — a compelling argument for GDPR compliance, customer trust and data sovereignty.
What you get with a self-hosted Chatwoot
- Shared omnichannel inbox: livechat, email, WhatsApp, Telegram, Facebook Messenger and Twitter/X DMs in a single dashboard.
- Embeddable livechat widget: a customisable widget to add to any website with two lines of JavaScript.
- Canned responses and automation rules: automatic assignment, first-contact reply and routing by language or keyword.
- Team collaboration: internal notes, conversation assignment, mentions and team queues visible to all agents.
- Integrated CRM: contact profiles with conversation history, custom attributes and labels.
- REST API and webhooks: integration with n8n, Activepieces or your own backend.
- MIT licence — no per-agent pricing, no data sent to a third party, full sovereignty.
Prerequisites
Chatwoot runs on a Ruby on Rails + Sidekiq + PostgreSQL 15 + Redis 7 stack — four Docker containers. Plan for a VPS with at least 2 GB of RAM (4 GB recommended for teams of more than 10 agents). Ubuntu 24.04 with Docker is the fastest path. On the network side, prepare a subdomain — support.your-domain.com for example — and a reverse proxy (Nginx or Caddy) to enable HTTPS. Chatwoot requires a FRONTEND_URL over HTTPS for OAuth redirects, email links and the livechat widget script to work correctly.
Before you start, verify that Docker Compose v2 is installed (docker compose version): Chatwoot v4 uses the docker compose syntax (with a space) rather than the legacy docker-compose command.
Deploy Chatwoot on a VPS in 6 steps
One-click deployment from the ServOrbit marketplace
Open your ServOrbit control panel, go to Marketplace → Collaboration and productivity → Chatwoot, then click Deploy. Docker pulls
chatwoot/chatwoot:latestand starts four containers: PostgreSQL, Redis, the Rails web server and the Sidekiq worker. On first start, the database migration runs automatically — wait 60 to 90 seconds before the interface becomes available.For a manual installation, clone the official
docker-compose.yml, copy.env.exampleto.env, setSECRET_KEY_BASE(generate it withopenssl rand -hex 64) and run:docker compose up -d docker compose exec rails bundle exec rails db:chatwoot_prepareCreate the administrator account
Go to
http://<your-vps-ip>:3000. Chatwoot displays a first-configuration wizard: enter your name, email address and a strong password. This account becomes the super-administrator. You can then invite additional agents and create teams from the Settings panel.If the page stays blank after 90 seconds, check the web container logs:
docker compose logs web --tail=50. ASECRET_KEY_BASE not setorPG::ConnectionBaderror points to a misconfiguration in.env.Configure FRONTEND_URL and enable HTTPS
Point your domain to the VPS (A record → VPS IP). Install Caddy (
apt install -y caddy) and create/etc/caddy/Caddyfile:support.your-domain.com { reverse_proxy localhost:3000 }. Reload Caddy (systemctl reload caddy). Then setFRONTEND_URL=https://support.your-domain.comin your.envfile and restart the web container:docker compose restart web. Chatwoot uses this URL for OAuth redirects, email links and the widget script.Also enable
FORCE_SSL=truein.envso Rails redirects HTTP connections to HTTPS and setsSecurecookies.Add your first inbox
In Chatwoot, go to Settings → Inboxes → Add Inbox. Choose Website for livechat, Email for SMTP/IMAP exchanges, or a messaging channel like WhatsApp Cloud API or Telegram. For livechat, copy the generated JavaScript snippet and paste it into the
<head>of your website. Visitors immediately see the chat bubble.Invite agents and configure automation
Go to Settings → Agents and send email invitations. In Settings → Automation, create rules to automatically assign conversations (for example, WhatsApp → sales team, email → billing) and send first-contact messages outside business hours. The Sidekiq worker handles all asynchronous tasks: sending emails, triggering webhooks and push notifications.
Connect WhatsApp Business (optional)
Create a Meta for Developers app and enable the WhatsApp Business Cloud API. In Chatwoot → Settings → Inboxes → Add → WhatsApp, enter your WhatsApp Business phone number, WhatsApp Business account ID, access token and Webhook verification token. Incoming WhatsApp messages now appear in the shared inbox alongside livechat and emails.
Set up canned responses (Settings → Canned Responses) on day one: contact acknowledgement, order confirmation, processing times. Your agents save several minutes per conversation — and tone consistency is maintained regardless of who replies. Combine with automation rules to automatically send the first-contact reply at night and on weekends.
Managing multiple inboxes
Chatwoot lets you centralise multiple channels in a single interface. Each channel creates an independent inbox, visible in the sidebar and assignable to a dedicated team.
Email (SMTP/IMAP): in Settings → Inboxes → Email, enter your inbound address and IMAP credentials. Chatwoot polls the mailbox every two minutes and creates one conversation per thread. Replies are sent via SMTP while preserving the same thread.
Twitter/X DMs: connect an account via the Twitter v2 API (API key + secret + access token). Incoming direct messages appear in real time via webhook. Note: Twitter v2 API access requires a Basic or higher developer subscription.
Phone and WebRTC: Chatwoot v4.17 migrated its video call integration from Dyte to Cloudflare RealtimeKit. If you use video calls, reconfigure the integration under Settings → Integrations → Cloudflare Calls with your App ID and App Token.
API channel: for custom integrations (chatbot, in-house CRM), create an API-type inbox. It exposes a REST endpoint to receive messages and you send replies via POST /api/v1/accounts/{id}/conversations/{conv_id}/messages. This is the entry point for connecting any external source.
Chatwoot integrations: webhooks, Slack, REST API
Chatwoot offers several levels of integration to fit into your existing stack.
Webhooks: in Settings → Integrations → Webhooks, add your endpoint URL. Chatwoot fires a JSON event on every conversation created, message received or status changed. Useful for syncing tickets to a CRM, triggering an n8n workflow or logging conversations to your database.
Slack notifications: connect a Slack workspace via OAuth under Settings → Integrations → Slack. New conversations and mentions in Chatwoot generate a notification in the Slack channel of your choice. Agents never miss a message even when outside the interface.
REST API: all Chatwoot resources (conversations, contacts, messages, teams, labels) are exposed by a documented v1 REST API at /swagger. Authentication via token (user_access_token or agent API key). Common use cases: importing contacts from a CRM, reporting on resolved conversations, automatically updating contact attributes.
Zapier / Make (ex-Integromat): Chatwoot has native connectors on Zapier and Make to trigger no-code actions. Example: new Chatwoot conversation → create an opportunity in your CRM → notify the sales team by email.
Updating Chatwoot
Minor updates (patch releases like v4.17.0 → v4.17.1) are safe and often contain security fixes: plan them within 48 hours of publication.
For a patch update:
docker compose pull
docker compose down
docker compose up -d
docker compose exec rails bundle exec rails db:migrateThen check the logs (docker compose logs web --tail=30) and test sending a message on each channel.
For a major upgrade (v3 → v4), the process is more delicate: v4 introduces blocking PostgreSQL schema migrations (restructured mentions and conversation_participants tables). Never run docker compose pull && docker compose up -d without a prior backup on a major version.
Best practice: always pin a specific version in docker-compose.yml (chatwoot/chatwoot:v4.17.1 rather than latest) so you control exactly what runs in production and avoid silent updates.
Migrating from Chatwoot v3 to v4
Chatwoot v4, released in June 2026, introduces the new 'Nova UI' interface and several blocking PostgreSQL schema migrations. An in-place upgrade from v3 will consistently break the instance if done without preparation — this is the subject of official issue #12088, which catalogues the most common failure cases.
The main reason for breakage: v4 renames the mentions table and restructures the conversation_participants table. A docker compose pull && docker compose up -d without a prior backup triggers the migrations automatically; if a migration fails mid-way (timeout, unsatisfied FK constraint), the database is left in an intermediate state and Chatwoot no longer starts.
In practice, three categories of instances are at risk: those running v3 with more than 50,000 conversations (bulk migrations are slow and may exceed Rails' 30-second timeout), those with undocumented custom columns in the contacts table, and those using Sidekiq Pro (removed from Community Edition in v4 — jobs queued at migration time are lost).
v3 → v4 migration procedure
Back up the database and volumes
Before any action, save the full state:
docker compose exec postgres pg_dumpall -U postgres > /tmp/chatwoot-v3-dump-$(date +%F).sql. Also copy the Docker volumes linked to PostgreSQL and Rails Storage. This backup is your only safety net: if the migration fails, restoration is the only clean exit.Pin the image tag to v4
In your
docker-compose.yml, replacechatwoot/chatwoot:latestwithchatwoot/chatwoot:v4.17.1(or the latest v4 patch release). Avoidlatestin production: this tag tracks HEAD and can introduce regressions without warning. Check the changelog for each version on the GitHub repository before targeting a tag.Run migrations manually
Rather than letting Rails launch migrations when the web container starts, run them explicitly in the foreground to monitor progress:
docker compose run --rm web bundle exec rails db:migrate. If an error occurs, the message is immediately visible — it identifies the failing migration and lets you fix it or skip it withdb:migrate:up VERSION=...before re-running.Start and verify
Once the migrations complete without error, start the stack:
docker compose up -d. Log in to the Nova UI and verify that existing inboxes, contacts and conversations are present. Test sending and receiving a message on each connected channel. If Sidekiq shows failed jobs, use the Sidekiq Web interface (mounted at/sidekiqif enabled) to retry them.
Troubleshooting: common errors
Sidekiq workers blocked. If outgoing emails no longer send or webhooks stop firing, Sidekiq is likely stalled or its workers are exhausted. Check via the Sidekiq Web interface (/sidekiq) or logs: docker compose logs sidekiq --tail=50. The most common cause is a saturated mailers queue. Restart the worker: docker compose restart sidekiq. If the issue persists, verify the Redis connection (docker compose exec redis redis-cli ping should return PONG).
Redis connection refused. If Rails and Sidekiq fail to start with Redis::CannotConnectError, the Redis container is not ready. Check its state: docker compose ps redis. A container in Restarting state indicates a volume or permission issue. Remove the Redis volume and restart if the Redis data is transient (queued jobs will be lost).
ActionCable WebSocket not connecting. The livechat widget shows 'connection lost' or agents don't receive messages in real time. Likely cause: the reverse proxy is not forwarding WebSocket headers. With Nginx, add to your location block:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";With Caddy, the reverse_proxy localhost:3000 configuration handles WebSockets automatically.
Migration stuck mid-way. If rails db:migrate stops with an error, do not retry immediately. Identify the failing migration in the error message, fix it manually or skip it (rails db:migrate:up VERSION=<timestamp>), then re-run. As a last resort, restore the backup taken before the migration.
Chatwoot vs Crisp vs Intercom: which tool for which situation
Scroll the table
| Criterion | Chatwoot (self-hosted) | Crisp | Intercom |
|---|---|---|---|
| Pricing model | Free (VPS infra cost only) | Free limited, then ~€25/month | From ~$74/agent/month |
| Hosting | On your own VPS | SaaS only | SaaS only |
| Customer data | On your infrastructure | Crisp servers | Intercom servers |
| GDPR / sovereignty | Total — no third party | DPA available | DPA available |
| Supported channels | Live chat, email, WhatsApp, Telegram, FB, Twitter/X, API | Live chat, email, Messenger | Live chat, email, SMS, WhatsApp (higher plan) |
| No agent cap | Yes | No (plan limited) | No (per-agent billing) |
| Automation | Native rules + API | Native rules | Advanced workflows |
| Deployment complexity | Medium (Docker required) | None | None |
Official documentation
For advanced configuration (SMTP, LDAP SSO, S3 storage, multi-accounts) and Chatwoot-specific options, refer to the official Chatwoot self-hosted documentation. This guide covers deployment on VPS; the vendor documentation remains the reference for fine-tuning and major upgrades.