You built the strategy. You backtested it. You started it with nohup. Three days later you check the P&L and… the process is gone. It has been gone since roughly two hours after you closed the terminal.
"Keep Freqtrade running" is the number one operational question in every Freqtrade forum, and the answers split into two camps: systemd and a cron watchdog. I have run both in production-style setups. Here is the honest comparison, and the pattern I actually use.
Option 1: systemd (the "proper" way)
# /etc/systemd/system/freqtrade.service
[Unit]
Description=Freqtrade
After=network.target
[Service]
User=freqtrade
WorkingDirectory=/home/freqtrade
ExecStart=/home/freqtrade/.venv/bin/freqtrade trade --config user_data/config.json
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
sudo systemctl enable --now freqtrade
What you get: automatic restart on crash, restart on reboot, journalctl logs, clean systemctl stop.
The catches, in practice:
- It only restarts the process. It does not check that the thing is healthy. A Freqtrade process can be alive and stuck: exchange connection dropped, a hung API, a wedged websocket.
Restart=alwayssees a running PID and does nothing. - No P&L record. You find out about a silent stall by noticing the trade count never moved.
- No risk rule. If your bot is bleeding, systemd faithfully keeps it bleeding.
- It needs root to install, and
sudoon a cheap VPS is a thing you have to think about.
systemd is excellent at "make sure a binary is running". It is not an observability or risk layer, and it never claimed to be.
Option 2: cron + watchdog (the pattern I use)
A tiny script, run every 15 minutes, that does three jobs: restart if dead, record health, and enforce a kill switch.
*/15 * * * * /home/user/freqtrade/scripts/crypto_watchdog.sh
The restart detection is the part that matters, and the naive version is a trap:
# DO NOT DO THIS
if ! pgrep -f freqtrade > /dev/null; then restart; fi
pgrep -f matches the full command line, and your watchdog's own invocation may contain the pattern — so the script can match itself, conclude "all good", or pkill its own shell. I hit exactly this and wasted an hour on a watchdog that was killing itself.
Do it properly by scanning /proc and requiring two independent facts:
VENV_PY=$(readlink -f "$BASE/.venv/bin/python")
find_bot() {
for p in /proc/[0-9]*; do
pid=${p#/proc/}
[ -r "$p/cmdline" ] || continue
cmd=$(tr '\0' ' ' < "$p/cmdline" 2>/dev/null) || continue
case "$cmd" in
*"freqtrade trade --config"*)
exe=$(readlink -f "$p/exe" 2>/dev/null)
[ "$exe" = "$VENV_PY" ] && echo "$pid"
;;
esac
done
}
-
/proc/<pid>/cmdlineis the real argv (NUL-separated), not a shell's mangled string. -
/proc/<pid>/exemust resolve to your venv interpreter — so you can never match a stray system Python or the watchdog itself.
Then the loop adds the two things systemd does not:
if [ -z "$(find_bot)" ]; then
nohup "$BASE/.venv/bin/freqtrade" trade --config "$BASE/config.json" \
--strategy SwingHighToSky --dry-run >> "$BASE/logs/trade_stdout.log" 2>&1 &
sleep 30
[ -n "$(find_bot)" ] || { echo "$(ts) restart FAILED"; exit 1; }
echo "crypto-bot: restarted at $(ts)" # speaks only when it acts
fi
OUT=$("$PY" "$BASE/monitor_pnl.py" 2>&1); RC=$?
if [ $RC -eq 2 ]; then # health script says: drawdown >= 10%
for pid in $(find_bot); do kill "$pid"; done
echo "$(ts) DRAWDOWN LIMIT HIT -> bot stopped"
exit 2
fi
echo "$OUT" >> "$LOG" # one P&L line every 15 minutes
monitor_pnl.py polls the local Freqtrade REST API (/api/v1/profit, /api/v1/status, /api/v1/balance) and appends a single line:
2026-09-25T15:02:58Z wallet=500.13 closed_pnl=+0.000USDT (+0.00%) all_pnl=-0.000USDT trades=0W/0L wr=0.0% open=1
Now you have a time series, not a vibe. You can see the day the behaviour changed.
The comparison, honestly
| systemd | cron + watchdog | |
|---|---|---|
| restart on crash | ✅ | ✅ |
| restart on reboot | ✅ (enable) |
✅ (cron is persistent) |
| needs root | ✅ | ❌ |
| detects "alive but stuck" | ❌ | ✅ (health script) |
| P&L time series | ❌ | ✅ |
| enforced risk rule | ❌ | ✅ |
| moves to any machine in 2 files | ❌ | ✅ |
Neither is "better". systemd is the right tool if all you need is "keep the binary up" and you have root. The watchdog wins when you want evidence and a kill switch — which, for anything handling money, is the actual requirement.
You can also run both: systemd to survive reboots, the watchdog cron for health and risk. They do not conflict.
What actually kills unattended bots
Ranked by how often I have seen them:
- The process dies and nobody notices — the watchdog fixes this in the first five minutes.
- It runs for weeks and you never look at the data — the P&L log fixes this.
- It draws down and you are asleep — the kill switch fixes this.
-
Machine reboots and nothing restarts it — cron/
enablefixes this.
Notice that three of the four are observability and risk, not supervision.
I packaged this exact layer — the /proc-based watchdog, the P&L logger and the drawdown kill switch, with a dry-run config and setup guide — as the Freqtrade Self-Healing Starter Kit ($9). It is the code from this post, ready to drop in, so you skip the hour I lost on the self-killing watchdog.
Related: Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern) — the same pattern, generalised beyond Freqtrade.
Related in this series:
- Run a self-healing crypto trading bot 24/7 for $0 (Freqtrade + watchdog)
- Keep any long-running Python process alive on Linux for $0 (the /proc watchdog pattern)
- I backtested 14 trading strategies. Only one passed my filters.
The watchdog from this post is open source: **https://github.com/moon-hacks/proc-watchdog* (MIT).*
Prefer someone to just do it? I also offer a **done-for-you setup* (https://tntofficial.gumroad.com/l/vweza) — Freqtrade installed, backtested and left running in dry-run with the watchdog active.*
Top comments (0)