How to Deploy Hermes Agent on Signal (signal-cli)
Deploy Hermes Agent on Signal via signal-cli daemon: link the device, set SIGNAL_HTTP_URL, lock the allowlist, and ship a private E2EE AI bot.

Why Run Hermes Agent on Signal
If your threat model puts metadata first, Signal is the messenger you already trust. End-to-end encryption is on by default, the server stores almost nothing beyond a phone number and a last-connection timestamp, and the protocol itself is open source. Putting your AI agent on the same surface keeps every prompt, every reply, and every file you hand it inside that same envelope.
Hermes Agent supports Signal natively through signal-cli, an unofficial but well-maintained daemon that speaks the Signal protocol on your behalf. The Hermes gateway talks to signal-cli over HTTP using JSON-RPC for outgoing actions and Server-Sent Events for the incoming stream, so the whole runtime can sit on a $5 VPS, your laptop, or a Raspberry Pi behind a NAT and still send and receive in real time.
This guide walks through the full setup: installing signal-cli, linking it to your Signal account as a secondary device, starting the HTTP daemon, wiring it into the Hermes runtime, and the gotchas that trip up first-time installs. If you already shipped Hermes on a more public surface, the Slack setup walkthrough and the Telegram delivery guide cover the same end of the stack and are worth keeping side by side.
What You Need Before You Start
A clean Signal deployment needs five things on the host:
- A running Hermes Agent install. The Hermes Agent Docker guide is the cleanest starting point if you do not already have one.
- A model provider key. Anthropic, OpenAI, OpenRouter, and any OpenAI-compatible endpoint work. The runtime uses this to actually answer the messages.
- A Signal account already activated on a primary device (your phone).
signal-clilinks as a secondary device, the same way Signal Desktop does - it does not register a new account. - Java Runtime Environment 21 or newer. The
signal-clijar will not start on JRE 17 in current builds. The GraalVM-native binary skips this requirement if you prefer a single executable. - About 20 minutes for the first install. Subsequent re-deployments take under a minute.
The phone number you use does not have to be the one on your daily-driver phone. Many self-hosters dedicate a second number (a cheap eSIM, a Twilio number, or a Google Voice line that accepts SMS) so the bot account is operationally separate.
Step 1 - Install signal-cli on Your Host
On Debian or Ubuntu, install Java and grab the latest signal-cli release:
sudo apt update
sudo apt install -y curl openjdk-21-jre
VERSION=$(curl -s https://api.github.com/repos/AsamK/signal-cli/releases/latest | jq -r .tag_name | sed 's/^v//')
curl -L -o /tmp/signal-cli.tar.gz \
"https://github.com/AsamK/signal-cli/releases/download/v${VERSION}/signal-cli-${VERSION}-Linux.tar.gz"
sudo tar -xf /tmp/signal-cli.tar.gz -C /opt
sudo ln -sf "/opt/signal-cli-${VERSION}/bin/signal-cli" /usr/local/bin/signal-cli
signal-cli --version
If you prefer a single static binary with no JRE dependency, the GraalVM native build from the project's release page is a drop-in replacement and avoids the Java upgrade churn entirely.
Step 2 - Link signal-cli to Your Signal Account
signal-cli joins your account as a linked device, exactly like Signal Desktop. Generate a provisioning URL and have the daemon print a QR code:
signal-cli link -n "hermes-agent"
The command prints a sgnl://linkdevice?... URL and a QR code in the terminal. On your phone, open Signal, go to Settings -> Linked devices -> Link new device, and scan the QR. Within a few seconds the terminal prints something like:
Associated with: +12025550123
That E.164 number is your SIGNAL_ACCOUNT value for the rest of the setup. The on-disk state for the linked identity lives under ~/.local/share/signal-cli/. Back that directory up before you destroy the host - it is the only place the linked-device keys are stored, and re-linking from scratch invalidates pending message history.
Step 3 - Start the signal-cli HTTP Daemon
The Hermes gateway speaks to signal-cli over HTTP, so the daemon needs to run in HTTP mode bound to a local-only port:
signal-cli -a +12025550123 daemon --http 127.0.0.1:8080
The --http flag exposes a JSON-RPC endpoint for sending and a Server-Sent Events stream for receiving. Bind it to 127.0.0.1 and nothing else; the daemon does no authentication of its own, so any process that can reach the port can send messages as you. If the gateway runs in Docker on the same host, host.docker.internal:8080 or a shared Docker network works without exposing the port to the LAN.
For long-running deployments, drop a systemd unit so the daemon restarts on reboot. The signal-cli wiki has a ready-to-use unit template; the only fields you need to fill in are the account number and the bind address.

Step 4 - Configure the Hermes Runtime
Open ~/.hermes/.env (or whichever .env your container reads) and add:
SIGNAL_HTTP_URL=http://127.0.0.1:8080
SIGNAL_ACCOUNT=+12025550123
SIGNAL_ALLOWED_USERS=+34611222333,+12025557788
SIGNAL_GROUP_ALLOWED_USERS=group.abc123==
SIGNAL_HOME_CHANNEL=+34611222333
A note on each:
SIGNAL_HTTP_URL- the local daemon endpoint from Step 3. Alwayshttp://, always loopback or a private network.SIGNAL_ACCOUNT- the E.164 number of the linked Signal identity. Must match exactly whatsignal-cli linkreported.SIGNAL_ALLOWED_USERS- comma-separated E.164 numbers or Signal UUIDs allowed to talk to the bot. Without this set (and without DM pairing), the gateway denies every incoming message as a safe default. This is the single most important security control in the whole stack.SIGNAL_GROUP_ALLOWED_USERS(optional) - comma-separated group IDs where the bot is allowed to respond. Group IDs come fromsignal-cli listGroups -d. Leave blank to disable group support.SIGNAL_HOME_CHANNEL(optional) - the default delivery target for scheduled tasks, cron jobs, and skill-triggered notifications. If unset, proactive output goes to the first allowed user.
chmod 600 ~/.hermes/.env after saving. The combination of the linked Signal identity and your model provider keys gives anyone with read access the ability to impersonate the bot and burn your tokens - treat the file like an SSH key.
Step 5 - Start the Gateway and Send the First Message
Restart the Hermes gateway so it picks up the new environment:
hermes gateway restart
Tail the logs and watch for two lines in order:
signal: connecting to http://127.0.0.1:8080
signal: subscribed to receive stream for +12025550123
From the allowed phone, open Signal and send a DM to the bot's number. Hermes responds in the same thread, with persistent memory and skill context intact - the memory and skills walkthrough covers what is happening underneath. Attachments up to 100 MB ride the same Signal upload API as a regular message, so screenshots, voice notes, and PDFs flow through without extra plumbing.

The Failure Modes That Bite First
Five mistakes account for almost every "Signal setup is broken" support thread:
Forgetting to link the device. Running signal-cli daemon against an unlinked account prints no error - the daemon starts and silently refuses every message. If logs show zero events and zero send attempts, run signal-cli listAccounts and confirm your number appears.
Wrong JRE version. Current signal-cli builds need JRE 21. On long-lived servers still pinned to JRE 17 or 11, the jar fails on a class-version mismatch the moment you call --http. Either upgrade Java or use the GraalVM native binary.
Empty SIGNAL_ALLOWED_USERS. The default is to deny everyone. This is deliberate - a leaked phone number for the bot account would otherwise let anyone in the world send prompts that burn your model tokens. The bot looking dead to a first-time tester who forgot to add their own number is the most common false alarm.
Daemon bound to 0.0.0.0. The HTTP daemon does no authentication. Binding it to a public interface means anyone who can reach the port can send Signal messages as you. Always bind to 127.0.0.1 or a private Docker network.
Stale linked-device state. Re-running signal-cli link overwrites the local state directory. The new link is fine, but Hermes is suddenly looking at a different identity than the env file declared. Either keep the old SIGNAL_ACCOUNT value when you re-link, or update the env to the new number printed by the link command.
If a problem looks Signal-shaped but the symptoms feel general - missing replies, memory drift, provider errors - the Telegram troubleshooting guide covers the underlying gateway and provider issues in more depth. Most are surface-agnostic.
Signal Next to Telegram and Slack
| Concern | Signal | Telegram | Slack |
|---|---|---|---|
| Setup time | 20 min (link + daemon) | 5 min (BotFather token) | 15 min (manifest + 2 tokens) |
| Encryption | End-to-end by default | Opt-in secret chats | Server-side only |
| Metadata stored | Almost none | Server-side history | Workspace-wide retention |
| Account model | Linked phone number | Bot account, no number | Bot user in workspace |
| Allowlist field | SIGNAL_ALLOWED_USERS (E.164) | TELEGRAM_ALLOWED_USERS (numeric) | SLACK_ALLOWED_USERS (numeric) |
| Best for | Privacy-first individuals and small teams | Personal use, mobile-first, voice-mode | Team workflows and channel context |
The three surfaces are not mutually exclusive. The same Hermes runtime can deliver to all of them at once, multiplexing over a single memory store. A founder running a personal agent on Signal and a team-facing agent on Slack from the same install is a common pattern.
Skip the Signal Plumbing
The steps above are tractable but they leave you on the hook for the host: keeping signal-cli upgraded, backing up the linked-device state, rotating the JRE, watching for daemon restarts. For a personal install that is fine. For anything you want live within minutes and forget about, Hermify provisions a managed Hermes runtime with the Signal bridge ready to link, encrypts your SIGNAL_* config at rest, and keeps the signal-cli daemon healthy on a persistent server you never have to ssh into.
You bring the Signal number and a model provider key; the platform handles everything below. If you want to compare what stays in your control versus what gets handed off, the self-hosting versus managed walkthrough breaks down the maintenance and cost numbers. And if cost is the gating concern, the cheap VPS guide lays out what a $5/month self-host actually looks like in practice. Get started with Hermify when you are ready to skip the plumbing entirely.
Sources
Run Your Own Hermes Agent
Bring your API key, connect Telegram, and get a self-improving AI agent live in 60 seconds.
Get Started