Quick Takeaway: Production deployment of a 24/7 Freqtrade crypto bot—particularly with active FreqAI adaptive modeling—requires a dedicated KVM hypervisor provisioned with a minimum of 2 dedicated vCPUs (enforcing 0.0% CPU steal time), 4–8 GB ECC RAM to prevent kernel OOM killer evictions during rolling feature-engineering retrains, and 40 GB PCIe 4.0 NVMe storage. Low-latency order routing demands an unthrottled 1 Gbps network uplink tuned with TCP BBR (net.ipv4.tcp_congestion_control = bbr) to sustain sub-15 ms $p99$ round-trip times across CCXT WebSocket feeds and exchange REST endpoints. Run the stack strictly inside Docker Compose bounded by cgroups v2 memory quotas (memory.max), persisting local state via isolated SQLite or PostgreSQL volumes while streaming asynchronous, non-blocking Telegram event telemetry.
Table of Contents
- Why Cloud VPS is Mandatory for Quantitative and High-Frequency Crypto Trading
- Hardware Sizing for Freqtrade Backtesting and FreqAI Machine Learning Models
- Deploying Freqtrade via Docker Compose with Secure Volume Mapping
- Exchange API Key Security, IP Whitelisting, and Firewall Rules
- Configuring FreqUI Web Dashboard and Telegram Remote Control Bot
- Automated Backups of SQLite Trade History and Disaster Recovery Procedures
- Frequently Asked Questions (FAQ)
Why Cloud VPS is Mandatory for Quantitative and High-Frequency Crypto Trading
Deploying an algorithmic execution framework like Freqtrade on a local workstation or consumer desktop introduces catastrophic single points of failure. In quantitative finance, infrastructure reliability is inseparable from strategy profitability. Consumer environments suffer from non-deterministic ACPI power states (S3/S0ix sleep triggers, OS update reboot cycles), local hardware thermal throttling, residential ISP packet drops, and dynamic IP lease renegotiations. For automated strategies executing continuous DCA, grid spreads, or high-frequency order book momentum, a 30-second localized outage or an unhandled TCP disconnect translates immediately into unhedged market delta, broken state management, and severe execution slippage.
Reliable deployment of a freqtrade crypto bot on vps requires deterministic compute availability, isolated kernel control, and ultra-low jitter transport paths directly into exchange matching engines.
Execution Latency, Network Jitter, and Order Book Desynchronization
Cryptocurrency derivative and spot exchanges—including Binance, Bybit, OKX, and Coinbase—host their core matching engines and public gateway clusters within Tier-1 hyperscaler data centers, primarily AWS eu-central-1 (Frankfurt), AWS ap-southeast-1 (Singapore), and Equinix hubs in Tokyo and Dublin.
Residential broadband relies on multi-hop consumer transit, asymmetric routing policies, and carrier-grade NAT (CGNAT) traversal. When high volatility hits the market (such as during liquidations cascades or macroeconomic data releases), exchange WebSocket endpoints push tens of thousands of incremental depth update events (depthUpdate) per second. Under these conditions, consumer routers experience bufferbloat, causing packet queuing delays where p99 latency spikes from 20 ms to over 450 ms.
[ Exchange Matching Engine ] (e.g., AWS eu-central-1 Frankfurt)
│
├─► Public BGP Core (IXP: DE-CIX / AMS-IX)
│ │
│ ▼ [Direct Tier-1 Uplink: ~1–3 ms, p99 jitter < 0.5 ms]
│ [ tropic.host Cloud KVM VPS ] ──► Freqtrade Engine (Zero Queueing)
│
└─► Multi-Hop Transit ASNs ──► ISP CGNAT Gateway ──► Local Wi-Fi / FTTH
│
▼ [Bufferbloat / Drops: ~45–350 ms, p99 jitter > 120 ms]
[ Local PC / Desktop ] ──► Stale Order Book (Slippage / Missed Fills)
If your local instance drops or delays TCP packets, Freqtrade’s internal order book desynchronizes from the matching engine. The bot computes signal indicators (such as EMA crosses or Order Flow Imbalance) against stale order book snapshots. When the bot finally dispatches an execution order via REST HTTP POST, the fill price has already shifted, triggering immediate negative slippage or an outright exchange rejection with error codes like EXPIRED_ORDER or PRICE_OUT_OF_BOUNDS.
To verify transit latency and packet loss to target exchange endpoints from your current host, run a continuous MTR audit:
# Audit BGP routing stability, packet loss, and jitter over 100 cycles to Binance API
mtr --report --report-cycles 100 --tcp --port 443 api.binance.com
In production testing, instances deployed on cloud backbones exhibit $0.0\%$ packet loss and sub-millisecond standard deviation (stdev < 0.8), whereas residential connections regularly log $1.5\text{–}4.0\%$ intermittent drop rates during peak ISP utilization hours.
Infrastructure Comparison: Consumer Hardware vs. Generic VPS vs. High-Performance KVM
The following matrix contrasts operational environments across metrics critical for algorithmic trading execution:
| Architectural Metric | Consumer Workstation (Local ISP) | Budget Oversold VPS (OpenVZ / Shared KVM) | Enterprise KVM Cloud (tropic.host) |
|---|---|---|---|
| Virtualization Isolation | None (Bare Metal Barely Isolated / OS Bloat) | Container (OpenVZ/LXC) or Oversubscribed KVM | Pure Hardware KVM (Dedicated kernel space) |
CPU Steal Time (%st) |
N/A (Internal OS Thread Contention) | High & Unpredictable ($5.0\% - 25.0\%+$) | Guaranteed $0.0\%$ (%st = 0.0%, AMD EPYC / Ryzen 9) |
| Storage Performance (4K QD1) | Consumer SATA / PCIe 3.0 NVMe (Thermal drops) | Shared HDD / Saturated Network SAN (< 1,500 IOPS) | Enterprise NVMe PCIe 4.0 ($> 50,000$ IOPS, no throttle) |
| Network Transit & Uplink | Residential Asymmetric (100–500 Mbps, CGNAT) | Shared 100 Mbps (Throttled, packet drops) | 1–10 Gbps Unmetered, Direct IXP Peering (DE-CIX) |
| TCP Congestion Algorithm | OS Default (cubic with high bufferbloat) |
Vendor Locked (cubic or disabled modules) |
TCP BBR + FQ enabled at Linux kernel level |
| P99 API Latency (Frankfurt) | 35 ms – 280 ms (High jitter) | 15 ms – 120 ms (Noisy neighbor spikes) | < 1.8 ms – 4.5 ms (Stable, low-jitter baseline) |
| Uptime & Power Resilience | $97.0\% - 99.0\%$ (Grid/OS update vulnerable) | $99.0\% - 99.5\%$ (Unplanned host reboots) | $99.99\%$ SLA (Redundant data center A+B power) |
| IPv4 Address Reputation | Dynamic DHCP (Residential pool, WAF alerts) | Blacklisted / Flagged Cloud Subnets | Clean Dedicated Static IPv4 (Zero WAF/Cloudflare blocks) |
Hypervisor Schedulers and the Threat of CPU Steal Time (%st)
In automated crypto trading, computational determinism is as vital as network throughput. Freqtrade’s execution pipeline relies on Python’s asyncio event loop. The loop continuously handles incoming WebSocket frames, updates rolling Pandas/NumPy DataFrames, checks stop-loss triggers, and dispatches HTTP requests via the ccxt library.
When running on budget hosting providers that oversubscribe physical CPU cores, multiple tenant virtual machines compete for the same physical execution units. When a neighboring tenant initiates a compile job or crypto mining process, the hypervisor scheduler suspends your virtual CPU. The Linux kernel logs this waiting duration as CPU Steal Time (%st).
You can diagnose this bottleneck via vmstat and mpstat:
# Check CPU steal time under operational trading loads
vmstat 1 5
Inspect the last column (st):
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 2841288 124912 948120 0 0 0 12 1204 2410 12 4 84 0 0
If %st rises above $0.0\%$, your Python process experiences micro-stalls. A 50 ms scheduler pause prevents the asyncio event loop from processing incoming TCP buffers. During this window, incoming market packets accumulate in the socket receive queue (SO_RCVBUF). If the buffer overflows, the Linux kernel drops incoming packets silently, triggering TCP retransmissions, zero-window advertisements, and delayed stop-loss execution.
Using an infrastructure baseline like tropic.host prevents this failure mode entirely: KVM virtualization enforces dedicated vCPU thread-to-core pinning without oversubscription, guaranteeing %st = 0.0% across sustained analytical workloads on modern AMD EPYC and Ryzen 9 server platforms.
Production Kernel Tuning for Ultra-Low Latency WebSocket Streaming
Standard Linux distributions are pre-configured for generic server workloads, utilizing conservative buffer allocations and TCP Cubic congestion control. When deploying a freqtrade crypto bot on vps, modify the kernel's network stack to prioritize rapid socket buffer drainage, prevent TCP slow-start collapses after market quiet periods, and mitigate TCP head-of-line blocking.
Create a dedicated kernel tuning profile:
sudo tee /etc/sysctl.d/99-crypto-trading.conf << 'EOF'
# Increase maximum socket receive and send buffer sizes for high-throughput WebSocket streams
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 262144
net.core.wmem_default = 262144
# Configure TCP memory allocation (min, default, max in pages)
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# Enable TCP BBR Congestion Control with Fair Queueing (FQ)
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# Prevent TCP from resetting congestion window to initial state after idle periods
# Critical for trading bots waiting between infrequent order signals
net.ipv4.tcp_slow_start_after_idle = 0
# Mitigate local socket transmission latency (buffer bloat at the host level)
net.ipv4.tcp_notsent_lowat = 16384
# Enable rapid TCP TIME_WAIT socket reuse for heavy REST API polling
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 15
# Increase backlog queue depth to prevent dropped packets during high-volume market bursts
net.core.netdev_max_backlog = 10000
net.core.somaxconn = 4096
# Memory management: minimize swap aggressiveness to avoid memory pagination latency
vm.swappiness = 10
vm.dirty_ratio = 15
vm.dirty_background_ratio = 5
EOF
# Apply kernel settings immediately without rebooting
sudo sysctl --system
The combination of net.core.default_qdisc = fq and net.ipv4.tcp_congestion_control = bbr tracks bottleneck bandwidth and round-trip propagation times independently, preventing packet loss over congested public backbones. Setting tcp_slow_start_after_idle = 0 ensures that when Freqtrade dispatches an urgent order after minutes of market consolidation, the Linux kernel does not artificially throttle the congestion window down to initial defaults ($10\times\text{MSS}$).
Process Isolation and Resource Governance with Cgroups v2
Trading applications must be isolated from auxiliary host processes. If a background backup script, log compressor (logrotate), or security scanner spikes system resources, the trading bot's thread must not be starved of CPU cycles or terminated by the Linux Out-Of-Memory (OOM) Killer.
When operating Freqtrade via Docker Compose on a system running modern cgroups v2, configure explicit resource constraints and high-priority process weights:
version: '3.8'
services:
freqtrade:
image: freqtradeorg/freqtrade:stable
restart: always
container_name: freqtrade_production
volumes:
- "./user_data:/freqtrade/user_data"
command: >
trade
--config /freqtrade/user_data/config.json
--strategy AdvancedOrderFlowStrategy
deploy:
resources:
limits:
cpus: '2.0'
memory: 4096M
reservations:
cpus: '1.0'
memory: 2048M
ulimits:
nofile:
soft: 65535
hard: 65535
# Enforce priority execution inside systemd / cgroups v2
cpu_shares: 2048
oom_score_adj: -500
logging:
driver: "json-file"
options:
max-size: "50m"
max-file: "5"
The directive oom_score_adj: -500 lowers the container's badness score inside /proc/[pid]/oom_score, guaranteeing that if the host encounters unexpected RAM pressure, the Linux kernel terminates auxiliary background utilities long before touching the Freqtrade trading instance.
API Key Security and Static IPv4 Whitelisting
Major centralized crypto exchanges enforce strict security perimeters. To mitigate the risk of compromised API secrets, exchanges mandate Static IP Whitelisting for API credentials that carry order-placement and withdrawal capabilities:
[ Compromised API Key ] ──► Access Attempt from Unknown IP ──► Exchange Edge: REJECTED (HTTP 403)
[ Freqtrade Instance ] ──► Dedicated Static IPv4 (tropic.host) ──► Exchange Engine: AUTHORIZED
- Elimination of IP Drift: Consumer ISPs routinely rotate dynamic IPv4 assignments every 24 to 72 hours or force reconnections during PPPoE renegotiations. An IP rotation instantly breaks exchange API connectivity, halting automated order management until manual whitelisting can be reconfigured through 2FA.
- CDN & Edge Reputation: Residential IP blocks frequently share subnets with malware-infected consumer machines. Cloudflare and AWS CloudFront—which front-end exchange REST APIs—frequently trigger invisible Cloudflare turnstile checks, HTTP 403 Forbidden errors, or HTTP 429 rate-limiting responses against suspicious dynamic ASN ranges.
- Clean Route Isolation: Utilizing an isolated, dedicated static IPv4 provided by an engineering-grade platform like tropic.host gives your trading infrastructure a permanent, enterprise-grade IP profile. Unmetered 1–10 Gbps uplinks with direct peering at major European and Asian Internet exchanges (DE-CIX Frankfurt, AMS-IX Amsterdam, Equinix Singapore) guarantee that trade execution routes remain immutable, deterministic, and protected against layer 3/4/7 network anomalies.
Hardware Sizing for Freqtrade Backtesting and FreqAI Machine Learning Models
Deploying a production-grade freqtrade crypto bot on vps introduces diametrically opposed compute demands across its operational lifecycle. While live order execution with 15–30 whitelisted trading pairs consumes negligible compute overhead (< 5% of a single core and ~1.5 GB RAM), offline strategy validation and continuous machine learning inference present extreme computational friction:
┌────────────────────────────────────────────────────────────────────────────────────────┐
│ COMPUTE LOAD & MEMORY PROFILES │
├──────────────────────────┬─────────────────────────────┬───────────────────────────────┤
│ Operational Mode │ CPU Characteristics │ Memory Footprint & I/O │
├──────────────────────────┼─────────────────────────────┼───────────────────────────────┤
│ Live Execution │ I/O-bound (asyncio REST/WS) │ Static (~1.5–2.5 GB RSS) │
│ Multi-Pair Backtesting │ Compute-bound (SIMD/IPC) │ Transient (4–16 GB Dataframe) │
│ FreqAI Active Retraining │ Mixed Compute/Memory-bound │ Extreme (16–64+ GB Dynamic) │
└──────────────────────────┴─────────────────────────────┴───────────────────────────────┘
Selecting undersized or oversubscribed infrastructure directly corrupts model training, artificially skews backtesting latency metrics, and causes sudden process termination by the Linux Out-Of-Memory (OOM) Killer.
The Memory Math of FreqAI Feature Engineering
The primary cause of silent container crashes in Freqtrade environments is uncalculated memory ballooning during feature engineering. When running conventional strategies, Freqtrade stores candlestick history in standard pandas DataFrames using 64-bit precision (float64, consuming 8 bytes per cell).
FreqAI radically amplifies this base footprint. When configuring predictive models (such as LightGBM, CatBoost, XGBoost, or PyTorch neural networks), FreqAI expands historical market data through expansive feature pipelines: multiple timeframe lookbacks, rolling statistical indicators, Hilbert transforms, and recursive lag vectors (include_shifted_candles):
$$\text{Memory}{\text{raw}} = N{\text{pairs}} \times N_{\text{candles}} \times N_{\text{features}} \times 8\text{ bytes}$$
$$\text{Memory}{\text{training}} \approx \text{Memory}{\text{raw}} \times M_{\text{overhead}}$$
The memory multiplier $M_{\text{overhead}}$ ranges between $2.5\times$ and $4.5\times$ due to intermediate data allocation during: 1. Recursive concatenation (pd.concat) during window shifts. 2. Feature scaling matrices (e.g., RobustScaler, StandardScaler). 3. Principal Component Analysis (PCA) dimensionality reduction transformations. 4. Train/test splitting buffers and internal gradient boosting histogram structures.
Worked Sizing Scenario
Assume a FreqAI configuration targeting 30 cryptocurrency trading pairs across 1 year of historical 5-minute data with lookback windows: * Candles per pair per year: $\frac{365 \times 24 \times 60}{5} = 105{,}120 \text{ candles}$ * Total input rows: $30 \times 105{,}120 = 3{,}153{,}600 \text{ rows}$ * Engineered features: 250 (momentum, volatility, volume profiles across 5m, 15m, 1h timeframes, plus 5 shifted steps) * Raw array size: $3{,}153{,}600 \times 250 \times 8 \text{ bytes} \approx 6.31 \text{ GB}$
During model retraining (fit()), pandas, NumPy, and Scikit-learn create contiguous memory buffers for gradient descent and split histograms. Peak memory easily surges to 18–25 GB. If your VPS instance provides only 8 GB or 16 GB of physical RAM, the Linux kernel invokes oom-killer, silently killing the Python PID mid-retrain and leaving open market orders unmanaged.
CPU Architecture, Single-Core IPC, and the 0.0% Steal Time Imperative
Freqtrade backtesting parallelizes processing across trading pairs using Python's concurrent.futures and multiprocessing workers. However, strategy code evaluation, custom indicators, and inner loop indicator calculations remain strictly limited by Python’s Global Interpreter Lock (GIL) and single-core Instructions Per Cycle (IPC) throughput.
Hypervisor Oversubscription (%st) vs. Deterministic Execution
On generic, budget cloud VPS hosts, providers overcommit physical CPU cores by ratios of 4:1 to 10:1. When neighboring virtual machines execute compute-heavy tasks, the hypervisor's Kernel-based Virtual Machine (KVM) scheduler deschedules your vCPU slices, resulting in elevated CPU Steal Time (%st).
# Check for CPU Steal Time under backtest load
$ top -b -n 1 | grep "%Cpu(s)"
%Cpu(s): 88.2 us, 2.1 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.2 si, 9.5 st
In the output above, %st reaches 9.5%. In Python multiprocessing workloads: * If one worker thread holding an IPC pipe or inter-process lock is descheduled by hypervisor steal time, all sibling workers stall waiting for barrier synchronization. * Backtesting runtime increases non-linearly (e.g., an 8-hour hyperopt run stretches to 22 hours). * In live trading, CPU starvation introduces latency spikes in the bot_loop iteration, delaying order generation past candle-close timestamps.
To guarantee zero hypervisor contention, run your freqtrade crypto bot on vps using dedicated KVM virtualization from tropic.host. With guaranteed allocations on high-frequency AMD EPYC and Ryzen 9 platforms delivering 0.0% CPU Steal Time (%st = 0.0%), all vector calculations and thread synchronization execute without host-level preemption.
Storage Subsystem: NVMe IOPS vs. Disk Serialization Bottlenecks
While historical candle extraction occurs predominantly in memory, high-frequency backtesting, Hyperopt sweeps, and FreqAI model persistence generate substantial I/O:
- Candle Caching: Freqtrade reads and writes compressed data frames using the Feather (Apache Arrow IPC) or Parquet formats (
--data-format-feather). Feather serialization achieves speeds near memory bus bandwidth, but only if underlying storage does not bottleneck on random access. - FreqAI Checkpointing: FreqAI writes trained model artifacts, PCA weights, and normalization metadata to disk every
retrain_interval(often every 50 to 120 candles). On a 40-pair setup, model serialization writes hundreds of megabytes of pickle/joblib binaries simultaneously. - Logging and Database Flush: SQLite/PostgreSQL trades and order state logs write transactions synchronously with
WAL(Write-Ahead Logging) mode.
┌─────────────────────────┬──────────────────────┬───────────────────────┐
│ Storage Architecture │ 4K QD1 Random Read │ p99 Write Latency │
├─────────────────────────┼──────────────────────┼───────────────────────┤
│ Shared Ceph / Cloud SAN │ 1,200–3,500 IOPS │ 8.5–25.0 ms │
│ Local SATA SSD │ 8,000–12,000 IOPS │ 1.2–3.0 ms │
│ Enterprise NVMe (PCIe4) │ 55,000–75,000+ IOPS │ < 0.08 ms (80 µs) │
└─────────────────────────┴──────────────────────┴───────────────────────┘
Shared network-attached block storage architectures introduce high p99 I/O wait times (%wa). When the Freqtrade event loop blocks on disk writes to save model binaries, trading iterations pause, risking missed limit fills. tropic.host equips each instance with local enterprise-grade PCIe 4.0 NVMe SSDs achieving random 4K QD1 reads exceeding 50,000 IOPS, eliminating I/O wait states during simultaneous model checkpointing and high-frequency trade logging.
Production Hardware Sizing Matrix
Match your infrastructure allocation directly to your computational profile:
| Profile | Strategy Scope & Algorithms | Minimum vCPU | Minimum RAM | Storage Allocation | Recommended Specification |
|---|---|---|---|---|---|
| Live Production | 10–30 pairs, TA-Lib indicators, no ML, 1m/5m timeframe | 2 vCPU (High IPC) | 4 GB ECC | 40 GB NVMe | Entry KVM Node (tropic.host) |
| Strategy Development & Backtesting | 50+ pairs, 2–3 year history, Hyperopt parameter optimization | 8–16 vCPU | 16–32 GB ECC | 100 GB NVMe | Compute-Optimized KVM Node |
| FreqAI Production Inference | 20–40 pairs, LightGBM/CatBoost, continuous retraining (1–4h) | 8 vCPU | 32 GB ECC | 160 GB NVMe | Memory-Optimized KVM Node |
| FreqAI Research & Deep Learning | 60+ pairs, PyTorch/TensorFlow, deep feature sets, PCA | 16–32 vCPU | 64–128 GB ECC | 300+ GB NVMe | Dedicated Bare-Metal / High-RAM KVM |
Linux Kernel and cgroups v2 Optimization for Heavy Backtesting Nodes
To stabilize long-running backtest suites and machine learning training routines against kernel paging stalls, apply targeted sysctl parameters on the host or container base.
1. Kernel Memory and Scheduler Tuning
Create /etc/sysctl.d/99-freqtrade-perf.conf to configure virtual memory behavior and process migration latency:
# /etc/sysctl.d/99-freqtrade-perf.conf
# Minimize aggressive swap usage while preserving memory headroom
vm.swappiness = 10
# Disable overcommit heuristics to prevent sudden OOM cascades during allocations
vm.overcommit_memory = 1
# Maintain minimum free memory reserves for network buffer allocation
vm.min_free_kbytes = 1048576
# Ensure smooth background dirty page flushing to high-speed NVMe
vm.dirty_background_ratio = 5
vm.dirty_ratio = 10
# Discourage premature migration of compute threads across disparate CPU cores
kernel.sched_migration_cost_ns = 5000000
# Increase maximum file descriptors for concurrent WebSocket connections and local caches
fs.file-max = 2097152
Apply the configuration immediately:
sudo sysctl -p /etc/sysctl.d/99-freqtrade-perf.conf
2. Deterministic Resource Management via Docker Compose (cgroups v2)
When running Freqtrade alongside auxiliary services (PostgreSQL, Grafana, Promtail), constrain container footprints to prevent a single hyperopt run from crashing the entire system.
version: '3.8'
services:
freqtrade_freqai:
image: freqtradeorg/freqtrade:stable_freqai
container_name: freqtrade_freqai
restart: unless-stopped
volumes:
- ./user_data:/freqtrade/user_data
environment:
- PYTHONUNBUFFERED=1
- OMP_NUM_THREADS=8
- OPENBLAS_NUM_THREADS=8
- MKL_NUM_THREADS=8
# cgroups v2 resource governance
deploy:
resources:
limits:
cpus: '7.5'
memory: 28G
reservations:
cpus: '4.0'
memory: 16G
ulimits:
nofile:
soft: 65535
hard: 65535
command: >
trade
--config /freqtrade/user_data/config.json
--strategy FreqaiExampleStrategy
--freqaimodel LightGBMRegressor
Setting OMP_NUM_THREADS, OPENBLAS_NUM_THREADS, and MKL_NUM_THREADS explicitly aligns linear algebra subroutines with the designated vCPU limit. Leaving these unset allows NumPy and LightGBM to spawn thread pools matching the total logical cores of the host hypervisor, generating excessive context switching overhead (perf stat context-switches > 150,000/s) that reduces net computational throughput.
Deploying Freqtrade via Docker Compose with Secure Volume Mapping
Running an automated trading engine in production demands deterministic isolation between the immutable application runtime, operational strategy code, mutable state (databases, market telemetry caches), and sensitive exchange credentials. In a containerized environment, misconfigured storage permissions or leaky volume mounts introduce two primary failure modes: container escape vectors through privilege escalation, and SQLite database is locked panics caused by asynchronous filesystem latency spikes across host-mount boundaries.
Operating a high-frequency or multi-pair freqtrade crypto bot on vps infrastructure requires structuring the host filesystem strictly around the upstream image's unprivileged execution model while hardening container capabilities at the Linux kernel level.
Host Directory Hierarchy and File Permissions
Freqtrade’s official Docker images (freqtradeorg/freqtrade) execute under an unprivileged user named ftuser with a fixed UID of 1000 and GID of 1000. Mounting host directories into /freqtrade/user_data without explicitly aligning discretionary access control (DAC) permissions leads to immediate runtime failures when the engine attempts to initialize state databases, write backtest caches, or rotate log files.
Establish a dedicated base directory on the host (typically /opt/freqtrade or /srv/freqtrade) with restrictive ownership. Run the following provisioning sequence as root or via sudo:
# Define deployment root and user_data hierarchy
DEPLOY_DIR="/opt/freqtrade"
sudo mkdir -p "${DEPLOY_DIR}/user_data"/{backtest_results,data,hyperopts,logs,plot,strategies}
# Enforce host user mapping matching container internal UID:GID (1000:1000)
sudo chown -R 1000:1000 "${DEPLOY_DIR}/user_data"
# Lock down directory permissions: read/write/execute for ftuser only
sudo chmod -R 750 "${DEPLOY_DIR}/user_data"
# Create a restricted environment file for exchange API secrets
sudo touch "${DEPLOY_DIR}/.env"
sudo chown 1000:1000 "${DEPLOY_DIR}/.env"
sudo chmod 600 "${DEPLOY_DIR}/.env"
The tree structure on the host must match the directory schema expected by the trading engine:
/opt/freqtrade/
├── .env # chmod 600: Contains API keys and webhook secrets
├── docker-compose.yml # chmod 644: Production orchestration spec
└── user_data/ # chmod 750: Owned by 1000:1000
├── backtest_results/ # Backtesting artifacts and feather files
├── config.json # chmod 600: Core trading logic configuration
├── data/ # Historical OHLCV feather/json data
├── hyperopts/ # Parameter optimization spaces
├── logs/ # Rotating file logs (freqtrade.log)
├── plot/ # Generated HTML/Vega charts
├── strategies/ # Custom Python trading strategies
└── tradesv3.sqlite # Runtime state database (auto-generated)
Hardened Production docker-compose.yml
The orchestration specification must decouple mutable data directories from configuration files that should remain immutable from inside the container. Mounting config.json and strategies/ as read-only (:ro) guarantees that even in the event of an arbitrary code execution vulnerability within a third-party dependency, the trading bot cannot overwrite its own configuration or modify the active algorithmic code at runtime.
Save the following specification to /opt/freqtrade/docker-compose.yml:
version: '3.8'
services:
freqtrade:
image: freqtradeorg/freqtrade:stable
container_name: freqtrade_engine
restart: unless-stopped
user: "1000:1000"
# Drop all default Linux capabilities to prevent host privilege escalation
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# Mount ephemeral memory for volatile scratchfiles to reduce NVMe wear
tmpfs:
- /tmp:rw,noexec,nosuid,size=128m
environment:
- PYTHONUNBUFFERED=1
- TZ=Etc/UTC
# Exchange credentials injected via host .env file
- EXCHANGE_KEY=${EXCHANGE_KEY}
- EXCHANGE_SECRET=${EXCHANGE_SECRET}
- EXCHANGE_PASSWORD=${EXCHANGE_PASSWORD:-}
- TELEGRAM_TOKEN=${TELEGRAM_TOKEN:-}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID:-}
volumes:
# Read-only mounts for code and configuration integrity
- ./user_data/config.json:/freqtrade/user_data/config.json:ro
- ./user_data/strategies:/freqtrade/user_data/strategies:ro
# Read-write mounts for operational state and logging
- ./user_data/data:/freqtrade/user_data/data:rw
- ./user_data/logs:/freqtrade/user_data/logs:rw
- ./user_data/backtest_results:/freqtrade/user_data/backtest_results:rw
- ./user_data/hyperopts:/freqtrade/user_data/hyperopts:rw
- ./user_data/plot:/freqtrade/user_data/plot:rw
# Bind mount parent dir for the SQLite database file and WAL journaling
- ./user_data:/freqtrade/user_data:rw
# cgroups v2 resource accounting and limits
deploy:
resources:
limits:
cpus: '2.0'
memory: 4096M
reservations:
cpus: '1.0'
memory: 2048M
ulimits:
nofile:
soft: 65535
hard: 65535
# Direct port binding strictly bound to localhost for reverse proxy or SSH tunneling
ports:
- "127.0.0.1:8080:8080"
command: >
trade
--logfile /freqtrade/user_data/logs/freqtrade.log
--config /freqtrade/user_data/config.json
--strategy ClucHAnSTDRangeStrategy
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
Key Architecture Directives Explained:
cap_drop: [ALL]&no-new-privileges:true: Strips every kernel capability (CAP_NET_ADMIN,CAP_SYS_ADMIN,CAP_CHOWN, etc.). The containerized process cannot manipulate routing tables, inspect host processes, or bypass filesystem permissions through setuid binaries.user: "1000:1000": Forces the runtime engine to execute asftuser. Even if the underlying base image is accidentally rebuilt with a root entrypoint, Docker rejects root execution.127.0.0.1:8080:8080(Localhost Binding): Exposing Freqtrade's REST API and WebUI (FreqUI) on0.0.0.0creates a critical vulnerability if API credentials are leaked or subject to brute-force attacks. Binding exclusively to the loopback interface ensures the interface is accessible only via an SSH tunnel (ssh -L 8080:localhost:8080 user@vps-ip) or an authenticated, TLS-terminated reverse proxy (e.g., NGINX with Let's Encrypt).- Storage Subsystem Requirements: Freqtrade relies on SQLite in Write-Ahead Logging (WAL) mode by default (
tradesv3.sqlite,tradesv3.sqlite-wal,tradesv3.sqlite-shm). When executing high-concurrency order tracking across dozens of pairs, SQLite issues frequentfsync()system calls to guarantee ACID compliance. On oversubscribed virtual platforms where disk I/O is shared,fsync()latency spikes from microseconds to hundreds of milliseconds, blocking Python’s single-threadedasyncioevent loop. Deploying on a certified tropic.host KVM VPS provides enterprise-grade PCIe 4.0 NVMe storage running with zero host-level overcommit (%st = 0.0%). Dedicated NVMe controllers consistently deliver 4K random write latency under 50 microseconds, preventing transaction lockups and maintaining p99 order execution times well within exchange-mandated thresholds.
Secure Environment and config.json Structuring
To avoid committing sensitive API keys to source control or baking them into container layers, decouple secrets using environment variable interpolation.
Populate /opt/freqtrade/.env with your exchange credentials:
# /opt/freqtrade/.env
EXCHANGE_KEY=abc1234567890def
EXCHANGE_SECRET=1234567890abcdef1234567890abcdef
EXCHANGE_PASSWORD=OptionalPassphraseForOKXOrKucoin
TELEGRAM_TOKEN=123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ
TELEGRAM_CHAT_ID=-1001234567890
Configure /opt/freqtrade/user_data/config.json. Ensure the exchange and telegram blocks reference the injected environment variables rather than hardcoded credentials:
{
"max_open_trades": 5,
"stake_currency": "USDT",
"stake_amount": "unlimited",
"tradable_balance_ratio": 0.95,
"dry_run": false,
"dry_run_wallet": 1000,
"cancel_open_orders_on_exit": false,
"trading_mode": "spot",
"margin_mode": "",
"timeframe": "5m",
"exchange": {
"name": "binance",
"key": "${EXCHANGE_KEY}",
"secret": "${EXCHANGE_SECRET}",
"password": "${EXCHANGE_PASSWORD}",
"ccxt_config": {
"enableRateLimit": true,
"rateLimit": 50
},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 50
},
"pair_whitelist": [
"BTC/USDT",
"ETH/USDT",
"SOL/USDT",
"BNB/USDT"
],
"pair_blacklist": [
".*(UP|DOWN|BULL|BEAR)/.*",
".*AUD/.*"
]
},
"entry_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1,
"price_last_balance": 0.0,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"exit_pricing": {
"price_side": "same",
"use_order_book": true,
"order_book_top": 1
},
"telegram": {
"enabled": true,
"token": "${TELEGRAM_TOKEN}",
"chat_id": "${TELEGRAM_CHAT_ID}",
"notification_settings": {
"status": "on",
"warning": "on",
"startup": "on",
"entry": "on",
"entry_cancel": "on",
"entry_fill": "on",
"exit": "on",
"exit_cancel": "on",
"exit_fill": "on",
"protection_trigger": "on"
}
},
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080,
"verbosity": "info",
"enable_openapi": false,
"jwt_secret_key": "generate-a-random-64-character-hex-string-here",
"CORS_origins": [],
"username": "freqtrade_admin",
"password": "UseAStrongGeneratedHashPasswordHere"
},
"bot_name": "tropic_freqtrade_prod",
"initial_state": "running",
"force_entry_enable": false,
"internals": {
"process_throttle_secs": 5
}
}
Note on api_server.listen_ip_address: Inside the container network namespace, setting listen_ip_address: "0.0.0.0" is required so the process accepts incoming connections forwarded across the Docker bridge. External exposure remains strictly controlled via ports: ["127.0.0.1:8080:8080"] in the Compose file.
Initialization, Configuration Validation, and Startup
Before running the container as a daemon, execute dry-run validation checks to confirm exchange API connectivity, credential parsing, and strategy syntax without executing live capital transactions:
cd /opt/freqtrade
# 1. Download initial historical candle data for whitelist verification
docker compose run --rm freqtrade download-data \
--config /freqtrade/user_data/config.json \
--timeframes 5m 1h \
--days 14 \
--pairs BTC/USDT ETH/USDT
# 2. Validate strategy logic and pairlist parsing against exchange endpoints
docker compose run --rm freqtrade test-pairlist \
--config /freqtrade/user_data/config.json
# 3. Launch the containerized daemon in background mode
docker compose up -d
# 4. Stream real-time engine initialization logs
docker compose logs -f --tail 100 freqtrade
Inspect the running container state and verify that kernel-level security profiles and mounts applied cleanly:
# Verify process user context (UID 1000) and capability restrictions
docker inspect freqtrade_engine --format '{{json .HostConfig.SecurityOpt}}'
# Expected output: ["no-new-privileges:true"]
docker inspect freqtrade_engine --format '{{json .HostConfig.CapDrop}}'
# Expected output: ["ALL"]
# Check SQLite database initialization and write permissions
ls -lh /opt/freqtrade/user_data/tradesv3.sqlite*
When successfully launched, user_data/tradesv3.sqlite is generated alongside its -wal and -shm sidecar files, owned strictly by 1000:1000. The engine is now completely decoupled from host administrative privileges, immune to arbitrary configuration modification, and backed by direct NVMe I/O throughput.
Exchange API Key Security, IP Whitelisting, and Firewall Rules
Operating an automated algorithmic trading engine exposes infrastructure directly to financial risk. When deploying a freqtrade crypto bot on vps environments, security cannot be treated as an external boundary; it must be implemented across API credential architecture, network perimeter filtering, and kernel socket behaviors. Compromised API credentials or exposed management ports represent catastrophic failure states that lead to immediate balance drainage through artificial order book slippage or cross-account transfer schemes.
1. Cryptographic Credential Scoping and Key Architecture
Centralized cryptocurrency exchanges (Binance, Bybit, OKX, Kraken) classify API key permissions into three functional tiers: 1. Read-Only / Market Data: Querying ticker order books, candlestick data, and historical fills. 2. Trade / Execution: Placing, canceling, and amending spot or derivatives orders. 3. Withdrawals / Internal Transfers: Moving assets on-chain or shifting balances between main accounts and sub-accounts.
Under no circumstances should an automated execution node possess withdrawal permissions. The Principle of Least Privilege (PoLP) dictates that Freqtrade requires only Read-Only and Spot/Margin Trade capabilities.
┌────────────────────────────────────────────────────────────────────────┐
│ EXCHANGE API PERMISSION PROFILE │
├──────────────────────────┬─────────────────────┬───────────────────────┤
│ Permission Scope │ Production State │ Threat Implication │
├──────────────────────────┼─────────────────────┼───────────────────────┤
│ Read-Only (Info/Balance) │ ENABLED │ Information leakage │
│ Spot / Futures Trading │ ENABLED │ Unfavorable execution │
│ Universal Transfer │ STRICTLY DISABLED │ Sub-account drain │
│ Enable Withdrawals │ STRICTLY DISABLED │ Direct asset theft │
│ IP Access Restriction │ RESTRICTED TO STATIC│ Replay/Session hijack │
└──────────────────────────┴─────────────────────┴───────────────────────┘
Asymmetric Key Cryptography (RSA & Ed25519)
Where supported (Binance, Kraken, Coinbase Advanced), prefer asymmetric key pairs (RSA-2048/4096 or Ed25519) over symmetric HMAC-SHA256 keys. Symmetric HMAC authentication requires storing the shared secret directly inside /freqtrade/user_data/config.json. If this configuration file is exfiltrated, an attacker can sign arbitrary requests from any network location until the key is revoked.
With Ed25519 signing: * The public key is registered with the exchange. * The private key resides strictly on the local filesystem with chmod 400 ownership. * Freqtrade signs outbound payloads locally; the shared secret is never transmitted over the wire or stored in memory alongside exchange response data.
To generate an Ed25519 key pair for compatible exchanges:
# Generate high-entropy private key
openssl genpkey -algorithm ed25519 -out /opt/freqtrade/user_data/exchange_ed25519.pem
# Extract the corresponding public key in PEM format for upload to exchange console
openssl pkey -in /opt/freqtrade/user_data/exchange_ed25519.pem -pubout -out /opt/freqtrade/user_data/exchange_ed25519.pub
# Restrict permissions strictly to container UID 1000
chown 1000:1000 /opt/freqtrade/user_data/exchange_ed25519.*
chmod 400 /opt/freqtrade/user_data/exchange_ed25519.pem
2. Static IPv4 Whitelisting and Outbound Routing Determinism
Major exchanges enforce an automatic 90-day expiration policy on API keys that do not have an IP whitelist attached. Unrestricted API keys are also subject to lower rate limits and heightened risk of automated revocation by exchange risk-engine heuristics.
Pinning API keys requires an immutable, dedicated public IPv4 address. Residential proxies, dynamic consumer connections, and NAT-shared cloud VPS configurations are unsuitable because changing egress IPs causes immediate HTTP 401/403 execution outages. High-performance KVM instances provisioned on platforms such as tropic.host assign a clean, dedicated static IPv4 address per node without outbound port filtering or carrier-grade NAT (CGNAT) pools. This ensures that outbound REST queries and WebSocket upgrade handshakes originate from a single, deterministic address matching the exchange access control list.
Outbound IPv4 Resolution Enforcement
Modern Linux systems resolve DNS through dual-stack (IPv4 and IPv6) stacks by default. If an exchange endpoint exposes both A (IPv4) and AAAA (IPv6) records, and your VPS network interface has a dynamically assigned IPv6 prefix or link-local address, the Linux resolver may attempt an outbound connection over IPv6. Because the exchange API key is whitelisted against your dedicated IPv4 address, an IPv6 connection attempt results in an immediate authorization rejection:
{"code": -2015, "msg": "Invalid API-key, IP, or permissions for action."}
Verify your host's authoritative public egress IPv4:
# Validate clean egress IPv4 identity
curl -4 -s https://ifconfig.me
# Or query via specific routing interface
ip route get 1.1.1.1 | awk '{print $7; exit}'
To eliminate dual-stack routing ambiguity and force the system libc resolver to prioritize IPv4 for outbound REST/WebSocket traffic, adjust /etc/gai.conf:
# Prioritize IPv4 addressing in getaddrinfo() calls
sed -i 's/#precedence ::ffff:0:0\/96 100/precedence ::ffff:0:0\/96 100/' /etc/gai.conf
# If absent, append explicitly
grep -q "precedence ::ffff:0:0/96 100" /etc/gai.conf || echo "precedence ::ffff:0:0/96 100" >> /etc/gai.conf
3. Neutralizing the Docker-UFW Ingress Bypass Vulnerability
A critical security vulnerability in Docker-based deployments on Linux involves the direct manipulation of the Netfilter packet filtering engine. By default, the Docker daemon modifies host iptables rules by injecting its own chains (DOCKER, DOCKER-ISOLATION-STAGE-1, DOCKER-USER) into the nat and filter tables.
When Docker publishes a container port using standard syntax:
ports:
- "8080:8080"
It binds the port to 0.0.0.0:8080 and creates an explicit DNAT rule in the PREROUTING chain. This completely bypasses standard Uncomplicated Firewall (UFW) rules. Even if UFW is active with a default DENY policy, port 8080 (the FreqUI dashboard and administrative REST API) becomes immediately accessible to the entire public internet.
Remediation Architecture
To secure the node against remote exploitation, enforce two layers of defensive routing:
- Loopback-Only Binding: Direct Docker Compose to bind published daemon ports exclusively to the loopback interface (
127.0.0.1). - DOCKER-USER Chain Hardening: Intercept packets routed toward container networks before Docker’s internal forwarders execute.
Update the docker-compose.yml service definition:
services:
freqtrade:
image: freqtradeorg/freqtrade:stable
restart: unless-stopped
container_name: freqtrade_engine
volumes:
- ./user_data:/freqtrade/user_data
# Bind exclusively to 127.0.0.1 to deny public interface listening
ports:
- "127.0.0.1:8080:8080"
command: >
trade
--config /freqtrade/user_data/config.json
--strategy SampleStrategy
Verify that the socket is bound strictly to the loopback address on the host:
ss -tulpn | grep 8080
# Correct: tcp LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("docker-proxy",pid=...
# INSECURE: tcp LISTEN 0 4096 0.0.0.0:8080 0.0.0.0:*
4. Production Host Perimeter Hardening (UFW & Iptables DOCKER-USER)
Configure the host firewall to enforce strict ingress and egress boundaries. Ingress must allow only encrypted administrative management (SSH), while egress should be constrained to cryptographic exchange ports (HTTPS/WSS 443) and DNS resolution (UDP/TCP 53), mitigating risk from third-party Python package supply-chain attacks.
Implementing UFW and DOCKER-USER Rules
Docker reserves the DOCKER-USER chain specifically for administrator-defined rules. Filter logic placed inside this chain executes before Docker’s default routing routines evaluate.
Edit /etc/ufw/after.rules to insert the low-level Netfilter bridging instructions at the conclusion of the file:
# BEGIN DOCKER-USER PROTECTION RULES
*filter
:DOCKER-USER - [0:0]
# Allow established and related connections to existing container sockets
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# Allow internal loopback traffic to reach containers
-A DOCKER-USER -i lo -j ACCEPT
# Allow container-to-container internal communication over docker0
-A DOCKER-USER -i docker0 -j ACCEPT
# Drop all untracked public ingress attempts hitting published container ports
-A DOCKER-USER -j RETURN
COMMIT
# END DOCKER-USER PROTECTION RULES
Apply foundational UFW configurations directly from the administrative terminal:
# Reset UFW to known baseline state
ufw --force reset
# Set default security policies: Drop all unsolicited ingress, allow managed egress
ufw default deny incoming
ufw default allow outgoing
# Secure administrative SSH access (apply rate-limiting to mitigate credential brute-forcing)
# If using a customized SSH port (e.g., 2222), modify accordingly
ufw limit 22/tcp comment 'SSH brute-force rate limiting'
# Activate firewall subsystem
ufw --force enable
# Reload rules to parse the /etc/ufw/after.rules DOCKER-USER definitions
systemctl restart ufw
systemctl restart docker
5. Kernel-Level Network & Connection Tracking Tuning (sysctl)
High-frequency WebSocket streams (ticker streams, order books, and active order updates) maintain long-lived, high-volume TCP sessions. If the host network stack is misconfigured, dropped packets, connection timeouts, or TCP window collapses will introduce severe latency spikes into order execution pipelines.
When hosting trading workloads on high-throughput KVM nodes with unmetered BGP uplinks like those provided by tropic.host, optimizing the Linux kernel parameters ensures that p99 socket latency remains minimal and Netfilter connection tables do not saturate during market volatility events.
Create a dedicated sysctl configuration file at /etc/sysctl.d/99-crypto-node-networking.conf:
# /etc/sysctl.d/99-crypto-node-networking.conf
# 1. Congestion Control & Queueing Discipline
# Enforce TCP BBR for fast recovery across international transit routes
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
# 2. Connection Tracking (Netfilter conntrack table sizing)
# Prevent packet drops caused by 'nf_conntrack: table full, dropping packet'
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 600
net.netfilter.nf_conntrack_tcp_timeout_close_wait = 10
net.netfilter.nf_conntrack_tcp_timeout_fin_wait = 20
# 3. Socket Buffer Sizes and Autotuning (Optimal memory bounds for 1-10 Gbps uplinks)
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
# 4. TCP Keepalive Timing for Long-Lived Exchange WebSockets
# Detect silent dead peer connections across intermediary NAT firewalls within 60s
net.ipv4.tcp_keepalive_time = 60
net.ipv4.tcp_keepalive_intvl = 10
net.ipv4.tcp_keepalive_probes = 3
# 5. Socket Lifecycle and Connection Recycling
net.ipv4.tcp_fin_timeout = 15
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 8192
net.core.somaxconn = 8192
# 6. Mitigate TCP SYN Flood Attacks and IP Spoofing
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_source_route = 0
Load and apply the parameters immediately into the running kernel:
sysctl --system
Verify active congestion control and conntrack table thresholds:
# Verify active TCP congestion control algorithm is BBR
sysctl net.ipv4.tcp_congestion_control
# Expected output: net.ipv4.tcp_congestion_control = bbr
# Verify active queueing discipline is fair queueing (fq)
sysctl net.core.default_qdisc
# Expected output: net.core.default_qdisc = fq
# Verify current conntrack occupancy against new ceiling
cat /proc/sys/net/netfilter/nf_conntrack_count
cat /proc/sys/net/netfilter/nf_conntrack_max
This configuration protects the host against external port discovery, isolates containerized API operations, and aligns kernel-level TCP stack timings with the millisecond-precision requirements of live exchange order book streaming.
Configuring FreqUI Web Dashboard and Telegram Remote Control Bot
Operating a production freqtrade crypto bot on vps infrastructure requires reliable, low-latency telemetry and operational controls without expanding the attack surface of the host. Exposing Freqtrade’s internal REST API directly to the public internet introduces severe attack vectors: brute-force authentication attacks against the internal Uvicorn ASGI server, unencrypted credential transit, and potential state manipulation.
A resilient architecture decouples process runtime from public access. The internal API server binds strictly to the host loopback interface (127.0.0.1) or an isolated internal Docker bridge network. Public ingress is delegated to a hardened Nginx reverse proxy terminating TLS 1.3 with strict rate limiting, while real-time mobile notifications and out-of-band trade execution are routed through an authenticated Telegram Remote Procedure Call (RPC) bot.
┌────────────────────────────────────────────────────────┐
│ tropic.host KVM │
│ │
[ Client Browser ] ──HTTPS──> │ [ Nginx Reverse Proxy ] ──HTTP/WS──> [ FreqUI / API ] │
(FreqUI UI) (443) │ (TLS 1.3, Rate-Limiting) (127.0.0.1:8080) │ │
└────────────────────────────────────────────────────────┘
▲
[ Telegram App ] <──Long Polling HTTPS (Outbound Port 443)─────────────┘
(2FA Auth ID)
1. Freqtrade API Engine and FreqUI Configuration
Freqtrade contains a built-in FastAPI/Uvicorn HTTP service that delivers both the REST API and the static assets for the FreqUI dashboard. Modify the primary user_data/config.json file to enable the engine, enforce strong authentication, and confine socket listening to loopback.
Generate a cryptographically secure 256-bit hexadecimal string for the JSON Web Token (JWT) secret key before editing the configuration:
# Generate a high-entropy secret for API token signing
python3 -c "import secrets; print(secrets.token_hex(32))"
Insert the generated secret and define the api_server block within user_data/config.json:
{
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "info",
"enable_openapi": false,
"jwt_secret_key": "4f9d8a1c2e7b6a5d4e3f2a1b0c9d8e7f6a5b4c3d2e1f0a9b8c7d6e5f4a3b2c1d",
"CORS_origins": [
"https://bot.yourdomain.com"
],
"username": "trader_ops",
"password": "SuperSecretProductionPassword_2026!#"
}
}
Key security directives in this schema: * listen_ip_address: "127.0.0.1": Prevents binding to 0.0.0.0. The API will reject all packets not originating from the local networking stack or internal container gateway. * enable_openapi: false: Disables public Swagger/OpenAPI documentation endpoints (/docs, /redoc), mitigating schema reconnaissance. * CORS_origins: Restricts Cross-Origin Resource Sharing exclusively to your fully qualified domain name (FQDN), neutralizing Cross-Site Scripting (XSS) and cross-origin injection vectors.
2. Hardened Nginx TLS 1.3 Reverse Proxy
The reverse proxy terminates HTTPS, enforces HTTP/2 or HTTP/3 transport, handles WebSocket protocol upgrades for streaming candlestick updates and log queues, and applies token-bucket rate limiting against brute-force attacks.
2.1. Certificate Issuance via Certbot
Install Certbot with the Nginx plugin and issue a production TLS certificate via Let's Encrypt using the clean dedicated static IPv4 address assigned to your tropic.host VPS:
# Install Certbot and Nginx on Debian/Ubuntu
apt-get update && apt-get install -y certbot python3-certbot-nginx nginx
# Issue automated certificate via Let's Encrypt
certbot certonly --nginx --agree-tos --no-eff-email --email [email protected] -d bot.yourdomain.com
2.2. Production Nginx Virtual Host
Create the site configuration at /etc/nginx/sites-available/freqtrade.conf. This configuration incorporates WebSocket connection upgrading (Connection: "upgrade"), strict transport security headers, and an isolated rate-limiting zone:
# Rate limiting: 10 requests/sec per IP with a burst cushion of 20
limit_req_zone $binary_remote_addr zone=freqtrade_limit:10m rate=10r/s;
# Connection mapping for WebSocket support
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
listen [::]:80;
server_name bot.yourdomain.com;
# Enforce immediate HTTPS redirection
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name bot.yourdomain.com;
# TLS Certificates
ssl_certificate /etc/letsencrypt/live/bot.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/bot.yourdomain.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/bot.yourdomain.com/chain.pem;
# Protocol & Cipher Hardening (Modern Profile)
ssl_protocols TLSv1.3 TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Security Headers
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-
## Configuring FreqUI Web Dashboard and Telegram Remote Control Bot
Operating a high-frequency trading bot requires low-latency observability and out-of-band operational controls without exposing the execution engine to remote exploitation. Exposing the underlying FastAPI/Uvicorn server of a `freqtrade crypto bot on vps` directly to the public internet presents critical vulnerabilities: unauthenticated memory inspection, resource exhaustion via unmetered JSON payloads, and credential interception over unencrypted HTTP channels.
A production-grade topology implements strict network perimeter boundaries. The application's internal API engine binds exclusively to the loopback interface (`127.0.0.1`). Ingress traffic from web clients is intercepted, sanitized, and terminated at an edge Nginx reverse proxy running TLS 1.3 with token-bucket request throttling. Concurrently, routine operational control and mission-critical trade alerts are offloaded to an asynchronous Telegram RPC daemon operating purely over outbound HTTPS long-polling connections.
┌────────────────────────────────────────────────────────┐
│ tropic.host KVM Host │
│ │
[ Client Browser ] ──HTTPS──> │ [ Nginx Edge Ingress ] ──HTTP/WS──> [ Freqtrade API ] │ (FreqUI UI) (443) │ (TLS 1.3 / Rate-Limit) (Loopback) (127.0.0.1:8080) │ └────────────────────────────────────────────────────────┘ ▲ [ Telegram App ] <──Outbound HTTPS Poll (TCP 443 to Telegram API)─────┘ (Authorized ID)
---
### 1. Freqtrade API Server Hardening
Freqtrade exposes its management surface through a built-in ASGI server delivering REST endpoints and static FreqUI single-page application (SPA) bundles. To prevent binding to all physical interfaces (`0.0.0.0`), configure the `api_server` block within `/etc/freqtrade/config.json`.
Before writing the configuration, generate an isolated 64-character cryptographic entropy token to sign JSON Web Tokens (JWT) for session management:
```bash
# Generate high-entropy hex string for JWT payload signing
python3 -c "import secrets; print(secrets.token_hex(32))"
Incorporate the generated secret into the configuration file:
{
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "info",
"enable_openapi": false,
"jwt_secret_key": "7b2e9d3a1f4c8b6e5a0d2f8a4c6e9b1d3f5a7c9e1b3d5f7a9c1e3b5d7f9a1c3e",
"CORS_origins": [
"https://trader.yourdomain.com"
],
"username": "ops_engineer",
"password": "Argon2_Compliant_Complex_Passphrase_2026!"
}
}
Key configuration parameters: * listen_ip_address: Set explicitly to 127.0.0.1. The application runtime will reject SYN packets arriving from physical Ethernet interfaces or external bridge adapters. * enable_openapi: Set to false. This disables /docs, /redoc, and raw OpenAPI JSON schemas, denying automated scanners architectural reconnaissance against the underlying API models. * CORS_origins: Declares the exact, fully qualified origin URL of the management domain. Any cross-origin XHR or Fetch request initiated outside this domain is rejected at the HTTP header evaluation layer.
Verify that the application binds strictly to the loopback interface using ss:
# Verify process socket binding state
ss -tulpn | grep 8080
# Output confirmation:
# tcp LISTEN 0 128 127.0.0.1:8080 0.0.0.0:* users:(("freqtrade",pid=4102,fd=7))
2. Edge Ingress: Nginx Reverse Proxy with TLS 1.3 and WebSockets
FreqUI requires bidirectional WebSocket communication on /api/v1/message/ws to stream order-book updates, state transitions, and real-time execution logs. Nginx must be configured to negotiate protocol upgrades dynamically while enforcing TLS 1.3 ciphers and request-rate limits.
2.1. Ingress Security and Upstream Definition
Create the site definition file at /etc/nginx/conf.d/freqtrade.conf:
# Allocate 10MB memory zone tracking IP states (approx. 160,000 unique IPs)
limit_req_zone $binary_remote_addr zone=api_throttle:10m rate=15r/s;
limit_conn_zone $binary_remote_addr zone=conn_throttle:10m;
# Evaluate WebSocket upgrade headers
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# Redirect all unencrypted HTTP requests to HTTPS
server {
listen 80;
listen [::]:80;
server_name trader.yourdomain.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# Main HTTPS Ingress Server
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on;
server_name trader.yourdomain.com;
# TLS Certificates (Managed via Certbot)
ssl_certificate /etc/letsencrypt/live/trader.yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/trader.yourdomain.com/privkey.pem;
# Cryptographic Configuration (Strict TLSv1.3 & High-Security TLSv1.2)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# Session Optimization
ssl_session_timeout 1d;
ssl_session_cache shared:SSL_CACHE:20m;
ssl_session_tickets off;
# Hardened Security Response Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "0" always;
add_header Content-Security-Policy "default-src 'self'; connect-src 'self' wss://trader.yourdomain.com; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;" always;
# Apply Connection Throttling
limit_conn conn_throttle 10;
# Reverse Proxy Passage for REST Endpoints and FreqUI Assets
location / {
# Apply token bucket limit: allow burst of 25 requests with zero delay
limit_req zone=api_throttle burst=25 nodelay;
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
# WebSocket Passthrough Support
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Downstream Header Preservation
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout configuration for long-lived WebSocket sessions
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
# Disable buffering to minimize transmission latency of streaming frames
proxy_buffering off;
}
}
Verify the syntax and reload Nginx:
nginx -t && systemctl reload nginx
Test the WebSocket upgrade handshake against the reverse proxy using curl:
# Execute synthetic WebSocket handshake check
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Host: trader.yourdomain.com" \
-H "Origin: https://trader.yourdomain.com" \
https://trader.yourdomain.com/api/v1/message/ws
An HTTP response status of 101 Switching Protocols validates that the reverse proxy cleanly upgrades TCP streams into bidirectional full-duplex WebSocket channels without buffering overhead.
3. Telegram Remote Control Bot Architecture
While FreqUI provides visual charting and execution logs, Telegram serves as an out-of-band command and telemetry channel. Freqtrade’s Telegram integration operates via outbound polling (HTTPS long-polling against api.telegram.org:443). Because communication originates inside the VPS and reaches outward, no inbound firewall ports need to be opened.
3.1. Telemetry and RPC Configuration
To prevent unauthorized parties from interacting with your execution loop, define the telegram stanza inside /etc/freqtrade/config.json. Configure strict identity isolation by obtaining your numeric Telegram User ID via @userinfobot and provisioning a dedicated bot via @BotFather.
{
"telegram": {
"enabled": true,
"token": "7182938475:AAF1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q",
"chat_id": "987654321",
"reload": true,
"balance_dust_level": 0.001,
"notification_settings": {
"status": "silent",
"warning": "on",
"startup": "on",
"buy": "on",
"buy_cancel": "on",
"buy_fill": "on",
"sell": "on",
"sell_cancel": "on",
"sell_fill": "on",
"protection_trigger": "on",
"protection_trigger_global": "on"
},
"allow_custom_messages": false
}
}
Crucial security mechanics: * chat_id: Must be set to your unique, private numeric identifier. If an unauthorized entity sends messages to your bot token, the internal RPC dispatcher discards the payload before processing any trade actions. * notification_settings: Separate informational events from critical risk triggers. Market fill events and risk limits (protection_trigger) invoke audible notifications, while routine telemetry updates run under silent dispatch to prevent notification fatigue. * allow_custom_messages: false: Blocks arbitrary dynamic message injection from downstream analytical plugins.
3.2. Primary Operational Commands
Once authenticated, manage the running execution state via Telegram using the following command subset:
| Telegram Command | Operational Function |
|---|---|
/status |
Returns active open trades, current mark prices, profit margins, and stop-loss levels. |
/profit |
Computes cumulative realized profit and performance metrics across configured intervals. |
/balance |
Displays real-time exchange wallet allocations, reserved trade margins, and free collateral. |
/stop |
Ceases new entry order generation while continuing to manage trailing exits on open positions. |
/reload_config |
Hot-reloads updated strategy parameters, pairlists, and blacklists from disk without dropping active positions. |
/forcesell <trade_id> |
Dispatches an immediate emergency market sell order directly to the exchange order book. |
4. Firewall Ingress Lockdown and Kernel Namespace Isolation
With Nginx handling public ingress and Telegram operating over outbound long-polling, isolate the host perimeter using nftables. This ensures that only ports 80 and 443 are reachable externally, while inter-process communication remains pinned to the local network namespace.
# Verify current host firewall rules via nftables
nft list ruleset
Ensure your /etc/nftables.conf contains strict input drop policies for administrative interfaces:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established and related connections
ct state established,related accept
# Drop invalid packets immediately
ct state invalid drop
# Loopback interface accepts all traffic
iif "lo" accept
# Rate-limited SSH access
tcp dport 22 ct state new limit rate 5/minute accept
# Public Web Traffic to Nginx Ingress
tcp dport { 80, 443 } accept
# ICMP echo-request rate limiting
ip protocol icmp icmp type echo-request limit rate 2/second accept
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
Apply the ruleset atomically:
nft -f /etc/nftables.conf
5. Infrastructure Validation on tropic.host Compute
The deterministic delivery of WebSocket frames to FreqUI and instantaneous execution of /forcesell commands via Telegram rely entirely on predictable CPU scheduling and reliable TCP transport. When deploying an algorithm under aggressive market conditions, hypervisor contention can degrade API polling loops from sub-millisecond dispatch to multi-second delays.
Running your trading infrastructure on tropic.host guarantees high-performance execution through dedicated KVM virtualization. The platform provides hardware metrics designed for real-time applications: * Zero Oversubscription: CPU Steal Time is strictly zero (%st = 0.0%) on high-frequency AMD EPYC and Ryzen 9 cores, ensuring that execution cycles are never preempted by neighbor processes during volatility spikes. * Deterministic Disk IOPS: Enterprise NVMe drives operating over PCIe 4.0 deliver random 4K QD1 access times exceeding 50,000 IOPS, preventing SQLite and SQLite WAL lockups when writing high-frequency tick data. * Unthrottled Network Ingress: Direct BGP peering at major transit hubs (Frankfurt, Amsterdam, London) with native TCP BBR congestion control ensures minimal round-trip latency to international exchanges and Telegram API endpoints.
Verify CPU scheduling latency directly from your terminal session to confirm clean hypervisor allocation:
# Monitor kernel CPU steal time under continuous load
vmstat 1 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 3824104 142100 892304 0 0 0 8 312 420 2 1 97 0 0
0 0 0 3824088 142100 892304 0 0 0 0 289 398 1 1 98 0 0
0 0 0 3824088 142100 892304 0 0 0 0 295 405 1 1 98 0 0
The st column remaining at 0 validates zero thread stealing, guaranteeing that API responses, WebSocket state updates, and Telegram RPC calls execute without hypervisor-induced latency penalties.
Automated Backups of SQLite Trade History and Disaster Recovery Procedures
Running a production-grade Freqtrade crypto bot on a VPS introduces a critical statefulness challenge: the bot’s active operational state, trade histories, open position trackers, and profit metrics reside inside a local SQLite database—specifically user_data/tradesv3.sqlite. Unlike stateless web applications that can be redeployed trivially from a container registry, losing or corrupting this database causes severe state desynchronization. The running instance loses track of exchange-side stop-losses, open limit orders, and trailing profit targets, leading to untracked market exposure or orphaned exchange positions.
Reliable data persistence requires a two-fold engineering strategy: an automated, atomic, hot-backup pipeline that respects SQLite’s concurrency model, followed by a deterministic, cold/warm Disaster Recovery (DR) failover protocol executed on a secondary VPS instance.
The Concurrency Problem: Why Naive Copies Corrupt SQLite
By default, modern Freqtrade deployments run SQLite with Write-Ahead Logging (PRAGMA journal_mode=WAL;). Under WAL mode, write transactions append sequential log records to the companion tradesv3.sqlite-wal file rather than modifying the main database directly, while index lookups utilize the shared memory ring buffer (tradesv3.sqlite-shm).
Executing standard POSIX file operations—such as cp, rsync, or basic archive commands (tar)—against an active database produces torn pages. If Freqtrade writes a transaction during an ongoing filesystem read, the copied file will contain a mix of pre- and post-transaction data blocks. Subsequent restore operations will fail with SQLITE_CORRUPT (11) errors or broken B-Tree structures.
To produce mathematically consistent, atomic snapshots without stopping the container or terminating trading threads, you must leverage SQLite’s native Online Backup API or perform an atomic safe vacuum via the CLI tool:
# Validating WAL mode and journal persistence
sqlite3 /opt/freqtrade/user_data/tradesv3.sqlite "PRAGMA journal_mode;"
# Returns: wal
The safest extraction method executes sqlite3 /path/to/db ".backup '/path/to/target.sqlite'". The .backup command acquires a brief read lock on the database, copies pages incrementally while absorbing concurrent write frames from the WAL file, and outputs a single, self-contained SQLite file where all uncommitted WAL pages are safely integrated.
Production Hot-Backup Pipeline
The automated backup daemon must perform five distinct tasks: 1. Generate an atomic, integrated SQLite snapshot. 2. Execute a strict database integrity check on the replica. 3. Encrypt the snapshot at rest using AES-256 via GPG. 4. Ship the encrypted archive offsite using rclone to an S3-compatible bucket or dedicated storage instance. 5. Prune local and remote retention layers to conserve NVMe storage.
Deploy the following production-hardened backup script to /usr/local/bin/freqtrade-backup.sh:
#!/usr/bin/env bash
set -euo pipefail
# ==============================================================================
# FREQTRADE SQLITE ATOMIC BACKUP & DISASTER RECOVERY EXPORT
# ==============================================================================
PROJECT_DIR="/opt/freqtrade"
DATA_DIR="${PROJECT_DIR}/user_data"
DB_FILE="${DATA_DIR}/tradesv3.sqlite"
CONFIG_FILE="${DATA_DIR}/config.json"
BACKUP_TMP_DIR="/var/tmp/freqtrade_backup"
TIMESTAMP="$(date -u +'%Y%m%d_%H%M%SZ')"
SNAPSHOT_DB="${BACKUP_TMP_DIR}/tradesv3_${TIMESTAMP}.sqlite"
ARCHIVE_TAR="${BACKUP_TMP_DIR}/freqtrade_state_${TIMESTAMP}.tar.gz"
ENCRYPTED_PAYLOAD="${ARCHIVE_TAR}.gpg"
# Encryption passphrase file (secured with chmod 400 root:root)
GPG_PASS_FILE="/etc/freqtrade/backup_passphrase"
RCLONE_REMOTE="s3-coldstorage:freqtrade-backups/node-01"
TELEGRAM_BOT_TOKEN="YOUR_TELEGRAM_BOT_TOKEN"
TELEGRAM_CHAT_ID="YOUR_TELEGRAM_CHAT_ID"
log() {
echo "[$(date -u +'%Y-%m-%dT%H:%M:%SZ')] $1"
}
notify_failure() {
local msg="CRITICAL: Freqtrade backup failed on host $(hostname -f) at step: $1"
log "$msg"
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="${msg}" > /dev/null || true
exit 1
}
# Trap unexpected exits
trap 'notify_failure "Unexpected execution termination"' ERR
mkdir -p "${BACKUP_TMP_DIR}"
chmod 700 "${BACKUP_TMP_DIR}"
log "Stage 1: Creating atomic SQLite online backup..."
sqlite3 "${DB_FILE}" ".backup '${SNAPSHOT_DB}'"
log "Stage 2: Validating SQLite page integrity..."
INTEGRITY_CHECK=$(sqlite3 "${SNAPSHOT_DB}" "PRAGMA integrity_check;")
if [[ "${INTEGRITY_CHECK}" != "ok" ]]; then
log "Integrity validation failed! Output: ${INTEGRITY_CHECK}"
notify_failure "SQLite integrity check returned corrupt pages"
fi
FOREIGN_KEY_CHECK=$(sqlite3 "${SNAPSHOT_DB}" "PRAGMA foreign_key_check;")
if [[ -n "${FOREIGN_KEY_CHECK}" ]]; then
log "Foreign key constraints broken! Output: ${FOREIGN_KEY_CHECK}"
notify_failure "SQLite foreign key check failed"
fi
log "Stage 3: Bundling state snapshot with configuration manifests..."
tar -czf "${ARCHIVE_TAR}" \
-C "${BACKUP_TMP_DIR}" "tradesv3_${TIMESTAMP}.sqlite" \
-C "${DATA_DIR}" "config.json"
log "Stage 4: Encrypting archive with GPG (AES-256)..."
gpg --batch --yes --passphrase-file "${GPG_PASS_FILE}" \
--symmetric --cipher-algo AES256 \
--output "${ENCRYPTED_PAYLOAD}" "${ARCHIVE_TAR}"
log "Stage 5: Offloading encrypted snapshot to remote target..."
rclone copyto "${ENCRYPTED_PAYLOAD}" "${RCLONE_REMOTE}/freqtrade_state_${TIMESTAMP}.tar.gz.gpg" \
--fast-list \
--retries 3 \
--low-level-retries 10
log "Stage 6: Pruning remote artifacts older than 14 days..."
rclone delete "${RCLONE_REMOTE}" --min-age 14d
log "Stage 7: Local workspace cleanup..."
rm -rf "${BACKUP_TMP_DIR}"
log "Backup completed successfully: freqtrade_state_${TIMESTAMP}.tar.gz.gpg"
Lock down permissions on the host system to protect API secrets and database dumps:
sudo chmod 700 /usr/local/bin/freqtrade-backup.sh
sudo chown root:root /usr/local/bin/freqtrade-backup.sh
# Generate a high-entropy passphrase file
sudo mkdir -p /etc/freqtrade
sudo openssl rand -base64 32 | sudo tee /etc/freqtrade/backup_passphrase > /dev/null
sudo chmod 400 /etc/freqtrade/backup_passphrase
sudo chown root:root /etc/freqtrade/backup_passphrase
Systemd Scheduling and Sandboxing
Never rely on classic cron jobs for critical financial infrastructure. A native systemd timer provides strict journal integration (journalctl), execution timeout enforcement, and Linux cgroups sandboxing to isolate backup operations from trading compute cycles.
Create the service unit file /etc/systemd/system/freqtrade-backup.service:
[Unit]
Description=Freqtrade SQLite Atomic Backup Daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
User=root
Group=root
ExecStart=/usr/local/bin/freqtrade-backup.sh
TimeoutStartSec=300
# Security Hardening & Isolation
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/var/tmp/freqtrade_backup /opt/freqtrade/user_data
PrivateTmp=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictNamespaces=true
MemoryHigh=512M
MemoryMax=768M
CPUQuota=50%
Create the companion timer file /etc/systemd/system/freqtrade-backup.timer:
[Unit]
Description=Periodic Trigger for Freqtrade Backup Daemon
[Timer]
OnCalendar=*:0/30
RandomizedDelaySec=60
Persistent=true
[Install]
WantedBy=timers.target
Enable and activate the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now freqtrade-backup.timer
# Confirm timer scheduling
systemctl list-timers freqtrade-backup.timer
This schedule creates a clean restore point every 30 minutes, keeping the potential recovery point objective (RPO) within an acceptable delta while limiting NVMe storage wear.
Mitigating Split-Brain Concurrency Risks
The primary failure mode during cryptocurrency bot disaster recovery is the split-brain scenario: bringing up a secondary standby VPS while the primary VPS is still active or recovering from a transient network partition.
If two Freqtrade instances execute simultaneously using identical exchange API credentials: * Both instances read identical order book conditions and double-order position sizes. * Rate limits on the exchange API trigger HTTP 429 Too Many Requests, causing orders to drop. * Order cancellations and position exits collide, generating inconsistent trade records and margin balance liquidations.
To eliminate split-brain events: 1. API Key Fencing: Never store production read-write exchange API keys on the standby node. Maintain dedicated keys for the secondary instance or apply strict IP whitelisting. On high-performance KVM platforms like tropic.host, every VPS receives a dedicated, static public IPv4 address that does not change across reboots or hypervisor re-allocations. Bind your exchange API keys directly to the primary node’s IPv4 via the exchange interface. In a disaster recovery event, updating the whitelisted IP at the exchange takes under 60 seconds and instantly neutralizes the compromised primary host. 2. Deterministic Fencing via API Revocation: As the initial step of any failover runbook, terminate the primary API key pair directly via the exchange dashboard or CLI trading terminal before launching the secondary instance.
Disaster Recovery Runbook: Failover to Standby VPS
When a catastrophic failure occurs on the primary server (hardware fault, persistent upstream BGP blackhole, or fatal hypervisor stall), execute the following restoration protocol on the standby VPS.
Deploying the standby instance on tropic.host ensures hardware parity: bare-metal KVM virtualization guarantees 0.0% CPU Steal Time (%st = 0.0%), while enterprise PCIe 4.0 NVMe drives sustain random 4K QD1 access times above 50,000 IOPS. This prevents SQLite WAL lockups when writing high-frequency tick data during catch-up cycles.
DISASTER RECOVERY ARCHITECTURE
PRIMARY KVM VPS (tropic.host) STANDBY KVM VPS (tropic.host)
IPv4: 194.x.x.10 (Whitelisted) IPv4: 185.x.x.25 (Standby)
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ Freqtrade Engine (Docker) │ │ Cold Standby Node │
│ tradesv3.sqlite (WAL Mode) │ │ (Docker + Pre-pulled Image) │
└──────────────┬───────────────┘ └──────────────▲───────────────┘
│ Atomic .backup │ Pull & Decrypt
▼ │ Snapshot
┌──────────────────────────────┐ ┌──────────────┴───────────────┐
│ Encrypted Offsite S3 Bucket ├──────────►│ Restored tradesv3.sqlite │
│ (AES-256 via GPG / Rclone) │ │ PRAGMA integrity_check: OK │
└──────────────────────────────┘ └──────────────────────────────┘
Step 1: Invalidate Primary Node Access
Log into the exchange portal and revoke the API key assigned to the primary host, or update the IP whitelist to the standby host's dedicated public IP:
# Verify external connectivity and dedicated IP identity from the standby node
curl -s https://ipinfo.io/ip
Step 2: Retrieve and Decrypt Latest State Archive
On the standby VPS, pull the newest backup payload from the remote repository:
RESTORE_DIR="/var/tmp/restore"
mkdir -p "${RESTORE_DIR}"
cd "${RESTORE_DIR}"
# Fetch the most recent snapshot file
LATEST_BACKUP=$(rclone lsf s3-coldstorage:freqtrade-backups/node-01 --files-only | sort | tail -n 1)
rclone copy "s3-coldstorage:freqtrade-backups/node-01/${LATEST_BACKUP}" ./
# Decrypt the AES-256 payload
gpg --batch --yes --passphrase-file /etc/freqtrade/backup_passphrase \
--decrypt "${LATEST_BACKUP}" > restored_state.tar.gz
# Extract contents
tar -xzvf restored_state.tar.gz
Step 3: Validate Restored Database Integrity
Never inject an unverified database into an active trading directory. Run direct PRAGMA checks:
RESTORED_DB=$(ls tradesv3_*.sqlite)
# Verify page allocation
sqlite3 "${RESTORED_DB}" "PRAGMA integrity_check;"
# Must return: ok
# Query internal trade ledger for sanity check
sqlite3 "${RESTORED_DB}" "SELECT count(*) FROM trades;"
sqlite3 "${RESTORED_DB}" "SELECT id, pair, is_open, open_date, open_rate FROM trades WHERE is_open = 1;"
Step 4: Seed the Container Workspace
Move the verified database and runtime configuration into the active Freqtrade volume:
TARGET_DATA_DIR="/opt/freqtrade/user_data"
mkdir -p "${TARGET_DATA_DIR}"
# Copy database to standard operational filename
cp -p "${RESTORE_DIR}/${RESTORED_DB}" "${TARGET_DATA_DIR}/tradesv3.sqlite"
cp -p "${RESTORE_DIR}/config.json" "${TARGET_DATA_DIR}/config.json"
# Ensure container UID/GID owns the filesystem assets (Freqtrade container runs as uid 1000)
chown -R 1000:1000 /opt/freqtrade/user_data
chmod 644 "${TARGET_DATA_DIR}/tradesv3.sqlite"
Step 5: Execute Position State Reconciliation
Start the containerized bot in a controlled diagnostic mode before allowing autonomous order execution. Review logs to confirm open trade synchronization against the exchange’s actual order books:
cd /opt/freqtrade
# Start container attached to stdout to observe exchange balance synchronization
docker compose up freqtrade
Examine the startup output for the internal order-matching reconciliation cycle:
2026-10-04 14:12:08,412 - freqtrade.persistence.models - INFO - Database version: 28
2026-10-04 14:12:08,414 - freqtrade.persistence.models - INFO - Validating trades from database...
2026-10-04 14:12:08,520 - freqtrade.freqtradebot - INFO - Found 3 open trades in database.
2026-10-04 14:12:08,890 - freqtrade.exchange.binance - INFO - Syncing open order: Trade id 412, pair BTC/USDT, order_id 98231841...
2026-10-04 14:12:09,114 - freqtrade.freqtradebot - INFO - All positions successfully reconciled with exchange balances.
If a discrepancy appears (such as a position closed by a stop-loss on the exchange during hypervisor downtime while the database shows it open), issue a manual resolution command via Freqtrade's RPC client or force-exit the desynchronized record:
# Force-reconcile trade status through Freqtrade CLI inside the container
docker compose run --rm freqtrade force-exit 412
Once log verification demonstrates consistent state, detach the container and verify operational metrics:
docker compose up -d freqtrade
docker compose logs -f --tail=100 freqtrade
This procedure guarantees a deterministic Recovery Time Objective (RTO) under five minutes and limits financial risk exposure by enforcing complete transactional continuity across infrastructure failures.
Frequently Asked Questions (FAQ)
How much RAM does Freqtrade require when running FreqAI models?
Standard algorithmic strategies require just 1–2 GB RAM. When running FreqAI machine learning models (LightGBM, XGBoost) with multi-pair backtesting, 4–8 GB RAM and 4 vCPU cores are recommended.
How does low-latency VPS hosting improve trading results?
Deploying a KVM VPS close to exchange servers (e.g. Frankfurt or Singapore) reduces order execution round-trip latency from 150ms to 2–8ms, minimizing slippage on volatile markets.