Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

license.NET

Features

  • 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

Contributions

Code contributions are very welcome and should be submitted as standard pull requests (PR) based on the dev branch.

Building on Ubuntu 24.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-24_04.sh

Building on Ubuntu 22.04

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-ubuntu-22.04.sh

Building on Debian 12

git clone https://github.com/bitweb-project/miningcorecd miningcore./build-debian-12.sh

All build scripts install dotnet-sdk-10.0 and the required system libraries, then run:

dotnet publish -c Release --framework net10.0 -o ../../build

An optional output path can be passed as the first argument:

./build-ubuntu-24_04.sh /opt/miningcore

Building on Windows

Step 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 -y
src\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.bat

The final binaries are placed in the build\ directory.

Building using Docker

Docker builds always produce a Linux executable.

git clone https://github.com/bitweb-project/miningcorecd miningcore

Linux 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/'

Building and running from a container

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:latest
docker system prune -af

Running Miningcore

Production OS

Linux and Windows (x64) are both supported for production use.

Database setup

Miningcore requires PostgreSQL 11 or higher.

Run Postgres's psql tool:

sudo -u postgres psql

In 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.sql

Create a partition for each pool

The 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.

Configuration

Create a configuration file config.json. Sample configs for all supported coins can be found in the examples/ directory.

Start the Pool

cd build./Miningcore -c config.json

On Windows:

cd build
Miningcore.exe -c config.json

Test

dotnet test src/Miningcore.Tests/Miningcore.Tests.csproj -c Release --logger "console;verbosity=normal"

Supported Currencies

Only Bitcoin-family (Stratum) coins are supported. All other families (Monero/RandomX, Ethereum, ZCash Equihash, etc.) have been removed.

Bitweb (BTE) — Argon2id

The primary coin this pool software is built for.

Sample config: examples/bitweb_pool.json

SHA-256d

Bitcoin (BTC), Bitcoin Cash (BCH)...

Scrypt

Litecoin (LTC), Dogecoin (DOGE)...

Argon2ID

Bitweb (BTE)...

API

Miningcore includes an integrated REST API. See: https://pool.bitwebcore.net/api

Running a production pool

A public production pool needs a web frontend. you can use this https://github.com/bitweb-project/miningcore-ui

Extra fast Linux setup example what we use ( used ubuntu-24_04 )

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 ufw

Setup 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.sh
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini --dns-cloudflare-propagation-seconds 60 --force-renewal -d pool-api.example.net
nano /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}save
nano /etc/logrotate.d/miningcoreput/home/pool/miningcore/log/miningcore.log { daily rotate 7 compress missingok notifempty copytruncate su pool pool}save

Test

logrotate -vf /etc/logrotate.d/update-cf-ipslogrotate -vf /etc/logrotate.d/miningcore
nano /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 nginx
nano /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 restart
nano /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_none0
ufw statusufw allow sshufw allow httpufw allow httpsufw allow 26333ufw allow 3032ufw allow 3033ufw enableufw status26333 - node p2p port 3032 PPLNS mining port3033 SOLO mining port
sudo 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.target
sudo 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=300

start 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.sql

write 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.json

edit settings, put address / password etc.

save

login as root and start pool

sudo systemctl start miningcoresudo systemctl status miningcoresudo systemctl stop miningcore

admin script

python3 mcadmin.py

restore shares

./Miningcore -c bitweb_pool.json -rs recovered-shares.txt

check logs

tail -f /home/pool/miningcore/log/miningcore.logtail -f /home/pool/miningcore/log/bte1.logtail -f /home/pool/miningcore/log/bte1solo.log

go 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.

About

Miningcore is a high-performance Mining Pool Software for Linux and Windows.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages