Skip to content

Run a Cashu Mint

The mint. This is the service that issues and redeems ecash using your Lightning node as a backend for deposits and withdrawals. Mints are elegant in their operational simplicity, once you’ve gotten the setup right.

This is the full walkthrough. It uses CDK, the Cashu Development Kit, whose cdk-mintd is a production-minded mint daemon in Rust. You install its official prebuilt binary, point it at your LND node, store its data in the Postgres you set up for Lightning, and expose it on the internet with a Cloudflare Tunnel.

  • cdk-mintd installed from its official prebuilt binary, checksum-verified and placed as a system binary.
  • An isolated cdk-mintd service user that reads your LND node’s TLS certificate and macaroon.
  • Postgres and Redis wired in: Postgres for the mint database (the same instance your LND node uses) and Redis for the response cache.
  • A hardened systemd service so the mint starts on boot and restarts on failure.
  • A public mint URL served through a Cloudflare Tunnel, with TLS terminated at Cloudflare’s edge.
  • A running LND node from the Lightning step, reachable on its gRPC port (10009 by default), with its TLS certificate and macaroons on this machine.
  • PostgreSQL installed and running. The Lightning step sets this up if you followed its tip to choose the Postgres backend. You reuse that instance here.
  • A domain. Registered with any registrar. The mint is exposed through a Cloudflare Tunnel, which routes a hostname on your domain to it.
  • A free Cloudflare account. Add your domain to it and point the domain’s nameservers at Cloudflare, so the tunnel can manage the hostname’s DNS.

cdk-mintd caches deterministic responses (NUT-19) so a retried mint, swap, or melt returns the same result instead of being processed twice. Backing that cache with Redis keeps it across restarts.

  • With user admin, install Redis and enable it to start on boot
Terminal window
sudo apt update
sudo apt install redis-server
sudo systemctl enable redis-server
  • Confirm Redis is listening on 127.0.0.1 only, reachable from this machine and not the network
Terminal window
sudo ss -tulpn | grep 6379

Example of expected output:

tcp LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=1234,fd=6))

Reuse the Postgres instance your LND node runs on, but give the mint its own role instead of the shared admin one. Create a cdk_mintd role and a database it owns.

  • With user admin, create the role and its database, choosing a strong password
Terminal window
sudo -u postgres psql -c "CREATE ROLE cdk_mintd WITH LOGIN PASSWORD 'your-mint-db-password';"
sudo -u postgres createdb -O cdk_mintd cdk_mintd

For improved security, create a dedicated cdk-mintd user to run the mint. A dedicated user limits the damage if the daemon is ever compromised: an attacker is confined to this user’s permissions and cannot reach the rest of the machine.

  • With user admin, create the cdk-mintd user and group
Terminal window
sudo adduser --disabled-password --gecos "" cdk-mintd
  • Add the cdk-mintd user to the lnd group, so it can read your Lightning node’s TLS certificate and macaroon
Terminal window
sudo usermod -aG lnd cdk-mintd

Give the mint access to your LND credentials

Section titled “Give the mint access to your LND credentials”

cdk-mintd authenticates to LND with a TLS certificate and an admin macaroon. As a member of the lnd group, the cdk-mintd user has read-only access to those files; symlink the LND data directory into its home so the daemon finds them under ~/.lnd.

  • Change to the cdk-mintd user
Terminal window
sudo su - cdk-mintd
  • Link the LND data directory into the cdk-mintd home
Terminal window
ln -s /data/lnd /home/cdk-mintd/.lnd
  • Check that the symbolic link was created correctly
Terminal window
ls -la /home/cdk-mintd/.lnd

Example of expected output:

lrwxrwxrwx 1 cdk-mintd cdk-mintd 9 Jun 19 12:00 /home/cdk-mintd/.lnd -> /data/lnd
  • Come back to the admin user
Terminal window
exit
  • Make the LND data directories browsable by the group, and allow the group to read the admin.macaroon
Terminal window
sudo chmod -R g+X /data/lnd/data/
sudo chmod g+r /data/lnd/data/chain/bitcoin/mainnet/admin.macaroon

CDK publishes a static binary for each release, built with the Postgres and Redis features this guide uses, so there is nothing to compile.

  • With user admin, download the release binary for your architecture and its checksum file
Terminal window
cd /tmp
VERSION=0.17.1
ARCH=$(uname -m)
wget https://github.com/cashubtc/cdk/releases/download/v$VERSION/cdk-mintd-$VERSION-$ARCH
wget https://github.com/cashubtc/cdk/releases/download/v$VERSION/SHA256SUMS
  • Verify the download against the checksum published with the release
Terminal window
sha256sum --check --ignore-missing SHA256SUMS

Example of expected output:

cdk-mintd-0.17.1-x86_64: OK
  • Install the binary to a system path, confirm the version, and remove the downloads
Terminal window
sudo install -m 0755 cdk-mintd-$VERSION-$ARCH /usr/local/bin/cdk-mintd
cdk-mintd --version
rm cdk-mintd-$VERSION-$ARCH SHA256SUMS

Example of expected output:

cdk-mintd 0.17.1

The mint keeps its configuration and seed in a work directory. Following the same layout, put it under /data.

  • With user admin, create the work directory and give it to the cdk-mintd user
Terminal window
sudo mkdir -p /data/cdk-mintd
sudo chown -R cdk-mintd:cdk-mintd /data/cdk-mintd

cdk-mintd derives the mint’s signing keys from a standard BIP-39 seed phrase. The mint signs with it on every operation, so the seed lives here on the server. CDK does not generate one for you, so make a fresh 24 word phrase with the BIP-39 reference implementation, which Ubuntu packages.

  • With user admin, install the generator and print a fresh phrase
Terminal window
sudo apt install python3-mnemonic
python3 -c "from mnemonic import Mnemonic; print(Mnemonic('english').generate(strength=256))"

Example of expected output (yours will differ, and you must use your own):

army van defense carry jealous true garbage claim echo media make ...
  • Write the phrase down and keep an offline copy before going further. You paste it into config.toml in the next step, but the server should not be the only place it lives: if the disk dies, that offline copy is half of how you restore the mint.
  • Change to the cdk-mintd user and create the config file in the work directory
Terminal window
sudo su - cdk-mintd
nano /data/cdk-mintd/config.toml
  • Paste the following, replacing the placeholders. Set url to the public hostname you will route through the tunnel below, paste your seed into mnemonic, and use the LND macaroon path for your network (mainnet shown). Save and exit
/data/cdk-mintd/config.toml
[info]
url = "https://mint.yourdomain.com/"
listen_host = "127.0.0.1"
listen_port = 8085
mnemonic = "your twelve or twenty-four word seed phrase here"
[info.http_cache]
backend = "redis"
ttl = 60
tti = 60
key_prefix = "mintd"
connection_string = "redis://127.0.0.1:6379"
[mint_info]
name = "Your Mint"
description = "Sovereign bank in cyberspace"
contact_email = "you@yourdomain.com"
[database]
engine = "postgres"
[database.postgres]
url = "postgresql://cdk_mintd:your-mint-db-password@127.0.0.1:5432/cdk_mintd"
tls_mode = "disable"
[ln]
ln_backend = "lnd"
min_mint = 1
max_mint = 500000
min_melt = 1
max_melt = 500000
[lnd]
address = "https://localhost:10009"
cert_file = "/home/cdk-mintd/.lnd/tls.cert"
macaroon_file = "/home/cdk-mintd/.lnd/data/chain/bitcoin/mainnet/admin.macaroon"
[mint_management_rpc]
enabled = true
address = "127.0.0.1"
port = 8086
  • Come back to the admin user
Terminal window
exit

Now ensure the mint starts as a service so that it is always running, restarts on failure, and comes back after a reboot.

  • With user admin, create the service file
Terminal window
sudo nano /etc/systemd/system/cdk-mintd.service
  • Paste the following configuration. Save and exit
/etc/systemd/system/cdk-mintd.service
# Orchard: systemd unit for cdk-mintd
# /etc/systemd/system/cdk-mintd.service
[Unit]
Description=CDK Cashu mint daemon
Requires=lnd.service postgresql.service redis-server.service
After=lnd.service postgresql.service redis-server.service
[Service]
ExecStart=/usr/local/bin/cdk-mintd --work-dir /data/cdk-mintd
User=cdk-mintd
Group=cdk-mintd
# Process management
####################
Restart=on-failure
RestartSec=30
Type=simple
# Hardening Measures
####################
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true
PrivateDevices=true
[Install]
WantedBy=multi-user.target
  • Reload systemd so it sees the new unit
Terminal window
sudo systemctl daemon-reload
  • Enable autoboot (optional)
Terminal window
sudo systemctl enable cdk-mintd
  • Prepare cdk-mintd monitoring by the systemd journal. You can exit monitoring at any time with Ctrl-C
Terminal window
journalctl -fu cdk-mintd

To keep an eye on the mint as it starts, open a second terminal, connect to the node, and log in as admin.

  • With user admin, start the service
Terminal window
sudo systemctl start cdk-mintd

Watch the journal in the other terminal until it settles.

  • Ensure the mint is listening on its local 8085 port
Terminal window
sudo ss -tulpn | grep 8085

Example of expected output:

tcp LISTEN 0 1024 127.0.0.1:8085 0.0.0.0:* users:(("cdk-mintd",pid=5678,fd=10))
  • Ask the mint for its public info from the machine itself. It returns a JSON document describing your mint
Terminal window
curl http://127.0.0.1:8085/v1/info

Example of expected output (truncated):

{"name":"Your Mint","version":"cdk-mintd/0.17.1","description":"Sovereign bank in cyberspace","contact":[...],"nuts":{...}}

Put your mint online with a Cloudflare Tunnel

Section titled “Put your mint online with a Cloudflare Tunnel”

Wallets reach a mint over HTTPS, so the mint needs a public hostname. A Cloudflare Tunnel makes one outbound connection from this machine to Cloudflare’s network and forwards traffic to the mint’s local port — your home IP is never exposed, and there is no router port to forward.

Follow MiniBolt’s Cloudflare Tunnel guide to install cloudflared, authenticate it to your Cloudflare account, create a tunnel, and run it as a systemd service. It is the same cloudflared setup MiniBolt uses for its other services, with checksum-verified downloads and screenshots for the Cloudflare dashboard steps. Two things are specific to the mint:

  • Use your mint’s hostname throughout. Wherever the guide uses subdomain.domain.com — in the cloudflared tunnel route dns command and in the tunnel config — use the same hostname you set as url in config.toml, for example mint.yourdomain.com.
  • Route it to the mint’s local port. In the tunnel’s config.yml, replace the guide’s example ingress rules with a single rule for the mint, keeping the http_status:404 rule last:
ingress:
# Cashu mint
- hostname: mint.yourdomain.com
service: http://localhost:8085
- service: http_status:404

With the tunnel running, ask your public mint for its info from any machine. You should get the same JSON as the local check, now over HTTPS:

Terminal window
curl https://mint.yourdomain.com/v1/info
  • Mint information. name, description, contact_email, motd, and icon_url in [mint_info] are what wallets show before someone trusts your mint. Keep them accurate. Once Orchard is connected you manage these from the dashboard; see Mint information.
  • Fees and limits. Set input_fee_ppk under [info] to charge a per-input fee, fee_percent and reserve_fee_min under [lnd] to tune the Lightning fee reserve, and max_inputs / max_outputs under [limits] to cap transaction size. Once Orchard is connected, you can change the input fee by rotating to a new keyset; see Keysets.
  • A least-privilege macaroon. Instead of admin.macaroon, bake an LND macaroon limited to invoice and offchain permissions and point macaroon_file at it, so a leaked mint credential cannot touch the rest of your node.
  • Other ways to expose the mint. The Tunnel is the easy option and hides your IP. Alternatives: a VPS relay you control (also hides your IP, no Cloudflare in the path), or a forwarded port from home (simplest, but exposes your IP). Tor won’t work as wallets can’t reach a .onion mint.

The management gRPC runs insecurely in the main setup because Orchard shares this machine and the port is reachable only from the machine itself. If Orchard runs on a different host, or you want authentication once the connection leaves this machine, give the interface mutual TLS instead. cdk-mintd does not generate these certificates: you create a small private CA that signs one certificate for the mint and one for Orchard.

  • As the cdk-mintd user, create the TLS directory the mint reads and generate the certificates
Terminal window
sudo su - cdk-mintd
mkdir -p /data/cdk-mintd/tls && cd /data/cdk-mintd/tls
# A private CA that signs both the mint (server) and Orchard (client)
openssl genpkey -algorithm RSA -out ca.key
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -subj "/CN=cdk-mintd CA" -out ca.pem
# The mint's server certificate, valid for the local address Orchard dials
openssl genpkey -algorithm RSA -out server.key
openssl req -new -key server.key -subj "/CN=cdk-mintd" -out server.csr
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -sha256 \
-extfile <(printf "subjectAltName=IP:127.0.0.1,DNS:localhost") -out server.pem
# Orchard's client certificate, signed by the same CA
openssl genpkey -algorithm RSA -out client.key
openssl req -new -key client.key -subj "/CN=orchard" -out client.csr
openssl x509 -req -in client.csr -CA ca.pem -CAkey ca.key -CAcreateserial -days 3650 -sha256 -out client.pem
rm server.csr client.csr
exit

The mint reads server.pem, server.key, and ca.pem from this directory. Because the directory now exists with certificates in it, cdk-mintd serves the management gRPC over mutual TLS on its next start, and any client must present a certificate signed by your CA.

  • With user admin, restart the mint to pick up the certificates
Terminal window
sudo systemctl restart cdk-mintd

Give Orchard the three client files so it can connect: ca.pem to verify the mint, plus client.pem and client.key as its own identity. To return to running insecurely, stop the mint, remove /data/cdk-mintd/tls, and start it again.

  • With user admin, stop the service
Terminal window
sudo systemctl stop cdk-mintd
  • Back up the mint database before upgrading, so you can roll back if the new version misbehaves. Orchard reads the database straight from Postgres, so the backup works with the mint stopped — and stopping first gives a clean snapshot. See Backup and restore.

  • Download the new release binary and its checksum for your architecture (replace the version with the release you want)

Terminal window
cd /tmp
VERSION=0.17.1
ARCH=$(uname -m)
wget https://github.com/cashubtc/cdk/releases/download/v$VERSION/cdk-mintd-$VERSION-$ARCH
wget https://github.com/cashubtc/cdk/releases/download/v$VERSION/SHA256SUMS
sha256sum --check --ignore-missing SHA256SUMS
  • Replace the binary, clean up, and start the service again
Terminal window
sudo install -m 0755 cdk-mintd-$VERSION-$ARCH /usr/local/bin/cdk-mintd
rm cdk-mintd-$VERSION-$ARCH SHA256SUMS
sudo systemctl start cdk-mintd
  • With user admin, stop and disable the service, then remove the unit
Terminal window
sudo systemctl stop cdk-mintd
sudo systemctl disable cdk-mintd
sudo rm /etc/systemd/system/cdk-mintd.service
sudo systemctl daemon-reload
  • Delete the cdk-mintd user. Do not worry about the userdel: cdk-mintd mail spool (/var/mail/cdk-mintd) not found message
Terminal window
sudo userdel -rf cdk-mintd
  • Drop the mint database
Terminal window
sudo -u postgres dropdb cdk_mintd
  • Remove the work directory and the installed binary
Terminal window
sudo rm -rf /data/cdk-mintd
sudo rm /usr/local/bin/cdk-mintd
  • Delete the mint’s ingress entry from /home/admin/.cloudflared/config.yml, remove the DNS record from your Cloudflare dashboard, and delete the tunnel if it is no longer used. Use the name you gave it (cloudflared tunnel list shows it)
Terminal window
cloudflared tunnel delete <NAME>
PortProtocolUse
8085TCP (localhost)Mint HTTP API, forwarded by the tunnel
8086TCP (localhost)Management gRPC, used by Orchard
6379TCP (localhost)Redis response cache
5432TCP (localhost)Postgres, shared with LND

The mint has no inbound public port. Wallets reach it through the Cloudflare Tunnel’s outbound connection.


New Mint Cashu Mint

Last updated: