Adding an orderer to a Hyperledger Fabric channel expands the ordering set that can receive transactions and replicate channel blocks. The change is useful when an ordering service needs another node for capacity, location, or fault tolerance without recreating the channel.
A new ordering service node joins the channel through the channel participation API with an up-to-date config block. At that point it can track the channel as a follower, but it does not become a voting consenter until the channel configuration includes its endpoint and consensus identity.
Use a channel admin identity that can satisfy the channel modification policy, plus the new orderer's admin TLS client certificate. BFT channels need endpoint, consenter mapping, and BlockValidation policy changes; a Raft channel uses the Raft consenter TLS certificate list instead, and either consensus type should add or remove one orderer at a time so the existing cluster keeps quorum.
$ export CHANNEL_ID=mychannel $ export EXISTING_ORDERER=orderer.example.com:7050 $ export EXISTING_ORDERER_HOST=orderer.example.com $ export NEW_ORDERER=orderer5.example.com:7060 $ export NEW_ORDERER_HOST=orderer5.example.com $ export NEW_ORDERER_ADMIN=orderer5.example.com:7061 $ export ORDERER_CA=/etc/hyperledger/fabric/tls/orderer-ca.crt $ export OSN_TLS_CA_ROOT_CERT=/etc/hyperledger/fabric/tls/admin-ca.crt $ export ADMIN_TLS_SIGN_CERT=/etc/hyperledger/fabric/admin/client.crt $ export ADMIN_TLS_PRIVATE_KEY=/etc/hyperledger/fabric/admin/client.key
The new orderer must already be running with its own MSP, TLS key pair, cluster listener, admin TLS listener, and channel participation enabled. Do not reuse another orderer's MSP or TLS private key.
$ peer channel fetch config config_block.pb \ -o "$EXISTING_ORDERER" \ --ordererTLSHostnameOverride "$EXISTING_ORDERER_HOST" \ -c "$CHANNEL_ID" \ --tls --cafile "$ORDERER_CA" INFO [channelCmd] readBlock -> Received block: 3
$ osnadmin channel join \
-o "$NEW_ORDERER_ADMIN" \
--ca-file "$OSN_TLS_CA_ROOT_CERT" \
--client-cert "$ADMIN_TLS_SIGN_CERT" \
--client-key "$ADMIN_TLS_PRIVATE_KEY" \
--channelID "$CHANNEL_ID" \
--config-block config_block.pb
Status: 201
{
"name": "mychannel",
"url": "/participation/v1/channels/mychannel",
"consensusRelation": "follower",
"status": "onboarding",
"height": 0
}
A follower orderer can receive channel blocks before it is added to the consenter set. If the output already shows consenter, the channel config block already includes this orderer.
$ configtxlator proto_decode \ --input config_block.pb \ --type common.Block \ --output config_block.json
$ jq '.data.data[0].payload.data.config' \ config_block.json > original_config.json
The JSON edit uses jq only to extract and wrap Fabric config JSON. Install it on the admin workstation before building the update envelope.
$ cp original_config.json modified_config.json
| BFT channel config item | Value to add for the new orderer |
|---|---|
| Orderer organization Endpoints | orderer5.example.com:7060 |
| Orderer Orderers.value.consenter_mapping | New id, host, port, msp_id, enrollment identity PEM, client TLS cert PEM, and server TLS cert PEM. |
| Orderer BlockValidation policy | The new orderer identity and the updated threshold rule required by the channel policy. |
Channel config JSON stores certificate bytes, not local filesystem paths. Add the PEM content that belongs to the new orderer, and keep the BFT id unique inside the channel.
$ configtxlator proto_encode \ --input original_config.json \ --type common.Config \ --output original_config.pb
$ configtxlator proto_encode \ --input modified_config.json \ --type common.Config \ --output modified_config.pb
$ configtxlator compute_update \ --channel_id "$CHANNEL_ID" \ --original original_config.pb \ --updated modified_config.pb \ --output config_update.pb
$ configtxlator proto_decode \ --input config_update.pb \ --type common.ConfigUpdate \ --output config_update.json
$ jq -n \
--arg channel "$CHANNEL_ID" \
--slurpfile update config_update.json \
'{"payload":{"header":{"channel_header":{"channel_id":$channel,"type":2}},"data":{"config_update":$update[0]}}}' \
> config_update_in_envelope.json
$ configtxlator proto_encode \ --input config_update_in_envelope.json \ --type common.Envelope \ --output envelope.pb
$ peer channel signconfigtx -f envelope.pb
Run the signing command after loading every peer or orderer admin MSP required by the channel modification policy. The final submitter signs automatically when it sends the update.
$ peer channel update \ -o "$EXISTING_ORDERER" \ --ordererTLSHostnameOverride "$EXISTING_ORDERER_HOST" \ -c "$CHANNEL_ID" \ -f envelope.pb \ --tls --cafile "$ORDERER_CA" INFO [channelCmd] InitCmdFactory -> Endorser and orderer connections initialized INFO [channelCmd] update -> Successfully submitted channel update
$ osnadmin channel list \
-o "$NEW_ORDERER_ADMIN" \
--ca-file "$OSN_TLS_CA_ROOT_CERT" \
--client-cert "$ADMIN_TLS_SIGN_CERT" \
--client-key "$ADMIN_TLS_PRIVATE_KEY" \
--channelID "$CHANNEL_ID"
Status: 200
{
"name": "mychannel",
"url": "/participation/v1/channels/mychannel",
"consensusRelation": "consenter",
"status": "active",
"height": 4
}
consenter and active show that the channel config update promoted the new orderer from follower tracking to active ordering membership.
$ peer channel fetch newest newest_block.pb \ -o "$NEW_ORDERER" \ --ordererTLSHostnameOverride "$NEW_ORDERER_HOST" \ -c "$CHANNEL_ID" \ --tls --cafile "$ORDERER_CA" INFO [channelCmd] readBlock -> Received block: 4
A successful block fetch from orderer5.example.com:7060 proves the new orderer is serving the channel from its client-facing endpoint.
$ rm -f config_block.pb config_block.json original_config.json modified_config.json \ original_config.pb modified_config.pb config_update.pb config_update.json \ config_update_in_envelope.json envelope.pb newest_block.pb