
Self-host OpenWA on Netcup — a free WhatsApp HTTP API gateway
TL;DR: Running OpenWA on Netcup in 5 minutes
Self-host OpenWA on a Netcup VPS to put a REST API in front of WhatsApp without paying per-message fees to a SaaS gateway. OpenWA is a free, MIT-licensed WhatsApp API alternative built on Node.js 20 and NestJS, with multi-session support, Socket.IO webhooks, an API-key auth layer, and a React dashboard. It drives WhatsApp through a headless Chromium under the hood, so RAM is the resource that matters — exactly the number Netcup is cheap on.
- What it is: a NestJS HTTP gateway that wraps the
whatsapp-web.jslibrary and exposes REST plus WebSocket interfaces on ports2785(API) and2886(dashboard). - Competes with: Twilio's WhatsApp API, Vonage, MessageBird, Wassenger, and Meta's WhatsApp Cloud API — all of which bill per conversation or per session.
- Hosting profile: 2 GB RAM for one session at idle, ~4 GB once you have two or three concurrent sessions, more if you keep many chats hot in memory.
- Storage: session cookies, media cache, audit log, and either SQLite (default) or PostgreSQL. Modest unless your bots send media in volume.
- License: MIT. Commercial use is fine; nothing in the code dictates how you may deploy it.
- Latest stable: v0.1.6 (May 17, 2026), maintained on GitHub by
rmyndharis.
Provision a fresh Debian 12 box and bring it up:
git clone https://github.com/rmyndharis/OpenWA.git
cd OpenWA
docker compose up -d
Primary pick: VPS 1000 G12 — 4 vCPU, 8 GB DDR5 ECC, 256 GB NVMe, around 10.37 € per month. The 8 GB keeps two or three Chromium sessions and Postgres comfortably out of swap, and the dedicated 256 GB NVMe absorbs months of media cache before pruning becomes a job.
- First month free:
5799nc17800061381 - Second code:
5799nc17774618550 - Third code:
5799nc17800061380
Introduction
Every business that touches WhatsApp at scale eventually runs the same maths. Twilio charges a per-conversation fee on top of Meta's per-conversation fee. Vonage and MessageBird sit in the same pricing band. The official WhatsApp Cloud API is cheaper, but it forces template-message approval, Business Manager verification, and a phone number you cannot use anywhere else. For internal automation, transactional alerts, support bots, and small commercial deployments, none of those models is a clean fit — you are billed twice for sending notifications you would happily send for free.
OpenWA is the obvious escape hatch: a free, MIT-licensed gateway that speaks WhatsApp on one side and HTTP on the other. It does this the unglamorous way — by driving the WhatsApp Web client through a headless Chromium and exposing the resulting message stream as a clean REST and WebSocket API. There is no per-message fee, no template approval, no phone-number lock. You point it at any WhatsApp account, scan a QR code once, and you have a programmable inbox.
The footprint is the only thing to plan around. Each session is a Chromium process plus its tabs and IndexedDB, which is famously hungry. A Netcup VPS with 8 GB of real DDR5 ECC RAM and NVMe storage is the right shape of box: cheap enough that the maths beats Twilio inside a week, fast enough that the WhatsApp Web client feels native, and located in EU jurisdiction (Nuremberg, Vienna, Karlsruhe) where the legal answers to "where is the data" are short.
The rest of this article walks through what OpenWA is, how to deploy it on Debian behind Caddy with TLS, how to size the box for the number of sessions you intend to run, and where the operational edges are — especially the Chromium memory growth that you will eventually meet if the gateway sticks around long enough.
What is OpenWA?
OpenWA is a self-hosted WhatsApp API gateway written in TypeScript, built on the NestJS 11 framework, and licensed under MIT. It is the most recent of a long line of open-source projects that solve the same problem — putting an HTTP-shaped interface in front of WhatsApp Web — and it does so with a deliberately small surface: REST, WebSocket, multi-session, webhooks, dashboard, done.
The core machinery is the whatsapp-web.js library, which automates the official WhatsApp Web client through Puppeteer. OpenWA wraps that library in a NestJS application, gives every authenticated session a UUID and an API key, persists session metadata in SQLite or PostgreSQL through TypeORM, and broadcasts events through Socket.IO. A React dashboard built on Vite and TanStack Query talks to the same API for human-friendly administration.
Architecture
A running OpenWA instance has four moving parts:
- The NestJS API server on port
2785, also serving Swagger documentation at/api/docsand a health endpoint at/api/health. - The React dashboard on port
2886, used to scan QR codes, manage sessions, browse messages, and rotate API keys. - A database, either SQLite by default or PostgreSQL once you outgrow file locking. TypeORM handles the abstraction.
- One Chromium process per active session, spawned and supervised by
whatsapp-web.js. This is where almost all the memory goes.
Optional pieces include Redis for caching, S3 or MinIO for media storage, and Traefik (the production docker-compose.yml ships with it) for TLS termination. None of these are mandatory, but Postgres-plus-Redis is what you reach for once a single SQLite file starts to feel cramped.
Who maintains it
The repository is at github.com/rmyndharis/OpenWA, with v0.1.6 as the most recent tag at the time of writing. The pace is rapid, the issue tracker is responsive, and the codebase is small enough to read in an afternoon. It is the kind of project where you can fork, patch, and rebase against upstream without inheriting a six-month merge conflict.
How big does the data get
Modest. The TypeORM-managed tables track sessions, contacts, messages, and audit entries. The expensive growth comes from media — incoming images, voice notes, documents — which OpenWA caches on disk under a configurable path. If your gateway forwards media to S3 or to a downstream application immediately, on-disk growth stays bounded. If you keep media for later retrieval, plan for storage to climb with throughput.
A reasonable rule of thumb: 50 to 200 MB per session per month for metadata and audit log, plus whatever media you elect to retain. The 256 GB NVMe on the smallest serious VPS tier is years of headroom for most deployments.
How to use OpenWA
OpenWA is a daemon you configure once, point at one or more WhatsApp accounts, and then talk to over HTTP from the rest of your stack. The model is straightforward once you have the vocabulary.
Core concepts
- Session — one authenticated WhatsApp account, backed by one Chromium browser context. A session has a UUID, a state (
pending,qr,ready,disconnected), and a set of API keys scoped to it. - API key — a bearer token used in the
Authorizationheader on every request. Keys are scoped per session, have a role (read or read-write), and can be revoked individually. - Webhook — an HTTP endpoint you register that OpenWA POSTs to whenever an event happens on a session. Payloads are signed with an HMAC so the receiver can verify origin.
- Event — incoming message, message delivery receipt, session-state change, group membership change. Each event flows through both webhooks and the Socket.IO channel.
- IP allowlist — an optional CIDR-based whitelist applied at the request layer, useful for locking the dashboard to your office network without touching the firewall.
Day-to-day workflow
The shape of a typical integration is: create a session via the REST API, retrieve the QR code through the dashboard or /sessions/{id}/qr endpoint, scan it with the phone holding the WhatsApp account, then issue API keys and start sending. Outgoing messages are a POST /messages with a target number and content; incoming messages arrive on your webhook or on a Socket.IO subscription. Rate limits and HMAC signing are configurable in the environment.
Most deployments end up scripting two automations against this surface: an inbound flow that pushes messages into a ticketing system or LLM-backed responder, and an outbound flow that sends transactional notifications driven by an application event (order shipped, alert fired, invoice paid).
Integrations and extensibility
The REST surface is the integration. There is no plugin model in the OpenWA codebase itself — and that is a feature, not a gap. Anything you want to bolt on lives outside the gateway, talking to it as a client. Webhook receivers can be written in any language; the project includes example payloads in the Swagger docs at /api/docs. Common patterns include forwarding incoming messages to n8n, posting them to a Slack channel, or feeding them through an LLM with structured-output formatting.
Backup and operational reality
Three things matter for a clean restart: the database (SQLite file or Postgres dump), the session-data directory where the Chromium user-data lives (this is what keeps you logged in across restarts), and your .env with API keys and webhook secrets. Back those three up, and a new VPS plus an SSH copy will have the gateway back online in fifteen minutes.
The session-data directory is the surprising one. Lose it, and every account has to scan a fresh QR code — which means physical access to the phones, which means downtime measured in however long it takes someone to walk to the warehouse. restic to S3 nightly handles this in one cron line; the directory compresses well, and S3 storage at Hetzner or Backblaze costs more in mental overhead than in money.
Chromium will leak memory if you leave a session running for weeks at a time. The published guidance is to restart sessions on a schedule — once a day on a low-traffic gateway, more often on a busy one. docker compose restart or a systemctl restart cron is the blunt fix; OpenWA's session lifecycle endpoints let you do it without a full process restart if you would rather.
Quick Start Guide
The following steps assume Debian 12 on a fresh Netcup VPS with SSH access and an A record pointing your domain at the box. End state: OpenWA running behind Caddy with automatic TLS, a single session ready to scan, and systemd keeping it alive.
1. Provision the box
Sign up for the VPS 1000 G12 at netcup.com using coupon 5799nc17804382700 for one free month. Pick Debian 12 as the operating system on creation, drop in your SSH public key, and SSH in:
# /etc/ssh/sshd_config.d/00-hardening.conf
PasswordAuthentication no
PermitRootLogin prohibit-password
systemctl reload ssh
Add a non-root user with sudo if you have not already, and update the box:
apt update && apt full-upgrade -y
apt install -y curl ca-certificates git ufw
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable
The 80 and 443 rules are for Caddy. OpenWA itself listens on 2785 and 2886 — those stay bound to localhost and never see the public firewall.
2. Install Docker
OpenWA ships a working docker-compose.yml, so Docker is the path of least resistance:
curl -fsSL https://get.docker.com | sh
systemctl enable --now docker
Add the working user to the docker group if you do not want to sudo on every command:
usermod -aG docker $USER
newgrp docker
3. Pull and configure OpenWA
git clone https://github.com/rmyndharis/OpenWA.git
cd OpenWA
cp .env.example .env
Open .env and set the essentials: a strong ADMIN_PASSWORD, a unique JWT_SECRET, the DASHBOARD_URL (your real domain), and the database driver. SQLite is fine for a first deployment; switch DB_DRIVER=postgres once you cross a handful of sessions.
# .env (excerpt)
NODE_ENV=production
ADMIN_USERNAME=admin
ADMIN_PASSWORD=<long-random-string>
JWT_SECRET=<another-long-random-string>
DB_DRIVER=sqlite
DASHBOARD_URL=https://wa.example.com
API_BASE_URL=https://wa.example.com/api
WEBHOOK_HMAC_SECRET=<a-third-long-random-string>
4. Bring up the stack
docker compose up -d
docker compose logs -f api
The API container will run TypeORM migrations on first boot, create the SQLite database file under ./data/, and start listening on 127.0.0.1:2785. The dashboard container builds the Vite bundle on first start and serves it on 127.0.0.1:2886. Wait for both health checks to flip to green in docker compose ps.
5. Reverse-proxy with Caddy
OpenWA's stock docker-compose.yml includes Traefik, which works but is heavier than most single-tenant deployments need. The lean alternative is Caddy on the host:
apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | tee /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy
# /etc/caddy/Caddyfile
wa.example.com {
reverse_proxy /api/* 127.0.0.1:2785
reverse_proxy /socket.io/* 127.0.0.1:2785
reverse_proxy /* 127.0.0.1:2886
}
systemctl reload caddy
Caddy fetches a Let's Encrypt certificate the first time it sees an HTTPS request — no certbot, no cron, no expiry surprises.
6. Authenticate the first session
Open https://wa.example.com in a browser, log in with the credentials from .env, click New Session, and scan the rendered QR with the phone holding your business WhatsApp account. The session flips from qr to ready within a few seconds. Issue an API key from the Keys tab, copy it once (it is shown plain in the response then hashed at rest), and you can immediately send your first message:
curl -X POST https://wa.example.com/api/messages \
-H "Authorization: Bearer $OPENWA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"sessionId":"<uuid>","to":"4915112345678","text":"Hello from OpenWA."}'
7. Make it survive a reboot
Docker is already set to start at boot. Caddy too. The only remaining detail is the nightly restart of long-running sessions, which prevents Chromium memory creep from eating the box over a week:
# /etc/cron.d/openwa-restart
0 4 * * * root cd /home/<user>/OpenWA && /usr/bin/docker compose restart api > /dev/null 2>&1
Pair that with a restic job pointed at ./data/ and ./sessions/ and the gateway is operationally complete.
Choosing the Right Netcup Server for cheap OpenWA hosting
OpenWA's footprint is driven almost entirely by the number of concurrent Chromium processes you keep alive. A single session at idle is around 400 to 700 MB of RSS; under message bursts and media downloads it can briefly double. Sizing rules follow directly.
VPS 500 G12 — for testing one session
Specs: 2 vCPU, 4 GB DDR5, 128 GB NVMe, traffic included. Around 5.91 €/month (verify on netcup.com).
Enough for a single session with light traffic, a SQLite database, and Caddy. Two sessions will fit if both are quiet. You will run out of headroom the moment one session under load coexists with a Postgres container — the swap thrash that follows is not subtle.
There is no dedicated coupon category for VPS 500 G12; use the €5-off-any-order coupon instead:
36nc1771801554036nc1771801554536nc17718015549
VPS 1000 G12 — recommended for most OpenWA deployments
Specs: 4 vCPU, 8 GB DDR5 ECC, 256 GB NVMe, traffic included. Around 10.37 €/month.
The right tier for the standard case: two or three concurrent sessions, Postgres instead of SQLite, a Redis container for cache, plus generous headroom for media downloads. The 256 GB NVMe is enough for years of audit log and session data, and the four shared vCPUs absorb the Chromium spin-up cost when you cycle sessions on the nightly restart.
5799nc178000613825799nc177180152615799nc17800061381
VPS 2000 G12 — multi-session production gateway
Specs: 8 vCPU, 16 GB DDR5 ECC, 512 GB NVMe, traffic included. Around 19.25 €/month.
Pick this tier once you cross four or five concurrent sessions, or once a single session handles enough media that you want comfortable burst capacity. The doubled RAM gives the OS page cache real room to keep IndexedDB hot, which directly translates into faster message processing under load. The 512 GB NVMe is more than most gateways will ever use, but if you retain media you will be glad of it.
5800nc177180152305800nc178043827105800nc17802654090
RS 1000 G12 — when latency under load matters
Specs: 4 dedicated cores on AMD EPYC 9645 (Zen 5), 8 GB DDR5 ECC, 256 GB NVMe. Around 12.79 €/month.
The same RAM as VPS 1000 G12 for about 2.40 € more per month, but the cores are dedicated rather than oversubscribed. If your gateway sits on the critical path of an interactive bot — customer support, an LLM-backed responder, anything where p99 latency is read by a human — the EPYC 9645 single-thread performance is worth the upgrade. Multiple concurrent sessions also see real benefits, because the Chromium event loops stop fighting each other for noisy-neighbour CPU cycles.
5159nc177180154435159nc177180154415997nc17755880771
Side-by-side
| Offer | vCPU | RAM | NVMe | Approx. price |
|---|---|---|---|---|
| VPS 500 G12 | 2 shared | 4 GB | 128 GB | 5.91 €/mo |
| VPS 1000 G12 | 4 shared | 8 GB | 256 GB | 10.37 €/mo |
| VPS 2000 G12 | 8 shared | 16 GB | 512 GB | 19.25 €/mo |
| RS 1000 G12 | 4 dedicated | 8 GB | 256 GB | 12.79 €/mo |
Which one to pick:
If you are kicking the tyres on one session for a side project or an internal alert bot, VPS 500 G12 is enough and cheap enough that you will never think about it again. If you are running a small commercial gateway with a handful of accounts and a Postgres backend, VPS 1000 G12 is the default — it leaves headroom for the inevitable third session and the day you turn on Redis. If you are running OpenWA as the front end of a paying support product where outage minutes show up in a customer complaint, RS 1000 G12 is the right pick; the dedicated cores eliminate the worst-case latency spikes that shared CPUs occasionally serve up.
Web Hosting is not appropriate for OpenWA — the gateway needs a long-running Node.js daemon and a headless Chromium, neither of which shared hosting will give you.
Conclusion
You now have OpenWA running behind TLS on a Netcup VPS, a session authenticated, an API key in hand, and a cron job preventing Chromium from quietly eating the box overnight. The monthly bill on the recommended path lands at around 10.37 € for the VPS 1000 G12, less the first month free from the coupon — for one of two or three sessions, a Postgres backend, and a Caddy reverse proxy that renews its own certificates.
For a final discount on any other order you place in the same checkout — a second VPS for a staging gateway, a domain registration, a Web Hosting tier for the landing page — apply the €5-off-any-order coupon: 36nc17718015548.
Two things to watch once the gateway is live. First, the Chromium memory baseline — RSS climbs slowly under load, and the cron restart should keep it bounded, but check docker stats once a week to confirm. Second, the WhatsApp Web protocol can shift without notice on Meta's side; pin OpenWA to a known-good tag in production and read the release notes before pulling a new version. The combination of a stable Netcup box, MIT-licensed gateway code, and a clean restic backup gives you the operational confidence to treat WhatsApp as just another HTTP API in your stack — which is what it should have been all along.
For more self-hosting walkthroughs, browse the blog index. If you are comparing tiers for a different workload, the VPS overview is the cleanest place to start.