Cheatsheet¶
Command cheatsheet organized by scenario. Every block is
copy-paste ready — replace X / Y with your own account and
password.
Conventions
X= login account (phone / Futubull ID / moomoo ID / email)Y= password plaintextZ= SMS verification code (sent to phone on first login)
1. Account platform selection¶
Futu has two independent account systems: Futubull
(auth.futunn.com) and moomoo (auth.moomoo.com). Both accept the
three formats (numeric ID / phone / email) as --login-account. The
same phone / email can have independent accounts on both platforms (with
different passwords) — --platform disambiguates.
# Futubull (default, CN / HK) — numeric ID / phone / email (any)
./futu-opend --login-account 12345678 --login-pwd Y
./futu-opend --login-account '+86-13900000000' --login-pwd Y
./futu-opend --login-account 'user@example.com' --login-pwd Y
# moomoo (US / SG / AU / JP / CA) — same three formats + --platform moomoo
./futu-opend --login-account 87654321 --login-pwd Y --platform moomoo
./futu-opend --login-account '+1-4155551234' --login-pwd Y --platform moomoo
./futu-opend --login-account 'user@example.com' --login-pwd Y --platform moomoo
Phone format: region code + dash required
Use +<rc>-<number> strictly, e.g. +86-13900000000 /
+1-4155551234. Anything else fails identification.
2. First deploy (recommended workflow)¶
Strongly recommended: two steps — foreground SMS first, production launch second. This avoids systemd / Docker getting stuck on SMS at startup.
# Step 1: foreground one-off setup (interactive; will prompt for SMS code)
./futu-opend --setup-only \
--login-account X --login-pwd Y --platform moomoo
# Step 2: normal launch (auto-uses cached credentials, skips SMS)
./futu-opend --login-account X --login-pwd Y --platform moomoo \
--rest-port 22222 --grpc-port 33333
--setup-only completes auth + credential cache and exits immediately —
no servers started. Credentials land in
~/.futu-opend-rs/credentials-<hash>.json and SMS won't be requested
again within ~30 days.
3. Production (unattended)¶
systemd¶
/etc/systemd/system/futu-opend.service:
[Unit]
Description=FutuOpenD Rust Gateway
After=network-online.target
[Service]
Type=simple
User=futu
Environment=FUTU_ACCOUNT=...
Environment=FUTU_PWD=...
ExecStart=/usr/local/bin/futu-opend \
--login-account ${FUTU_ACCOUNT} --login-pwd ${FUTU_PWD} \
--platform moomoo \
--rest-port 22222 --grpc-port 33333 \
--rest-keys-file /etc/futu-opend/keys.json \
--grpc-keys-file /etc/futu-opend/keys.json \
--audit-log /var/log/futu-opend/audit.jsonl
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
Deployment steps:
# 1. Foreground setup to write credentials (as futu user)
sudo -u futu /usr/local/bin/futu-opend --setup-only \
--login-account X --login-pwd Y --platform moomoo
# 2. Enable the systemd service
sudo systemctl enable --now futu-opend
Docker¶
docker run -d --name futu-opend --restart unless-stopped \
-p 11111:11111 -p 22222:22222 -p 33333:33333 \
-v ~/.futu-opend-rs:/root/.futu-opend-rs \
-v /etc/futu-opend/keys.json:/etc/futu-opend/keys.json:ro \
ghcr.io/futuleaf/futu-opend-rs:rs-v1.4.18 \
--login-account X --login-pwd Y --platform moomoo \
--rest-port 22222 --grpc-port 33333 \
--rest-keys-file /etc/futu-opend/keys.json \
--grpc-keys-file /etc/futu-opend/keys.json
Credential volume mount
-v ~/.futu-opend-rs:/root/.futu-opend-rs is critical — after a
container rebuild, device_id and credentials need to persist;
otherwise SMS is triggered on every restart.
4. Multi-instance parallel (Futubull + moomoo side by side)¶
Common scenario: one account on Futubull, one on moomoo; run both
gateways at once. Ports must differ — otherwise futucli's default
127.0.0.1:11111 points at the wrong instance.
# Instance A: Futubull (default ports)
./futu-opend --login-account 12345678 --login-pwd Y1 \
--rest-port 22222 --grpc-port 33333 &
# Instance B: moomoo (ports + 1)
./futu-opend --login-account '+86-13900000000' --login-pwd Y2 --platform moomoo \
--port 11112 --rest-port 22223 --grpc-port 33334 &
v1.4.16+ prints a WARN on startup if a port is already taken.
Client access:
curl http://localhost:22222/api/accounts # Futubull
curl http://localhost:22223/api/accounts # moomoo
5. Debug & development¶
# Verbose logs (protocol frames + HTTP requests/responses)
./futu-opend --login-account X --login-pwd Y --log-level trace
# Auth flow only (POST raw response / salt / tgtgt / error_code)
./futu-opend --login-account X --login-pwd Y --log-level debug 2>&1 \
| grep -E "POST.*raw|salt|tgtgt|error_code"
# JSON structured logs (for Loki / ELK / logging pipelines)
./futu-opend --login-account X --login-pwd Y --json-log
# Separate audit log (auth / trade events only; regular logs unchanged)
./futu-opend --login-account X --login-pwd Y \
--audit-log /var/log/futu-opend/audit.jsonl
6. Recovery¶
device_id locked (ret_type=15 or ret_type=21)¶
# Option A: clear files + fresh setup (recommended, thorough)
./futu-opend --reset-device --setup-only \
--login-account X --login-pwd Y --platform moomoo
# Option B: switch to a specific device_id (e.g. machine migration,
# skips new SMS)
./futu-opend --device-id abcdef0123456789 \
--login-account X --login-pwd Y
# Option C: nuke everything (when all caches are suspect)
rm -rf ~/.futu-opend-rs/
v1.4.17+ auto-rotates device_id on error_code=21 (wrong SMS code) up
to 2 times — usually no manual intervention needed.
Password correct but error_code=2 account/password mismatch¶
Almost always the wrong platform (moomoo account sent to futunn or vice versa):
Connection stalls (Connection timed out)¶
v1.4.10 added a 10s timeout, v1.4.11 picks the IP pool per region, v1.4.12 races 3 IPs in parallel. New versions rarely stall. If it still does:
# See where it's stuck
./futu-opend --login-account X --login-pwd Y --log-level debug
# If you're on an overseas account but the regional IP range is fully
# blocked (ISP filtering), you need a VPN.
Client-side common symptoms¶
| Symptom | Cause | Fix |
|---|---|---|
connect refused :11111 |
Gateway not up / port taken | Check futu-opend console log; v1.4.16+ WARNs on port collision |
account/password mismatch |
Wrong credentials | Fix --login-account / --login-pwd, or add --platform moomoo |
device not authorized |
New device needs SMS | Run --setup-only in a foreground terminal once |
no subscription |
Must subscribe before quoting | POST /api/subscribe before POST /api/quote |
gRPC UNAUTHENTICATED |
No Bearer token or wrong key | See the next section on --grpc-keys-file |
7. Login password secure storage (v1.4.18+)¶
Avoid leaking plaintext passwords via ps aux / ~/.bash_history /
config-file backups. futu-opend resolves the password via a 7-layer
priority chain, and OS keychain is recommended:
# One-time: save to the OS keychain
# (macOS Keychain / Linux Secret Service / Windows Credential Manager)
futucli set-login-pwd --account 12345678
# Interactive password prompt — no echo, no shell history
# Subsequent launches: drop --login-pwd, the keychain is read automatically
./futu-opend --login-account 12345678 --platform moomoo --rest-port 22222
7-layer priority (high → low)¶
| # | Method | Use case | Security |
|---|---|---|---|
| 1 | --login-pwd-file <path> |
Docker secrets / systemd LoadCredential | ⭐⭐⭐⭐⭐ |
| 2 | --login-pwd <plain> |
Legacy scripts (prints a WARN) | ⭐ (argv leak) |
| 3 | --login-pwd-md5 <hex> |
Same, md5 is equivalent to plaintext | ⭐ (argv leak) |
| 4 | FUTU_PWD env var |
CI / cron / bash export | ⭐⭐⭐ (no argv leak) |
| 5 | OS keychain | Long-term local / server (recommended default) | ⭐⭐⭐⭐ |
| 6 | Interactive prompt (when stdin is a tty) | Ad-hoc local dev | ⭐⭐⭐⭐ (no echo, no history) |
| 7 | None of the above | Error out | — |
Deployment scenario guide¶
# A. Long-term local (most recommended)
futucli set-login-pwd --account 12345678 # one-off
./futu-opend --login-account 12345678 ... # every launch auto-reads keychain
# B. Docker (secret mounted as a file)
docker run -v pwd-file:/run/secrets/futu-pwd:ro \
ghcr.io/futuleaf/futu-opend-rs \
--login-account X --login-pwd-file /run/secrets/futu-pwd ...
# C. systemd (LoadCredential from EncryptedDir / TPM)
# /etc/systemd/system/futu-opend.service
[Service]
LoadCredential=login-pwd:/etc/futu/pwd
ExecStart=/usr/local/bin/futu-opend \
--login-account X \
--login-pwd-file ${CREDENTIALS_DIRECTORY}/login-pwd ...
# D. CI / cron (FUTU_PWD env)
FUTU_PWD='...' ./futu-opend --login-account X ...
# E. Ad-hoc local (nothing configured → interactive prompt)
./futu-opend --login-account 12345678 ...
# → "Login password for account 12345678: "
Clear a stored password¶
8. REST / gRPC / core WebSocket auth¶
Strongly recommended in production — otherwise /api/* is open to
anyone who can reach the port.
# 1. Generate an API key (limited scope + expiration)
futucli gen-key \
--keys-file ~/.futu-opend-rs/keys.json \
--scopes quote,trade:read \
--expires 30d
# 2. Start opend with the keys file
./futu-opend --login-account X --login-pwd Y \
--rest-keys-file ~/.futu-opend-rs/keys.json \
--grpc-keys-file ~/.futu-opend-rs/keys.json \
--ws-keys-file ~/.futu-opend-rs/keys.json \
--rest-port 22222 --grpc-port 33333 --websocket-port 44444
# 3. Clients use the Bearer token
curl -H "Authorization: Bearer <key_plaintext>" \
http://localhost:22222/api/qot/get_global_state
See API Key setup for full scope tables and key management.
9. Restricted networks (advanced)¶
HTTPS proxy (debug / capture)¶
# --auth-server explicitly overrides auto-switching (v1.4.15+)
./futu-opend --login-account X --login-pwd Y \
--auth-server http://127.0.0.1:19998
Combined with a self-written HTTP proxy (e.g. mitmproxy) you can capture
the full /authority/salt and POST /authority/ bodies for debugging
account identification issues.
Overseas IPs unreachable¶
Futu's overseas IPs are occasionally ISP-blocked. Symptom: every HK / US IP connect times out (10s each), gateway falls into offline mode.
# First confirm whether it's a network issue
for ip in 101.32.217.163 43.135.111.64; do
nc -vz $ip 9595 # or `timeout 5 bash -c "</dev/tcp/$ip/9595"`
done
- All FAIL, CN IPs reachable → ISP blocking the overseas range → use a VPN
- All FAIL, 443 reachable but 9595 not → firewall blocks 9595 → ask IT to open it
- Partial → v1.4.12's parallel race will auto-pick the reachable IP
10. Credential file locations¶
v1.4.17+ puts all persistent files under ~/.futu-opend-rs/:
~/.futu-opend-rs/
├── device-<hash>.dat # 16-hex device_id (one per account)
├── credentials-<hash>.json # device_sig + tgtgt + rand_key + uid
└── keys.json # (optional) API key file
<hash> = md5(normalized_account)[..16], so +86-13900000000 and
13900000000 share the same file (normalization strips the region code).
When to delete:
| Situation | What to remove |
|---|---|
| device_id locked by the server | device-<hash>.dat + credentials-<hash>.json (or use --reset-device) |
| Password changed | Keep credentials-<hash>.json so SMS retriggers |
| Switching platform (futunn ↔ moomoo) | Nothing — the two platforms have independent caches |
| Total cleanup | rm -rf ~/.futu-opend-rs/ |
Index¶
- First run: Quick Start
- Go deeper: API Key setup / MCP integration / Production deploy
- Full CLI reference: CLI flags
- Issues: see Contact