Run a Stake Pool

From an empty server to a registered pool and the work that comes back every few weeks — 30 steps in five parts. Built in the order of the CoinCashew guide, written by BRIAN, every command checked against cardano-node 11.1.3. ← Back to Resources

Overview

What you build, the three kinds of machine, and how to read this guide.

O-1 What a stake pool is

Always-on Linux servers that make blocks for the ADA delegated to them.

A Cardano stake pool is a block producer that holds the pool's keys and forges blocks, and two or more relays that talk to the rest of the network. The block producer only ever talks to your own relays. The pool's cold keys never touch a computer with a network connection: they live on an air-gapped machine, which signs certificates and transactions that you carry over on a USB stick.

Each code block below is tagged with the machine it runs on. A block without a tag explains a file rather than running a command.

  • Block producer — the node with the pool keys, reachable only from your relays.
  • Relays — public nodes, at least two, ideally in different places.
  • Air-gapped machine — a computer that is never online. It creates and keeps the cold keys and signs.
  • Block producer or relay — any of your synced nodes; used to query the chain and submit transactions.

Note Build the whole pool on the Preview or Preprod testnet first. Test ADA is free from the faucet, and every step here works there with --testnet-magic 2 (Preview) or --testnet-magic 1 (Preprod) in place of --mainnet.

Next: Before you start →

O-2 Before you start

The versions this guide was checked against, and two settings every online command uses.

Every cardano-cli command on this page was run against cardano-node 11.1.3 with cardano-cli 11.2.3 on 3 October 2026. Commands start with cardano-cli latest, which is the current era (Conway); older guides without an era word no longer run.

Online commands find the node and the network through two variables. Put them into ~/.bashrc on every node:

Block producer or relay
echo 'export CARDANO_NODE_SOCKET_PATH=/run/cardano/node.socket' >> ~/.bashrc
echo 'export CARDANO_NODE_NETWORK_ID=mainnet' >> ~/.bashrc
source ~/.bashrc

Caution Nothing here is financial advice, and BRIAN is not responsible for your pool. Read each command before you run it, and keep the official documentation at hand: it changes with every node release.

Next: Prerequisites →

Part I — Installation

Machines, a hardened Ubuntu, time sync and the node itself.

I-1 Prerequisites

What you need before the first command.

  • Machines: one block producer, at least one registered relay (two or three are better, in different places), and one air-gapped computer.
  • Each node: 64-bit Linux (Ubuntu LTS), 2 or more CPU cores at 2 GHz, 24 GB RAM including swap, 300 GB free disk, a static IP address and at least 10 Mbps. Plan for about 30 GB of traffic a month. More RAM and disk give you room for the chain's growth.
  • ADA: 500 ADA pool deposit, 2 ADA stake address deposit and transaction fees — at least 505 ADA — plus your pledge.
  • Power and skills: reliable power (a UPS for anything at home), and comfort with the Linux command line, SSH and systemd.

Note The hardware figures are CoinCashew's published minimum. The deposits are protocol parameters and can change by governance vote; Part III reads them from the chain.

Next: Harden the server →

I-2 Harden the server

A non-root user, key-only SSH, automatic updates and fail2ban on every node.

Create an operator account and lock the root password:

All nodes
sudo useradd -m -s /bin/bash cardano-op
sudo passwd cardano-op
sudo usermod -aG sudo cardano-op
sudo passwd -l root

Create an SSH key on your own computer and copy it to the server:

Your own computer
ssh-keygen -t ed25519 -C "stake-pool-ops"
ssh-copy-id -i ~/.ssh/id_ed25519.pub cardano-op@<server-ip>

Then turn off password logins in /etc/ssh/sshd_config. Moving SSH to another port keeps the logs quiet:

File
Port 2222
PubkeyAuthentication yes
PasswordAuthentication no
PermitRootLogin no
PermitEmptyPasswords no
X11Forwarding no
KbdInteractiveAuthentication no
MaxAuthTries 3
LoginGraceTime 30
All nodes
sudo sshd -t && sudo systemctl reload ssh

Important Keep your current SSH session open and log in from a second terminal on the new port before you close it. A typo here locks you out.

Updates, automatic security updates and fail2ban:

All nodes
sudo apt-get update -y && sudo apt-get upgrade -y && sudo apt-get autoremove -y
sudo apt-get install -y unattended-upgrades fail2ban jq
sudo dpkg-reconfigure -plow unattended-upgrades
sudo systemctl enable --now fail2ban
Next: Firewall →

I-3 Firewall

Relays accept Cardano traffic from anyone; the block producer only from your relays.

This guide uses port 6000 for Cardano on every node and 2222 for SSH. Install nftables and write /etc/nftables.conf. On a relay:

File
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        ct state established,related accept
        iifname "lo" accept
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        tcp dport 2222 accept
        tcp dport 6000 accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

On the block producer, the Cardano port opens only to your relays, and SSH only to the address you manage from:

File
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        ct state established,related accept
        iifname "lo" accept
        ip protocol icmp accept
        ip6 nexthdr icmpv6 accept

        ip saddr <your-management-ip> tcp dport 2222 accept
        ip saddr { <relay-1-ip>, <relay-2-ip> } tcp dport 6000 accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}
All nodes
sudo apt-get install -y nftables
sudo systemctl enable nftables
sudo nft -f /etc/nftables.conf
sudo nft list ruleset
Next: Time sync →

I-4 Time sync

Blocks are made in one-second slots; a clock that drifts misses them.

All nodes
sudo apt-get install -y chrony
sudo systemctl enable --now chrony
chronyc tracking

chronyc tracking should show a system time offset of a few milliseconds at most.

Next: Install cardano-node →

I-5 Install cardano-node

The official release binaries, with the network configuration files included.

Take the newest release from IntersectMBO/cardano-node. Since 11.0 the Linux file is named …-linux-amd64.tar.gz.

All nodes
VERSION=11.1.3
cd ~
wget https://github.com/IntersectMBO/cardano-node/releases/download/${VERSION}/cardano-node-${VERSION}-linux-amd64.tar.gz
mkdir -p ~/.local
tar -xzf cardano-node-${VERSION}-linux-amd64.tar.gz -C ~/.local/
sudo install -m 755 ~/.local/bin/cardano-node ~/.local/bin/cardano-cli /usr/local/bin/
cardano-node --version
cardano-cli --version

The archive also holds mithril-signer, kes-agent and the configuration files for mainnet, preprod and preview under ~/.local/share/. Building from source is possible too; the Developer Portal describes it.

Next: Folders and configuration files →

Part II — Configuration

Folders, the chain via Mithril, topology, the air-gapped machine and the services.

II-1 Folders and configuration files

A system user for the node, its configuration in /etc/cardano, its data in /var/lib/cardano.

All nodes
sudo useradd -r -m -d /var/lib/cardano -s /sbin/nologin cardano
sudo mkdir -p /etc/cardano /var/lib/cardano/db
sudo cp ~/.local/share/mainnet/*.json /etc/cardano/
sudo chown -R cardano:cardano /etc/cardano /var/lib/cardano
sudo usermod -aG cardano $USER

Log out and back in so your account joins the cardano group and can use the node's socket.

The block producer must not share peers with the network:

Block producer
sudo jq '.PeerSharing = false' /etc/cardano/config.json > /tmp/config.json
sudo install -o cardano -g cardano -m 644 /tmp/config.json /etc/cardano/config.json
Next: Download the chain with Mithril →

II-2 Download the chain with Mithril

A certified snapshot of the chain in hours instead of days of syncing.

All nodes
curl --proto '=https' --tlsv1.2 -sSfL \
  https://raw.githubusercontent.com/IntersectMBO/mithril/refs/heads/main/mithril-install.sh \
  | sh -s -- -c mithril-client -d latest -p $HOME/.local/bin

export AGGREGATOR_ENDPOINT=https://aggregator.release-mainnet.api.mithril.network/aggregator
export GENESIS_VERIFICATION_KEY=$(wget -q -O - \
  https://raw.githubusercontent.com/IntersectMBO/mithril/main/mithril-infra/configuration/release-mainnet/genesis.vkey)
export ANCILLARY_VERIFICATION_KEY=$(wget -q -O - \
  https://raw.githubusercontent.com/IntersectMBO/mithril/main/mithril-infra/configuration/release-mainnet/ancillary.vkey)

mkdir -p ~/mithril && cd ~/mithril
~/.local/bin/mithril-client cardano-db download latest --include-ancillary
sudo rm -rf /var/lib/cardano/db
sudo mv ~/mithril/db /var/lib/cardano/db
sudo chown -R cardano:cardano /var/lib/cardano/db

Note If the install script reports a GitHub rate limit, download mithril-client from the Mithril releases by hand.

Next: Topology →

II-3 Topology

Who talks to whom: relays to the network and to your block producer, the block producer only to your relays.

On each relay, /etc/cardano/topology.json keeps the network settings of the release file and adds your block producer as a local root:

File
{
  "bootstrapPeers": [
    { "address": "backbone.cardano.iog.io", "port": 3001 },
    { "address": "backbone.mainnet.cardanofoundation.org", "port": 3001 }
  ],
  "localRoots": [
    {
      "accessPoints": [
        { "address": "<block-producer-ip>", "port": 6000 }
      ],
      "advertise": false,
      "trustable": true,
      "valency": 1
    }
  ],
  "peerSnapshotFile": "peer-snapshot.json",
  "publicRoots": [
    { "accessPoints": [], "advertise": false }
  ],
  "useLedgerAfterSlot": 194140785
}

On the block producer, only the relays — no bootstrap peers, no peers from the ledger:

File
{
  "bootstrapPeers": [],
  "localRoots": [
    {
      "accessPoints": [
        { "address": "<relay-1-ip>", "port": 6000 },
        { "address": "<relay-2-ip>", "port": 6000 }
      ],
      "advertise": false,
      "trustable": true,
      "valency": 2
    }
  ],
  "publicRoots": [],
  "useLedgerAfterSlot": -1
}

Copy useLedgerAfterSlot for the relays from the topology file of your release. The node re-reads the local roots without a restart:

Block producer or relay
sudo systemctl reload cardano-node
Next: The air-gapped machine →

II-4 The air-gapped machine

A computer that is never connected, for the cold keys and every signature.

Use a spare laptop or a small PC with a fresh Ubuntu install. After installing, remove or switch off Wi-Fi and Bluetooth for good and never plug in a network cable. Copy cardano-cli over on a USB stick:

Air-gapped machine
sudo install -m 755 /media/$USER/<usb-stick>/cardano-cli /usr/local/bin/
cardano-cli --version
mkdir -p ~/cold-keys && chmod 700 ~/cold-keys

Important cold.skey, cold.counter, payment.skey and stake.skey never leave this machine, except as an encrypted backup. Whoever holds them owns the pool and its rewards.

  • From a node to the air-gapped machine: unsigned transactions (tx.raw), kes.vkey, the current KES period.
  • From the air-gapped machine to a node: signed transactions (tx.signed), certificates, node.cert, vrf.skey and kes.skey for the block producer.
  • Keep two encrypted backups of the cold folder in different places, and test that you can restore them.
Next: Run the node as a service →

II-5 Run the node as a service

systemd starts the node at boot and restarts it when it stops.

Write /etc/systemd/system/cardano-node.service. On a relay:

File
[Unit]
Description=Cardano Node
Wants=network-online.target
After=network-online.target

[Service]
User=cardano
Group=cardano
Type=simple
WorkingDirectory=/var/lib/cardano
ExecStart=/usr/local/bin/cardano-node run \
  --config        /etc/cardano/config.json \
  --topology      /etc/cardano/topology.json \
  --database-path /var/lib/cardano/db \
  --socket-path   /run/cardano/node.socket \
  --host-addr     0.0.0.0 \
  --port          6000
ExecReload=pkill -HUP cardano-node
KillSignal=SIGINT
RestartKillSignal=SIGINT
TimeoutStopSec=300
LimitNOFILE=131072
Restart=always
RestartSec=5
SyslogIdentifier=cardano-node
RuntimeDirectory=cardano
RuntimeDirectoryMode=0750

[Install]
WantedBy=multi-user.target

On the block producer the same file, with the three pool files added to ExecStart once you have them (Part III, step 4):

File
ExecStart=/usr/local/bin/cardano-node run \
  --config        /etc/cardano/config.json \
  --topology      /etc/cardano/topology.json \
  --database-path /var/lib/cardano/db \
  --socket-path   /run/cardano/node.socket \
  --host-addr     0.0.0.0 \
  --port          6000 \
  --shelley-kes-key                 /var/lib/cardano/keys/kes.skey \
  --shelley-vrf-key                 /var/lib/cardano/keys/vrf.skey \
  --shelley-operational-certificate /var/lib/cardano/keys/node.cert
All nodes
sudo systemctl daemon-reload
sudo systemctl enable --now cardano-node
journalctl -fu cardano-node
Next: Check the sync →

Part III — Operation

Sync, keys, the two registrations and the first checks.

III-1 Check the sync

Wait until the node reports 100 percent before you register anything.

Block producer or relay
cardano-cli latest query tip

syncProgress has to read "100.00". If the command cannot reach the socket, check that you are in the cardano group and that the service runs.

Next: Payment and stake keys →

III-2 Payment and stake keys

The wallet that pays the deposits and receives the rewards.

Air-gapped machine
cd ~/cold-keys
cardano-cli latest address key-gen \
  --verification-key-file payment.vkey \
  --signing-key-file payment.skey
cardano-cli latest stake-address key-gen \
  --verification-key-file stake.vkey \
  --signing-key-file stake.skey
cardano-cli latest address build \
  --payment-verification-key-file payment.vkey \
  --stake-verification-key-file stake.vkey \
  --mainnet \
  --out-file payment.addr
cardano-cli latest stake-address build \
  --stake-verification-key-file stake.vkey \
  --mainnet \
  --out-file stake.addr

Copy payment.addr, stake.addr and the two .vkey files to a node, send at least 505 ADA plus your pledge to payment.addr, and check:

Block producer or relay
cardano-cli latest query utxo --address $(cat payment.addr) --output-json

Note Send a small amount first and check that it arrives.

Next: Register the stake address →

III-3 Register the stake address

A certificate with the 2 ADA deposit, built online, signed offline, submitted online.

Read the current deposit and create the certificate:

Block producer or relay
cardano-cli latest query protocol-parameters --out-file protocol.json
jq '.stakeAddressDeposit' protocol.json
cardano-cli latest stake-address registration-certificate \
  --stake-verification-key-file stake.vkey \
  --key-reg-deposit-amt $(jq '.stakeAddressDeposit' protocol.json) \
  --out-file stake.cert
cardano-cli latest transaction build \
  --tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
  --change-address $(cat payment.addr) \
  --certificate-file stake.cert \
  --witness-override 2 \
  --out-file tx.raw

Carry tx.raw to the air-gapped machine and sign with the payment and the stake key:

Air-gapped machine
cardano-cli latest transaction sign \
  --tx-body-file tx.raw \
  --signing-key-file payment.skey \
  --signing-key-file stake.skey \
  --mainnet \
  --out-file tx.signed

Carry tx.signed back and submit it:

Block producer or relay
cardano-cli latest transaction submit --tx-file tx.signed

Note keys[0] spends the first UTxO of the address. If the first one is too small, list them with query utxo and name the right one in --tx-in, or add several --tx-in.

Next: Pool keys and the operational certificate →

III-4 Pool keys and the operational certificate

Cold keys stay offline; the block producer gets VRF, KES and the certificate.

Create the cold keys, the VRF key and a first KES key on the air-gapped machine:

Air-gapped machine
cd ~/cold-keys
cardano-cli latest node key-gen \
  --cold-verification-key-file cold.vkey \
  --cold-signing-key-file cold.skey \
  --operational-certificate-issue-counter-file cold.counter
cardano-cli latest node key-gen-VRF \
  --verification-key-file vrf.vkey \
  --signing-key-file vrf.skey
cardano-cli latest node key-gen-KES \
  --verification-key-file kes.vkey \
  --signing-key-file kes.skey

The operational certificate needs the current KES period. A KES period is 129,600 slots (36 hours); a KES key lasts 62 periods, about 93 days.

Block producer or relay
slotsPerKESPeriod=$(jq -r '.slotsPerKESPeriod' /etc/cardano/shelley-genesis.json)
slotNo=$(cardano-cli latest query tip | jq -r '.slot')
echo $(( slotNo / slotsPerKESPeriod ))
Air-gapped machine
cardano-cli latest node issue-op-cert \
  --kes-verification-key-file kes.vkey \
  --cold-signing-key-file cold.skey \
  --operational-certificate-issue-counter-file cold.counter \
  --kes-period <kes-period> \
  --out-file node.cert

Copy kes.skey, vrf.skey and node.cert to the block producer and lock them down:

Block producer
sudo mkdir -p /var/lib/cardano/keys
sudo install -o cardano -g cardano -m 400 kes.skey vrf.skey node.cert /var/lib/cardano/keys/
shred -u kes.skey vrf.skey

Add the three files to the service (Part II, step 5) and restart the block producer: sudo systemctl daemon-reload && sudo systemctl restart cardano-node.

Caution Keep vrf.skey in your cold backup as well — a pool with a lost VRF key has to be registered again with a new one.

Next: Register the pool →

III-5 Register the pool

Metadata, the registration and delegation certificates, and the 500 ADA deposit.

Write poolMetaData.json and publish it at an address you control, at most 64 characters long — your homepage or GitHub Pages. The ticker has 3 to 5 characters:

File
{
  "name": "Your Pool Name",
  "description": "What your pool stands for",
  "ticker": "TICK",
  "homepage": "https://yourpool.example"
}

Hash the published file, not your local copy, and read the minimum fixed cost:

Block producer or relay
cardano-cli latest stake-pool metadata-hash \
  --pool-metadata-file <(curl -s -L https://yourpool.example/poolMetaData.json) \
  --out-file poolMetaDataHash.txt
jq '.minPoolCost, .stakePoolDeposit' protocol.json

Copy poolMetaDataHash.txt to the air-gapped machine and create both certificates there. Amounts are in lovelace (1 ADA = 1,000,000 lovelace); the example pledges 1,000 ADA, asks 170 ADA fixed cost and a 1 % margin, and names two relays:

Air-gapped machine
cardano-cli latest stake-pool registration-certificate \
  --cold-verification-key-file cold.vkey \
  --vrf-verification-key-file vrf.vkey \
  --pool-pledge 1000000000 \
  --pool-cost 170000000 \
  --pool-margin 0.01 \
  --pool-reward-account-verification-key-file stake.vkey \
  --pool-owner-stake-verification-key-file stake.vkey \
  --single-host-pool-relay relay1.yourpool.example --pool-relay-port 6000 \
  --single-host-pool-relay relay2.yourpool.example --pool-relay-port 6000 \
  --metadata-url https://yourpool.example/poolMetaData.json \
  --metadata-hash $(cat poolMetaDataHash.txt) \
  --mainnet \
  --out-file pool.cert
cardano-cli latest stake-address stake-delegation-certificate \
  --stake-verification-key-file stake.vkey \
  --cold-verification-key-file cold.vkey \
  --out-file deleg.cert

Build online — the deposit is added for you — sign offline with three keys, submit online:

Block producer or relay
cardano-cli latest transaction build \
  --tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
  --change-address $(cat payment.addr) \
  --certificate-file pool.cert \
  --certificate-file deleg.cert \
  --witness-override 3 \
  --out-file tx.raw
Air-gapped machine
cardano-cli latest transaction sign \
  --tx-body-file tx.raw \
  --signing-key-file payment.skey \
  --signing-key-file stake.skey \
  --signing-key-file cold.skey \
  --mainnet \
  --out-file tx.signed
Block producer or relay
cardano-cli latest transaction submit --tx-file tx.signed

Note transaction build downloads your metadata from the URL and refuses the transaction if its hash does not match — publish the file before you build.

Caution Your pledge must stay in the owner's wallet. If it drops below the pledge you declared, the pool earns no rewards for that epoch.

Next: Check the registration →

III-6 Check the registration

Your pool ID, its stake and its place on the explorers.

Air-gapped machine
cardano-cli latest stake-pool id --cold-verification-key-file cold.vkey --output-format bech32 > stakepoolid.txt
cat stakepoolid.txt
Block producer or relay
cardano-cli latest query stake-snapshot --stake-pool-id $(cat stakepoolid.txt)

Copy stakepoolid.txt to your nodes. Search the ID on cexplorer.io or adastat.net. Stake counts after two epoch boundaries; the first block can take a while for a small pool.

Next: Monitor and check the leader schedule →

III-7 Monitor and check the leader schedule

See the node's health, and know in advance when the pool is due to make a block.

For the terminal, gLiveView from the Guild Operators shows peers, sync and KES at a glance. For a full dashboard, Prometheus and Grafana read the node's metrics on port 12798; the Developer Portal walks through both.

The leader schedule for the next epoch is known 1.5 days before it starts (--current works any time):

Block producer
sudo -u cardano cardano-cli latest query leadership-schedule \
  --mainnet \
  --socket-path /run/cardano/node.socket \
  --genesis /etc/cardano/shelley-genesis.json \
  --stake-pool-id $(cat stakepoolid.txt) \
  --vrf-signing-key-file /var/lib/cardano/keys/vrf.skey \
  --next

Note Run a Mithril signer as well: it helps certify the snapshots others sync from. See mithril.network.

Next: Renew the KES key →

Part IV — Administration

What comes back every few weeks: KES, rewards, votes, updates and the end.

IV-1 Renew the KES key

Every 90 days at the latest, or the block producer stops making blocks.

Block producer
sudo -u cardano cardano-cli latest query kes-period-info \
  --mainnet \
  --socket-path /run/cardano/node.socket \
  --op-cert-file /var/lib/cardano/keys/node.cert

Read the current KES period as in Part III, step 4, then create a new KES key and certificate on the air-gapped machine. The counter in cold.counter goes up by one each time:

Air-gapped machine
cd ~/cold-keys
cardano-cli latest node key-gen-KES \
  --verification-key-file kes.vkey \
  --signing-key-file kes.skey
cardano-cli latest node issue-op-cert \
  --kes-verification-key-file kes.vkey \
  --cold-signing-key-file cold.skey \
  --operational-certificate-issue-counter-file cold.counter \
  --kes-period <kes-period> \
  --out-file node.cert
Block producer
sudo install -o cardano -g cardano -m 400 kes.skey node.cert /var/lib/cardano/keys/
shred -u kes.skey
sudo systemctl restart cardano-node

Caution The counter in a new certificate may be at most one higher than the counter in the pool's last block. If the pool made no block with your last certificate, do not issue another one on top: reuse it, or set the counter back with cardano-cli latest node new-counter. kes-period-info shows both numbers.

Next: Delegate your vote →

IV-2 Delegate your vote

Since 2025 a reward account must delegate its vote before rewards can be withdrawn.

Delegate to a DRep you trust, or to the built-in always abstain option:

Air-gapped machine
cardano-cli latest stake-address vote-delegation-certificate \
  --stake-verification-key-file stake.vkey \
  --always-abstain \
  --out-file vote-deleg.cert

Build a transaction with --certificate-file vote-deleg.cert and --witness-override 2, sign with payment.skey and stake.skey, and submit — the same three moves as Part III, step 3. To choose a DRep, use --drep-key-hash instead of --always-abstain.

Next: Withdraw the rewards →

IV-3 Withdraw the rewards

Move the rewards from the reward account into your wallet.

Block producer or relay
cardano-cli latest query stake-address-info --address $(cat stake.addr)
rewards=$(cardano-cli latest query stake-address-info --address $(cat stake.addr) | jq -r '.[0].rewardAccountBalance')
cardano-cli latest transaction build \
  --tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
  --withdrawal "$(cat stake.addr)+${rewards}" \
  --change-address $(cat payment.addr) \
  --witness-override 2 \
  --out-file tx.raw

Sign with payment.skey and stake.skey on the air-gapped machine and submit. The withdrawal must take the whole balance.

Next: Vote as a stake pool operator →

IV-4 Vote as a stake pool operator

Pools vote on hard forks, some parameter changes, no-confidence and committee updates.

Block producer or relay
cardano-cli latest query proposals --all-proposals \
  | jq '.[] | {id: .actionId, type: .proposalProcedure.govAction.tag, url: .proposalProcedure.anchor.url}'

Read the proposal behind its anchor, then create your vote on the air-gapped machine:

Air-gapped machine
cardano-cli latest governance vote create \
  --yes \
  --governance-action-tx-id <tx-id> \
  --governance-action-index 0 \
  --cold-verification-key-file cold.vkey \
  --out-file pool.vote
Block producer or relay
cardano-cli latest transaction build \
  --tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
  --change-address $(cat payment.addr) \
  --vote-file pool.vote \
  --witness-override 2 \
  --out-file tx.raw

Sign with cold.skey and payment.skey, submit. Use --no or --abstain as you decide; --anchor-url and --anchor-data-hash attach a rationale.

Next: Change the pool's parameters →

IV-5 Change the pool's parameters

Pledge, cost, margin, relays or metadata — without a new deposit.

Create a new pool.cert exactly as in Part III, step 5, with the new values. Submit it with --certificate-file pool.cert alone, signed with payment.skey, stake.skey and cold.skey. The new values take effect at an epoch boundary, the old ones apply until then; changed metadata needs a new hash.

Next: Upgrade the node →

IV-6 Upgrade the node

A new release every few months; relays first, the block producer last.

All nodes
VERSION=<new-version>
cd ~
wget https://github.com/IntersectMBO/cardano-node/releases/download/${VERSION}/cardano-node-${VERSION}-linux-amd64.tar.gz
tar -xzf cardano-node-${VERSION}-linux-amd64.tar.gz -C ~/.local/
sudo systemctl stop cardano-node
sudo install -m 755 ~/.local/bin/cardano-node ~/.local/bin/cardano-cli /usr/local/bin/
sudo systemctl start cardano-node
cardano-node --version

Read the release notes first: some releases need new configuration files or a database migration. Upgrade the block producer right after a block, never just before a scheduled one. Copy the new cardano-cli to the air-gapped machine as well.

Next: Retire the pool →

IV-7 Retire the pool

Announce an epoch; at its start the 500 ADA deposit goes to the reward account.

Air-gapped machine
cardano-cli latest stake-pool deregistration-certificate \
  --cold-verification-key-file cold.vkey \
  --epoch <retirement-epoch> \
  --out-file pool.dereg

Submit it in a transaction signed with payment.skey and cold.skey. Give your delegators time to move: announce it, and pick an epoch at least two ahead.

Next: Send ADA from the pool wallet →

Part V — Tips

Small things that come up.

V-1 Send ADA from the pool wallet

A plain payment, built online and signed offline.

Block producer or relay
cardano-cli latest transaction build \
  --tx-in $(cardano-cli latest query utxo --address $(cat payment.addr) --output-json | jq -r 'keys[0]') \
  --tx-out "<receiver-address>+10000000" \
  --change-address $(cat payment.addr) \
  --out-file tx.raw

Sign with payment.skey, submit. The example sends 10 ADA.

Next: Copy files between machines →

V-2 Copy files between machines

scp between your computer and the nodes; a USB stick to and from the air-gapped machine.

Your own computer
scp -P 2222 cardano-op@<node-ip>:~/tx.raw .
scp -P 2222 tx.signed cardano-op@<node-ip>:~/

Never copy a signing key over the network, and wipe the USB stick after moving key files.

Next: Add swap →

V-3 Add swap

Swap protects a node with little RAM from being killed during a memory peak.

All nodes
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h
Next: Checklist →

V-4 Checklist

The habits that keep a pool safe and running.

  • Cold keys only on the air-gapped machine, with two tested, encrypted backups.
  • Block producer reachable only from your relays; SSH only with keys.
  • KES renewed before day 90 — set a reminder.
  • Pledge never below what you declared.
  • Releases read and installed; relays first.
  • Votes cast on governance actions, rewards account delegated to a DRep or to abstain.
  • Every change tried on the testnet first.

Sources and license

The order follows the CoinCashew guide; the text is BRIAN's. Commands for system setup, the firewall, Mithril and the service file are adapted from the Cardano Developer Portal; every cardano-cli command was rewritten for and run against cardano-cli 11.2.3, because several on the portal no longer run unchanged. The node's own documentation always takes precedence.

Developer Portal license (MIT)

Copyright (c) 2021 Cardano Foundation

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.