Self-hosting
The hosted service is a convenience, not a moat. Everything it does, you can run on a spare machine — in one command with Docker, or by hand if you would rather see every part. This page is the honest version of both, including the bits that are annoying.
The short way
One command. The image builds the CLI with the right flags, so the mistake described further down cannot happen to you.
docker run -d --name wacli-me \
-p 127.0.0.1:8787:8787 \
-v wacli-stores:/app/stores \
-v wacli-config:/app/config \
-e WACLI_DEVICE_LABEL=your-domain.example \
ghcr.io/wacli-me/wacli-me:latest
Then link an account by opening http://127.0.0.1:8787/connect and scanning the code. The two volumes hold the message stores and the account file; keep them, or every linked account is gone.
Set WACLI_DEVICE_LABEL to something you recognise. Whatever it says is what appears under Linked devices in WhatsApp. Leave it unset and a container id shows up on the phone of whoever linked the account.
Do not publish port 8787 straight to the internet. The example binds it to localhost on purpose — put a tunnel or a TLS terminator in front, as described in step 5.
Prefer to build it yourself? docker build -t wacli-me . from a clone of the repository produces the same image; it takes a few minutes, most of it compiling the CLI. --build-arg WACLI_VERSION=v0.18.2 pins which CLI version goes in.
The long way
Everything below runs the same pieces without Docker — worth reading even if you took the short way, because the operational notes apply either way.
What you need
| Requirement | Why |
|---|---|
| A machine that stays awake | The sync daemon must hold a connection; a laptop that sleeps loses history |
| Go 1.24+ | Builds the wacli binary. Not needed with Docker |
| Node 20+ | Runs the gateway. Not needed with Docker |
| ~1 GB disk per busy account | SQLite store plus any media you download |
| A tunnel or reverse proxy | Only if you want to reach it from outside the machine |
1 · Build the CLI
git clone https://github.com/openclaw/wacli cd wacli go build -tags sqlite_fts5 -ldflags="-s -w" -o ~/bin/wacli ./cmd/wacli ~/bin/wacli --version
The build tag matters. Without -tags sqlite_fts5 the store migration fails with no such module: fts5, and full-text search is gone. It is the single most common setup mistake.
2 · Link an account
wacli --store ~/wa-store auth
A QR code appears in the terminal; scan it in WhatsApp under Settings → Linked devices. The code expires after about a minute, so have the phone ready. Over SSH the block graphics are often unscannable — link from a real terminal, or render the code to an image first.
The store directory now holds device credentials. Anyone who copies it can read that account: chmod 700 it, and keep it off shared volumes and backups you do not control.
3 · Keep it synced
wacli --store ~/wa-store sync
This is a long-running process, not a cron job. It holds the store lock, streams new messages in, and backfills history. Without it running, your store freezes at whatever it last fetched — the second most common setup mistake.
Run it under whatever your system uses to keep services alive: a launchd agent on macOS, a systemd unit on Linux.
# /etc/systemd/system/wacli-sync.service
[Unit]
Description=wacli sync
After=network-online.target
[Service]
ExecStart=/home/you/bin/wacli --store /home/you/wa-store sync
Restart=always
RestartSec=10
User=you
[Install]
WantedBy=multi-user.target
4 · Run the gateway
# the gateway running this service, MIT git clone https://github.com/wacli-me/wacli-me cd wacli-me npm install --prefix server node scripts/build.mjs # site/ -> web/ PORT=8787 HOST=127.0.0.1 node server/server.mjs
Tenants live in a JSON file that the process re-reads every few seconds, so adding an account needs no restart:
{
"tenants": [
{ "id": "me",
"token": "<openssl rand -hex 32>",
"store": "/home/you/wa-store",
"allowSend": false }
]
}
| Variable | Default | Meaning |
|---|---|---|
PORT | 8787 | Listen port |
HOST | 127.0.0.1 | Bind address — keep it on loopback |
WACLI_BIN | ./bin/wacli | Path to the CLI |
WACLI_ME_TENANTS | ./config/tenants.json | Tenant file |
5 · Expose it — carefully
If the client runs on the same machine, stop here: http://127.0.0.1:8787/mcp is all you need, and nothing is reachable from outside.
To reach it from elsewhere, put a TLS terminator in front and leave the service on loopback. A Cloudflare tunnel does this without opening a port or exposing your home IP:
cloudflared tunnel --url http://127.0.0.1:8787
Never bind the service to 0.0.0.0 on a public network. The token is the only thing between the internet and your message history, and a plaintext HTTP hop hands it to anyone on the path.
Operational reality
Sessions drop. WhatsApp logs linked devices out now and then. Watch account_status; re-linking means another QR scan, in person.
Old CLI versions get refused. A client outdated error in the sync log means rebuilding from a current tag. Pin a version you tested.
One writer per store. The sync daemon holds the lock. Read commands are fine alongside it; a second writer is not.
Back up deliberately. The store is one directory. Copying it copies the credentials too — encrypt the backup or skip it.