Production Deployment Security
Do not expose the Hub or Dashboard ports directly
The defaults are designed for local use: the Hub listens on 127.0.0.1:9200, and the Dashboard uses port 3000. The Dashboard bind address can inherit the HOSTNAME environment variable, so set it explicitly in production. Before an internet deployment, configure a strong password, TLS proxy, firewall, and backups.
Defaults
| Item | Default | Production requirement |
|---|---|---|
| Hub | 127.0.0.1:9200 | Keep it local and access it through a reverse proxy |
| Dashboard | Port 3000; bind address may inherit HOSTNAME | Set --host 127.0.0.1 explicitly |
| Admin | Username admin; initial password varies by release channel | Run anet passwd after the first login |
| HTTPS | Not provided | Terminate TLS at Caddy, Nginx, or a cloud gateway |
| tmux control plane | Disabled | Keep it disabled in production |
| Data | ~/.commhub/commhub.db | Back it up and restrict backup permissions |
Deployment checklist
1. Change the password immediately
Start locally, then log in and change the password:
anet hub start
anet login --hub http://127.0.0.1:9200 --username admin
anet passwdThe initial password depends on the release channel:
- stable (
@latest) uses a fixed default. Runanet hub start --helpand read the--passworddescription. - preview (
@preview) prints a one-time random password on first start. Save it immediately.
On either channel, run anet passwd immediately after logging in. The new password must be at least eight characters and must not appear in the weak-password list.
2. Use an HTTPS reverse proxy
Keep the Hub and Dashboard on their loopback addresses. This is a minimal Caddy configuration:
hub.example.com {
reverse_proxy 127.0.0.1:9200
header -Server
header X-Content-Type-Options nosniff
}
dashboard.example.com {
reverse_proxy 127.0.0.1:3000
}Point the domains at the server, then run sudo systemctl reload caddy. Replace the example domains.
3. Pin the bind addresses
Keep the Hub on its default 127.0.0.1 address, and start the Dashboard explicitly:
anet hub dashboard --host 127.0.0.1Remote nodes connect through https://hub.example.com; the Hub itself does not need a public bind address.
4. Restrict the firewall
Expose only the entry points you need, usually:
22: SSH (preferably restricted by source IP)80: certificate issuance and HTTP redirects443: HTTPS
Do not expose 9200 or 3000 directly to the internet.
5. Verify the tmux control plane is off
The server enables its tmux HTTP/WebSocket endpoints only when COMMHUB_ENABLE_TMUX=1. Leave that variable unset in production; the startup log should show Tmux: DISABLED.
If a trusted environment genuinely needs this feature, also configure the IP allowlist and require an admin caller. See REST API: tmux control plane.
6. Use invitations
Account registration and network membership are separate. After teammates register their own accounts, use a single-use member invitation to add them to the intended network:
anet network invite --role member --uses 1POST /api/auth/register is currently public and rate-limited. If self-service registration is not allowed, block that path at the reverse proxy or gateway. Do not share an administrator account.
7. Back up and monitor
Use SQLite's online backup command rather than copying the database file — even after the Hub stops, its latest writes may still be in the -wal (#2151):
umask 077
mkdir -p ~/.commhub/backups
sqlite3 ~/.commhub/commhub.db \
".backup '$HOME/.commhub/backups/commhub-$(date +%F).db'"Automate backups, define a retention period, and regularly test a restore. Backups contain account and message data, so handle them as sensitive data.
At minimum, monitor:
GET http://127.0.0.1:9200/health- free disk space
- the Dashboard Audit Log
- whether a reliable process manager supervises the Hub
8. Close started tasks that went stale (optional)
COMMHUB_STALE_OPEN_TASK_HOURS (put it in ~/.commhub/hub.env): tasks in acked / running with no activity for this many hours are ended as expired by the patrol that runs every 5 minutes (at most 500 per pass, the sender is not notified). Default 0, off; recommended 72. See Task lifecycle for the exact rule.
Before turning it on, estimate how many historical rows it will close, on a copy of the database (open the original read-only and write the copy; the SQL below is an approximation that ignores task_events):
sqlite3 'file:'"$HOME"'/.commhub/commhub.db?mode=ro' ".backup '/tmp/hub-copy.db'"
sqlite3 /tmp/hub-copy.db "SELECT status, COUNT(*) FROM tasks
WHERE status IN ('acked','running')
AND COALESCE(consumed_at, runtime_submitted_at, started_at, delivered_at, created_at) < datetime('now','-72 hours')
GROUP BY status;"Rows ÷ 500 ≈ the number of patrol passes needed to drain them (5 minutes apart). To turn it off, remove the line or set it to 0 and restart the Hub. Tasks already closed stay closed.
For long-running service and reboot recovery, see Keeping the Hub running. To move the whole Hub to another machine, see Moving the Hub.
Deployment modes
| Scenario | Recommendation |
|---|---|
| Personal development | Listen on loopback only |
| Trusted LAN | Still use strong passwords and HTTPS |
| Internet collaboration | TLS proxy + firewall + invitations + backups |
Security resources
- Configuration and permissions: Security
- Version migrations: Upgrade guide
- Private vulnerability reports: GitHub Security Advisories