- Supports clusters of pools each running individual currencies
- Ultra-low-latency, multi-threaded Stratum implementation using asynchronous I/O
- Adaptive share difficulty ("vardiff")
- PoW validation (hashing) using native code for maximum performance
- Session management for purging DDoS/flood initiated zombie workers
- Payment processing
- Banning System
- Live Stats API
- WebSocket streaming of notable events like Blocks found, Blocks unlocked, Payments and more
- Detailed per-pool logging to console & filesystem
- Runs on Linux and Windows
Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.
git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.shgit clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.shgit clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.shAll build scripts install dotnet-sdk-10.0 and the required system libraries, then run:
dotnet publish -c Release --framework net10.0 -o ../../buildAn optional output path can be passed as the first argument:
./build-ubuntu-24_04.sh /opt/miningcoreStep 1 — build the native crypto library (you can skip this stage and use exist)
Install choco
Set-ExecutionPolicy Bypass -Scope Process-Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))Install vs build tools.
choco install visualstudio2026buildtools -ychoco install visualstudio2026-workload-vctools -ysrc\Native\libmultihash\x64\Release\libmultihash.dll
Create the target directory and copy the DLL there:
mkdir libs\runtimes\win-x64
copy src\Native\libmultihash\x64\Release\libmultihash.dll libs\runtimes\win-x64\Download and install the .NET 10 SDK
Step 2 — build the managed project
git clone https://github.com/bitweb-project/miningcore
cd miningcore
build-windows.batThe final binaries are placed in the build\ directory.
Docker builds always produce a Linux executable.
git clone https://github.com/bitweb-project/miningcorecd miningcoreLinux build:
docker run --rm -v $(pwd):/app -w /app mcr.microsoft.com/dotnet/sdk:10.0 \ /bin/bash -c 'apt-get update && apt-get install -y cmake ninja-build build-essential \ libssl-dev pkg-config libboost-all-dev libsodium-dev libzmq5-dev libgmp-dev \ && cd src/Miningcore && dotnet publish -c Release --framework net10.0 -o /app/build/'Note — Build scripts optimise the binary for the CPU of the build host (AVX, SSE, etc.). Always build the container on the host you intend to run it on.
docker build -t <your_dockerhubid>/miningcore:latest .docker run -d \
-p 4000:4000 \
-p 4066:4066 \
-p 4067:4067 \
--name mc \
-v $(pwd)/config.json:/app/config.json \
--restart=unless-stopped \
<your_dockerhubid>/miningcore:latestdocker system prune -afLinux and Windows (x64) are both supported for production use.
Miningcore requires PostgreSQL 11 or higher.
Run Postgres's psql tool:
sudo -u postgres psqlIn psql execute:
CREATE ROLE miningcore WITH LOGIN ENCRYPTED PASSWORD 'your-secure-password';
CREATEDATABASEminingcore OWNER miningcore;Quit psql with \q.
Import the database schema:
sudo -u postgres psql -d miningcore -f miningcore/src/Miningcore/Persistence/Postgres/Scripts/createdb.sqlThe shares table is partitioned by poolid. You must create one partition per pool before starting the pool (replace mypool1 with the actual pool id from your config):
CREATETABLEshares_mypool1 PARTITION OF shares FOR VALUESIN ('mypool1');Repeat for each additional pool.
Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.
cd build./Miningcore -c config.jsonOn Windows:
cd build
Miningcore.exe -c config.jsonTest
dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.
The primary coin this pool software is built for.
- Algorithm: Argon2id (t=3, m=1024) — custom optimised implementation with SSE2 / SSSE3 / AVX2 / AVX-512 dispatch
- Website: https://bitwebcore.net
- Explorer: https://explorer.bitwebcore.net
- GitHub: https://github.com/bitweb-project/bitweb
Sample config: examples/bitweb_pool.json
Bitcoin (BTC), Bitcoin Cash (BCH)...
Litecoin (LTC), Dogecoin (DOGE)...
Bitweb (BTE)...
Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api
A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui
create user pool
then login to root or use sudo -s
apt-get update && apt-get upgradewget https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb -O packages-microsoft-prod.debsudo dpkg -i packages-microsoft-prod.debsudo apt-get -y install dotnet-sdk-10.0 nano git cmake ninja-build build-essential libssl-dev pkg-config libzmq5-dev libgmp-dev nginx python3-certbot-dns-cloudflare gnupg curl postgresql python3 python3-venv python3-dev python3-pip python3-setuptools python3-multidict ufwSetup certicifates ( you need cloudflare account domain)
nano /etc/letsencrypt/cloudflare.inidns_cloudflare_api_token = tokenchmod 600 /etc/letsencrypt/cloudflare.inimkdir -p /etc/letsencrypt/renewal-hooks/deploynano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shput#!/bin/bashsystemctl reload nginxsavecat /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shchmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shcertbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.netnano /etc/nginx/ngserver-global.conf
# Cloudflare real IP restorationset_real_ip_from 103.21.244.0/22;set_real_ip_from 103.22.200.0/22;set_real_ip_from 104.16.0.0/13;set_real_ip_from 104.24.0.0/14;set_real_ip_from 108.162.192.0/18;set_real_ip_from 141.101.64.0/18;set_real_ip_from 162.158.0.0/15;set_real_ip_from 172.64.0.0/13;set_real_ip_from 173.245.48.0/20;set_real_ip_from 188.114.96.0/20;set_real_ip_from 190.93.240.0/20;set_real_ip_from 197.234.240.0/22;set_real_ip_from 198.41.128.0/17;set_real_ip_from 131.0.72.0/22;real_ip_header CF-Connecting-IP;real_ip_recursive on;
# Global IP blacklist (override in server blocks with another geo if needed)geo $blocked_ip { default 0; # 1.2.3.4 1;}
# Global User-Agent blacklistmap $http_user_agent $blocked_agent { default 0; ~*(sqlmap|nmap|nikto|burp|scanner) 1;}
# Global malicious query blacklistmap $args $blocked_args { default 0; ~*(union.*select|drop.*table|insert.*into|\.\./|etc/passwd) 1;}nano /opt/update-cf-ips.sh#!/bin/bashset -euo pipefailCF_IPV4="https://www.cloudflare.com/ips-v4"CF_IPV6="https://www.cloudflare.com/ips-v6"
# Actual path nginx includes (conf.d/*.conf)TARGET="/etc/nginx/conf.d/ngserver-global.conf"
# Temp file in the same directory — keeps cp atomic-ish (same filesystem)TEMP="$(mktemp "${TARGET}.XXXXXX")"BACKUP="$(mktemp "${TARGET}.bak.XXXXXX")"cleanup() { rm -f "$TEMP" "$BACKUP"}trap cleanup EXITfail() { echo "$(date -u '+%Y-%m-%d %H:%M UTC') ERROR: $*" >&2 exit 1}fetch_ips() { local url="$1" curl -fsSL --max-time 10 "$url"}
# --- Step 1: fetch IP lists, explicitly checking each curl ---ipv4_list=$(fetch_ips "$CF_IPV4") || fail "failed to fetch $CF_IPV4"ipv6_list=$(fetch_ips "$CF_IPV6") || fail "failed to fetch $CF_IPV6"
# Sanity check: lists must not be empty or suspiciously shortipv4_count=$(printf '%s\n' "$ipv4_list" | grep -c '/' || true)ipv6_count=$(printf '%s\n' "$ipv6_list" | grep -c '/' || true)if [ "$ipv4_count" -lt 5 ] || [ "$ipv6_count" -lt 1 ]; then fail "suspiciously few IP ranges (v4=$ipv4_count v6=$ipv6_count) — not applying, keeping current config"fi
# --- Step 2: generate the new config ---generate_config() { echo "# Cloudflare IP ranges — auto-updated $(date -u '+%Y-%m-%d %H:%M UTC')" echo "# Do not edit manually — managed by update-cf-ips.sh" echo "" printf '%s\n' "$ipv4_list" | while IFS= read -r ip; do [ -n "$ip" ] && echo "set_real_ip_from ${ip};" done printf '%s\n' "$ipv6_list" | while IFS= read -r ip; do [ -n "$ip" ] && echo "set_real_ip_from ${ip};" done echo "real_ip_header CF-Connecting-IP;" echo "real_ip_recursive on;" echo "" cat << 'STATIC'geo $blocked_ip { default 0; # 1.2.3.4 1;}map $http_user_agent $blocked_agent { default 0; ~*(sqlmap|nmap|nikto|burp|scanner) 1;}map $args $blocked_args { default 0; ~*(union.*select|drop.*table|insert.*into|\.\./|etc/passwd) 1;}STATIC}generate_config > "$TEMP"
# --- Step 3: compare, ignoring the timestamp line (otherwise it always "differs") ---if [ -f "$TARGET" ]; then if diff -q <(tail -n +2 "$TARGET") <(tail -n +2 "$TEMP") > /dev/null 2>&1; then # Content (without the date line) unchanged — nothing to do exit 0 fi cp "$TARGET" "$BACKUP"else : > "$BACKUP" # empty backup = "no previous file"fi
# --- Step 4: apply the new config and validate it ---cp "$TEMP" "$TARGET"if ! nginx -t 2>/tmp/nginx-test-err; then # Roll back so we never leave a broken config on disk if [ -s "$BACKUP" ]; then cp "$BACKUP" "$TARGET" else rm -f "$TARGET" fi fail "nginx -t failed on the new config, rolled back: $(cat /tmp/nginx-test-err)"finginx -s reloadecho "$(date -u '+%Y-%m-%d %H:%M UTC') Cloudflare IPs updated and nginx reloaded (v4=$ipv4_count v6=$ipv6_count)"nano /etc/logrotate.d/update-cf-ipsput/var/log/update-cf-ips.log { weekly rotate 4 compress missingok notifempty}savechown root:root /opt/update-cf-ips.shchmod 700 /opt/update-cf-ips.shcrontab -eadd17 3 * * * /opt/update-cf-ips.sh >> /var/log/update-cf-ips.log 2>&1nano /etc/logrotate.d/update-cf-ipsadd/var/log/update-cf-ips.log { weekly rotate 4 compress missingok notifempty su root root}savenano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}saveTest
logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcorenano /etc/nginx/sites-available/pool-api.example.netputlimit_req_zone $binary_remote_addr zone=api_pool:10m rate=60r/m;limit_req_zone $binary_remote_addr zone=api_miner:10m rate=120r/m;limit_req_zone $binary_remote_addr zone=api_ws:10m rate=20r/m;limit_req_zone $binary_remote_addr zone=api_misc:10m rate=20r/m;limit_conn_zone $binary_remote_addr zone=ws_conn:10m;server { listen 80; server_name pool-api.example.net; return 301 https://$host$request_uri;}server { listen 443 ssl; server_name pool-api.example.net; ssl_certificate /etc/letsencrypt/live/pool-api.example.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pool-api.example.net/privkey.pem; server_tokens off; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "no-referrer" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always; add_header Access-Control-Allow-Origin "*" always; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always; add_header Access-Control-Allow-Headers "Content-Type" always; if ($request_method = OPTIONS) { return 204; } limit_req_status 429; limit_req_log_level warn; location = /notifications { limit_req zone=api_ws burst=10 nodelay; limit_conn ws_conn 10; proxy_pass http://127.0.0.1:4000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; 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_read_timeout 3600s; } location = /api/health-check { limit_req zone=api_misc burst=10 nodelay; proxy_pass http://127.0.0.1:4000; proxy_set_header Host $host; proxy_read_timeout 5s; } location = /api/help { limit_req zone=api_misc burst=5 nodelay; proxy_pass http://127.0.0.1:4000; proxy_set_header Host $host; } location = /api/pools-list { limit_req zone=api_pool burst=20 nodelay; proxy_pass http://127.0.0.1:4000; 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; } location ~ "^/api/pools/[a-zA-Z0-9_-]+(/performance|/blocks)?$" { limit_req zone=api_pool burst=20 nodelay; proxy_pass http://127.0.0.1:4000; 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; } location ~ "^/api/v2/pools/[a-zA-Z0-9_-]+/blocks$" { limit_req zone=api_pool burst=20 nodelay; proxy_pass http://127.0.0.1:4000; 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; } location ~ "^/api/pools/[a-zA-Z0-9_-]+/miners/[a-zA-Z0-9]{25,90}(/blocks|/payments|/settings)?$" { limit_req zone=api_miner burst=40 nodelay; proxy_pass http://127.0.0.1:4000; 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; proxy_read_timeout 20s; } location ~ "^/api/v2/pools/[a-zA-Z0-9_-]+/miners/[a-zA-Z0-9]{25,90}/(blocks|payments)$" { limit_req zone=api_miner burst=40 nodelay; proxy_pass http://127.0.0.1:4000; 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; proxy_read_timeout 20s; } location / { return 444; }}# /etc/nginx/sites-available/default-dropserver { listen 80 default_server; listen 443 ssl default_server; ssl_certificate /etc/letsencrypt/live/pool-api.example.net/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pool-api.example.net/privkey.pem; server_name _; return 444;}ln -s /etc/nginx/sites-available/pool.bitwebcore.net /etc/nginx/sites-enabled/pool.bitwebcore.netln -s /etc/nginx/sites-available/default-drop /etc/nginx/sites-enabled/default-dropsystemctl restart nginxnano /etc/postgresql/16/main/conf.d/tuning.confput
# MEMORYshared_buffers = 1GBwork_mem = 6MBmaintenance_work_mem = 128MBwal_buffers = 32MB
# WALmin_wal_size = 512MBmax_wal_size = 2048MBwal_compression = on
# PARALLELmax_worker_processes = 4max_parallel_workers = 4
# SSDrandom_page_cost = 1.1effective_io_concurrency = 200maintenance_io_concurrency = 100
# AUTOVACUUMautovacuum_vacuum_scale_factor = 0.05autovacuum_analyze_scale_factor = 0.02autovacuum_vacuum_insert_scale_factor = 0.02
# LOGGINGlog_min_duration_statement = 500log_lock_waits = ondeadlock_timeout = 500mssavepg_ctlcluster 16 main restartnano /etc/systemd/system/enable-transparent-huge-pages.serviceput[Unit]Description=Set Transparent Hugepages (THP)DefaultDependencies=noAfter=sysinit.target local-fs.targetBefore=mongod.service postgresql.service[Service]Type=oneshotExecStart=/bin/sh -c 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled && echo defer+madvise > /sys/kernel/mm/transparent_hugepage/defrag && echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none'[Install]WantedBy=multi-user.targetsavesystemctl daemon-reloadsystemctl enable enable-transparent-huge-pagessystemctl start enable-transparent-huge-pagescheckcat /sys/kernel/mm/transparent_hugepage/enabledalways [madvise] nevercat /sys/kernel/mm/transparent_hugepage/defragalways defer [defer+madvise] madvise nevercat /sys/kernel/mm/transparent_hugepage/khugepaged/max_ptes_none0ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining portsudo nano /etc/systemd/system/miningcore.service[Unit]Description=Miningcore PoolAfter=network.target postgresql.service[Service]Type=simpleUser=poolWorkingDirectory=/home/pool/miningcore/buildExecStart=/home/pool/miningcore/build/Miningcore -c bitweb_pool.jsonRestart=noTimeoutStopSec=60[Install]WantedBy=multi-user.targetsudo systemctl daemon-reloadsudo systemctl enable miningcorenot start now!sudo -u postgres psql
CREATE ROLE miningcore WITH LOGIN ENCRYPTED PASSWORD 'strongpassword';
CREATEDATABASEminingcore OWNER miningcore;
\q
dont forgot save some where password you will need it!now return to pool user exit or if you use different users login to pool user at your server.
download node and sutup it WITH
bitweb.conf example
server=1rpcuser=userrpcpassword=passwordrpcallowip=127.0.0.1rpcport=26332wallet=pooltxindex=1coinstatsindex=1server=1listen=1daemon=1zmqpubhashblock=tcp://127.0.0.1:28332rpcworkqueue=512rpcthreads=32rpcclienttimeout=30maxconnections=150maxuploadtarget=2000addnode=43.138.48.57dbcache=2048maxmempool=300start node and go to
cd bitweb/bin./bitweb-cli createwallet "pool"./bitweb-cli getnewaddress "" bech32mit will return web1q...save it../bitweb-cli encryptwallet "strongwalletpassword"save also.git clone https://github.com/bitweb-project/miningcorecd miningcorepsql -h 127.0.0.1 -U miningcore -d miningcore < ~/miningcore/src/Miningcore/Persistence/Postgres/Scripts/createdb.sqlwrite password and enter.
psql -h 127.0.0.1 -U miningcore -d miningcore -c "CREATE TABLE shares_bte1 PARTITION OF shares FOR VALUES IN ('bte1');"psql -h 127.0.0.1 -U miningcore -d miningcore -c "CREATE TABLE shares_bte1solo PARTITION OF shares FOR VALUES IN ('bte1solo');"check 10 tables.
psql -h 127.0.0.1 -U miningcore -d miningcore -c "\dt"bash build-ubuntu-24_04.shmkdir -p /home/pool/miningcore/sslopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem \ -days 36500 -nodes -subj "/CN=pool"openssl pkcs12 -export \ -out /home/pool/miningcore/ssl/pool.pfx \ -inkey key.pem \ -in cert.pem \ -passout pass:rm key.pem cert.pemopenssl pkcs12 -in /home/pool/miningcore/ssl/pool.pfx -noout -passin pass: && echo "OK"cp example bitweb_pool.json /home/pool/build/poolconfig.jsoncd build nano poolconfig.jsonedit settings, put address / password etc.
save
login as root and start pool
sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcoreadmin script
python3 mcadmin.pyrestore shares
./Miningcore -c bitweb_pool.json -rs recovered-shares.txtcheck logs
tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.loggo to cloud flare and c reate separate domain for mining
for example our mining.bitwebcore.net and pin to server.
if pool sucess run, not you can go to step for setup UI.
https://github.com/bitweb-project/miningcore-ui
Your steps may differ depending on whether you're using this guide as a basic example. Security settings and other overlooked details are your responsibility. Adaptation to your environment is your responsibility.