Run a canoLiq Node
A canoLiq node is a Canopy node for chain 29, the canoLiq chain on Canopy mainnet, running the canoLiq plugin. It follows the chain, re-executes every block with the plugin, and serves:
- the Canopy RPC (
:50002): blocks, accounts, validators, andPOST /v1/tx; - the canoLiq plugin API (
:8587):/v1/health,/v1/globals,/v1/params,/v1/account/{addr}, and more.
Everything you need is in github.com/Canoliq/node. Its config is already correct for chain 29, so clone it and use it as-is. To validate chain 29, see Run a canoLiq Validator.
Requirements
| Machine | 2 vCPU / 4 GB RAM / 50 GB SSD, with Docker. Alongside a chain-1 node: 4 vCPU / 8 GB / 100 GB. |
| Network | inbound TCP 9029 open, in both the host firewall and the provider's firewall |
| Hostname | a public hostname or IP that resolves directly to the server. P2P is raw TCP, so it cannot go through an HTTP proxy or CDN. |
| Root chain | a chain-1 RPC. The repo defaults to Canopy's public one, so you don't need to run chain 1 yourself. |
1. Clone the repo
git clone https://github.com/Canoliq/node.git canoliq-node
cd canoliq-node
2. Create .env
cp .env.example .env
Then set DOMAIN in .env to your server's public hostname:
DOMAIN=<your-public-host>
DOMAIN overrides externalAddress in config.json, so don't skip it. Without it the node advertises localhost and no peer can reach it.
If a chain-1 node runs on the same server, use the same DOMAIN as that node. It's the same machine; the two nodes are told apart by port (9001 for chain 1, 9029 for chain 29).
3. Create the node key
A full node needs its own key. Generate a fresh one:
make gen-key
This creates config/validator_key.json and config/keystore.json. Don't copy a validator's key here unless you're setting up that validator's chain-29 node (see Run a canoLiq Validator). Two chain-29 nodes with the same key is a double-sign.
4. Set the root chain
Chain 29 follows chain 1, so rootChain in config/config.json must point at a chain-1 RPC. Pick the row that matches your setup:
| Your setup | rootChain URL |
|---|---|
| No chain-1 node on this server (most people) | https://root.net.app.canopynetwork.org, the default, so leave it as is |
A chain-1 node on this same server (canopy-network/node, run as-is) | http://node:50002 |
The URL must be scheme, host and port only: no path and no trailing slash. A path such as /rpc breaks the websocket subscription with Empty root chain info / not subscribed.
Details for the same-server setup
In step 7 you start with make node-up-local-rootchain, which joins the chain-1 node's Docker network. That's how node resolves to your chain-1 container. That network is named after the folder the chain-1 repo was cloned into (node_default for a plain git clone). If you cloned it elsewhere, set ROOT_CHAIN_NETWORK in .env. docker network ls shows the name.
Both repos ship the same monitoring stack (same container names, ports 80/443), so run only one per server. make node-up-local-rootchain starts just the node.
Using a chain-1 node on a different server (advanced)
The standard chain-1 compose doesn't publish its RPC port outside Docker, so http://<other-server>:50002 won't answer by default. To use it you have to publish port 50002 on that server and firewall it to this server's IP. Exposing the admin RPC there is a security risk. For most setups the public default is simpler and safe.
5. Restore from a snapshot (optional)
Syncing from genesis takes about 25 minutes. To start faster, restore the latest chain-29 snapshot (~800 MB) first. SNAPSHOT_URL is already set in .env.
make snapshot
Skip this step to sync from genesis instead.
6. Open the P2P port
Allow inbound TCP 9029 in the host firewall. With ufw:
sudo ufw allow 9029/tcp
Open it in your provider's firewall too (the cloud panel's security group or firewall), if it has one.
7. Start the node
If a chain-1 node runs on this same server:
make node-up-local-rootchain
Otherwise (the usual case):
make node-up
The compose service is now named canoliq-node (it was node; the container name is unchanged). After git pull, recreate it once with docker compose down && docker compose up -d. Chain data in ./data is kept. A validator is offline for a few seconds.
If you edited config/config.json, run git stash && git pull && git stash pop so your own rootChain survives the pull.
8. Verify
Run each check on its own.
8a. Is the node at the latest height? The height should climb to the current block:
docker exec canoliq-node wget -qO- --post-data='{}' --header='Content-Type: application/json' http://127.0.0.1:50002/v1/query/height
8b. Is the plugin in canoLiq mode? Expect starting plugin in 'canoliq' mode (liquid staking):
docker exec canoliq-node head -2 /tmp/plugin/go-plugin.log
8c. Is the node ready? Expect "genesisComplete": true and "chainId": 29:
docker exec canoliq-node wget -qO- http://127.0.0.1:8587/v1/health
8d. Is the P2P port open and reachable? Run this from another machine (a remote session, not this server):
nc -z <your-host> 9029
If the node has been running for more than 25 minutes and the height check (8a) is stuck:
-
at block 0: the node isn't following chain 1. Check
rootChain(step 4). This shows the root-chain errors:docker logs canoliq-node 2>&1 | grep -i root | tail -
at block 2 or 4679 with
unequal block hashin the logs: the config was changed. Use the repo'sconfig/files as-is (see Troubleshooting).
Optional: open the node's wallet page
The node includes a web wallet and a block explorer. They aren't exposed directly: the repo serves them through Caddy, which is part of the monitoring stack, at https://wallet.<your-DOMAIN> behind a username and password. You don't need the wallet page to run a node.
Before you start, check that:
- this server runs only the canoLiq node. If another web server already uses ports 80/443 (a chain-1 node's monitoring stack, Traefik, nginx...), see the note at the end of this section;
DOMAINin.envis a hostname, such asnode.example.com, and not an IP address.wallet.<DOMAIN>has to be a real hostname.
1. Add a DNS record. Create an A record for wallet.<your-DOMAIN> pointing to this server's IP. Use a plain DNS record, not proxied (in Cloudflare, the grey cloud).
2. Open ports 80 and 443. Caddy uses them to get the HTTPS certificate automatically. Open them in the host firewall and in your provider's firewall.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
3. Set your own password. The default login (canopy / canopy) is public, so don't skip this step. In the canoliq-node folder, run:
make hash-password
Type the password you want. It prints a line that starts with AUTH_PASSWORDHASH=. Paste that whole line into .env, replacing the existing one, and set your username there as well:
AUTH_USER=<your-username>
4. Start the node with the monitoring stack (Caddy included):
make up
5. Open the page. Go to https://wallet.<your-DOMAIN> and log in with your username and password.
What the password protects: the wallet page and every admin call (keys, sending transactions). The read-only RPC at /rpc stays open, which the page needs.
This setup doesn't work out of the box there: the second Caddy can't take ports 80/443, and the monitoring container names (prometheus, grafana...) collide. Run the canoLiq node alone (make node-up or make node-up-local-rootchain). To stake CNLQ on chain 29, use FleetWallet, which supports chain 29 directly, so you don't need the node's wallet page. Ask in Discord if you need the page on a shared server.
Troubleshooting
| Symptom | Cause |
|---|---|
Height stays at 0, genesisComplete: false | rootChain doesn't reach a chain-1 node. See step 4. |
Stuck at block 2 with unequal block hash | The config is wrong, most likely missing chain 29's emission parameters. Use the repo's config/config.json as-is. |
Stuck at block 4679 with unequal block hash | The canoLiq plugin config is missing or wrong. The files in config/canoliq/ are consensus-critical and must be byte-identical to the repo's. |
Restart loop, canopy binary not found, after wiping data/ | Recreate the container: docker compose up -d --force-recreate canoliq-node. |
| Dials peers on port 9058 | A dial peer or validator address has :9029 in it. Canopy adds the chain ID to any port you give, so drop the port. |
| No peers | Port 9029 isn't reachable from outside, or DOMAIN isn't set. |
The canoLiq plugin updates itself from Canoliq/canoliq releases. Keep auto-update on: consensus changes ship there, pinned to a future block height, and every node must run them before that height.