Every Lightning node operator ends up in the same place sooner or later. You open a few channels, route some payments, and a month later half your channels are full on your side and the other half are empty. Money is sitting in the wrong place.
You can close and reopen, which costs on chain fees and time. Or you can move the liquidity around inside the network and pay only a routing fee. That second option is called a circular rebalance, and I added a small tool for it to NimbleNode, which makes possible to do that without headaches and it is available inside the nimblenode CLI.
The problem: channels that drift
A channel has two sides: what you can send (local balance) and what you can receive (remote balance). Payments flowing through your node push those numbers around. A channel that is 95% local can’t receive anything. One that is 5% local can’t send anything. Either way it’s not doing its job.
What a circular rebalance actually does
The idea is simple. Your node pays an invoice that it issued itself. The payment leaves through the channel you want to drain (call it FROM), travels through the network, and comes back in through the peer of the channel you want to fill (call it TO).
Local balance goes down on FROM and up on TO. The only cost is the routing fee for the round trip. No channel is closed and nothing leaves your node in the end.
The trick: pinning both ends
If you just asked LND to pay yourself, it would pick the cheapest route and that route might not touch the channels you care about. Two constraints make the whole thing work.
Outgoing channel
The tool forces the payment to leave through the FROM channel, using --outgoing_chan_id. That decides where the liquidity comes out.
Last hop
It also pins the final hop to the TO peer with --last_hop, so the payment comes back through the channel you chose. That decides where the liquidity goes in.
Before it sends anything, the tool also looks at the fee policy your TO peer has set on that channel and adds the cost of that last hop to the estimate. Skip that and your cost estimate is always a bit too optimistic.
The workflow: list, test, run
There are three modes.
./scripts/nimblenode rebalance list
./scripts/nimblenode rebalance test -f bfx-lnd0 -t kraken -a 1m
./scripts/nimblenode rebalance run -f bfx-lnd0 -t kraken -a 1m
list shows every channel with capacity, local, remote, percentage local and status, sorted by local balance. It also gives you the totals. It’s the picture you look at before deciding anything.
test is a dry run. It estimates the path and the cost, shows you the hops with node aliases, and checks the preconditions. FROM must have enough spendable outbound to cover the amount plus the estimated fee, reserve included. TO must have enough inbound for the amount, reserve included. Both channels must be online. You get TEST OK or TEST FAILED, and no funds move either way.
run does the real thing. It asks for confirmation unless you pass -y, and without a terminal it requires -y.
Options worth knowing
You can refer to a channel by its numeric short channel ID, the channel ID, the peer pubkey, or even part of an alias. If an alias matches more than one channel, the tool lists the candidates and stops instead of guessing.
Amounts take suffixes, so 500k and 3m work. The fee cap defaults to 1000 ppm of the amount, which is 0.1%. You can change it with -p, or set an absolute cap in sats with -F. If the estimated route costs more than the cap, nothing is sent. The payment timeout defaults to 120 seconds and can be changed with -T.
It needs docker and jq. By default it talks to LND through docker exec lit /app/lncli, and you can point it somewhere else with the NN_LNCLI variable.
Exit codes are meant for scripting. Zero is success, 1 is an error or a cancelled run, 2 is wrong usage, 3 means no route or failed preconditions with nothing sent, and 4 means the payment failed.
There are a few sanity checks built in. It refuses FROM equal to TO. It also refuses two channels that share the same peer, because the last hop could not tell them apart and the payment might come back through the one you just drained. If your TO peer has several channels with you, it warns that the payment may land on a different one.
Why success is judged from the invoice
After the payment, the tool does not read the text that payinvoice prints. That output is written for humans and it can change between LND versions. Instead it checks the state of the invoice. If the invoice is SETTLED, the rebalance happened. If not, it says clearly that no funds moved and shows the status, the failure reason and the last lines of the lncli output.
On success you get the amount moved, the fee paid, and the local balances before and after on both channels. That last part is what I actually want to see.
What it does not do at the moment
This is the part I want to be honest about, because someone asked me about it recently. The question was whether the CLI picks the circular route itself or whether you choose the channels, and the observation that fee cost against liquidity gained looks like the hard part to automate.
That person is right. You choose the channels. The tool does not search for the cheapest loop and it does not decide whether a given loop is worth it. What it gives you is the exact cost of the loop you picked, before you commit, and a hard ceiling on what you will pay.
I left that out on purpose. Deciding which pairs to balance means having a policy: a target balance for each channel, a maximum ppm you are willing to pay, maybe different rules for different peers. That’s a real design decision about how you run your node, and I’d rather not bake in my own preferences. If you want it, it can be built on top, since the exit codes and the test mode make it easy to script.
The bottom line
A rebalance tool should do the boring and error prone parts and leave the decisions alone. Route constraints, fee estimation, liquidity checks with reserves, and a reliable success signal are all mechanical. Choosing which channels deserve your sats is not.
The code is open source. You will find it in the NimbleNode repo at github.com/massmux/nimblenode, in scripts/rebalance (commit c102085, “Added rebalance tool”). Try list and test first, since they cost nothing. If you run a node and have ideas for the policy side, tell me how you would want it to work.
Checkout on nimblenode.dev if you want to test on a preinstalled VPS, that is powered by denali.pro


