How to run a full node for an Arbitrum chain
This page assumes you've completed the steps from the Start here page. If you haven't, you'll need to do so, as it gathers RPC endpoints, Nitro version, database snapshots, and other information required to run the node.
Putting it into practice: run a node
If you are running more than one node, you should run a feed relay.
To ensure the database persists across restarts, mount an external volume to /home/user/.arbitrum inside the Docker container. Make sure to:
- Create the host directory before running Docker (e.g.,
mkdir -p /some/local/dir/arbitrum), otherwise Docker may create it asroot, and the container (which runs as UID 1000) won't be able to write to it. - If you encounter permission errors on Linux or macOS, run
chmod -fR 777 /some/local/dir/arbitrumon the host directory.
If using a node-config.json file with Docker to mount, use the following command:
docker run --rm -it -v /Path/to/mount/arbitrum:/home/user/.arbitrum -v /Path/to/node-config.json:/home/user/.arbitrum/node-config.json -p 0.0.0.0:8450:8450 offchainlabs/nitro-node:v3.9.9-6b0af88 --conf.file /home/user/.arbitrum/node-config.json
- Here is an example of how to run
nitro-node:
- Arbitrum One, Nova, Sepolia
- Arbitrum chains
docker run --rm -it -v /some/local/dir/arbitrum:/home/user/.arbitrum -p 0.0.0.0:8547:8547 -p 0.0.0.0:8548:8548 offchainlabs/nitro-node:v3.9.9-6b0af88 --parent-chain.connection.url=<Ethereum RPC URL> --parent-chain.blob-client.beacon-url=<Ethereum beacon chain RPC URL> --chain.id=<Arbitrum chain id> --init.latest=pruned --http.api=net,web3,eth --http.corsdomain=* --http.addr=0.0.0.0 --http.vhosts=*
docker run --rm -it -v /some/local/dir/arbitrum:/home/user/.arbitrum -p 0.0.0.0:8547:8547 -p 0.0.0.0:8548:8548 offchainlabs/nitro-node:v3.9.9-6b0af88 --parent-chain.connection.url=<Parent chain RPC URL> --chain.info-json=<Orbit chain's info> --chain.name=<Orbit chain name> --node.feed.input.url=<Sequencer feed url> --execution.forwarding-target=<Sequencer node endpoint url> --http.api=net,web3,eth --http.corsdomain=* --http.addr=0.0.0.0 --http.vhosts=*
- You can see an example of
--chain.info-jsonin the section above.
-
Note that it is important that
/some/local/dir/arbitrumalready exists; otherwise, the directory might be created withrootas owner, and the Docker container won't be able to write to it. -
Note that if you are running a node for the parent chain (e.g., Ethereum for Arbitrum One or Nova) on localhost, you may need to add
--network hostright afterdocker runto use Docker host-based networking -
When shutting down the Docker image, it is important to allow a graceful shutdown to save the current state to disk. Here is an example of how to do a graceful shutdown of all Docker images currently running
docker stop --time=1800 $(docker ps -aq)
Important ports
| Protocol | Default port |
|---|---|
RPC/http | 8547 |
RPC/websocket | 8548 |
Sequencer Feed | 9642 |
- Please note: the
RPC/websocketprotocol requires some ports to be enabled, you can use the following flags:--ws.port=8548--ws.addr=0.0.0.0--ws.origins=\*
Note on permissions
-
The Docker image is configured to run as non-root UID 1000. This configuration means if you are running in Linux or OSX and you are getting permission errors when trying to run the Docker image, run this command to allow all users to update the persistent folders:
mkdir /data/arbitrumchmod -fR 777 /data/arbitrum
Watchtower mode
- By default, the full node runs in Watchtower mode, meaning that it watches the onchain assertions and, if it disagrees with them, logs an error containing the string
found incorrect assertion in watchtower mode. For a BoLD-enabled chain like Arbitrum One or Arbitrum Nova if you are running Nitro before v3.6.0, the--node.bold.enable=trueflag should be set to ensure your node can monitor for onchain assertions properly. - Setting this flag is not required as your node will continue to operate correctly, validate the Arbitrum One/Nova chain, and serve RPC requests as usual, regardless of this flag.
- Note that watchtower mode adds a small amount of execution and memory overhead. You can deactivate this mode using the parameter
--node.staker.enable=false.
Pruning
By default, all node types will use HashDB, go-ethereum's original state trie storage scheme. This will be the case until a path-based state scheme (e.g., PathDB) is supported across all Arbitrum node types. This means that, by default, pruning will continue to be an manual opt-in operation, should an operator decide to do so. As a refresher, pruning a full node means removing older, unnecessary data from the local copy of the blockchain the node maintains, thereby saving disk space and slightly improving the node's efficiency. Pruning will remove all states from blocks older than the latest 128.
For nodes using PathDB, pruning will be automatic, can be done online, and defaults to retaining only the last 24 hours' worth of state history on disk.
The pruning process occurs when the node starts (upon initialization) and will not serve RPC requests during pruning.
If you are using the default storage scheme (HashDB), then you can activate pruning by using the parameter:
--init.prune <pruning mode>, where<pruning mode>can be one of:minimal: The most aggressive prune and retains only the genesis state and the head state (at the latest snapshot). Takes the least amount of time to complete. Duration depends on the chain and database size (several hours for smaller chains; potentially days for large chains like Arbitrum One).full: Mostly intended for full nodes serving RPC requests, this mode retains the genesis state, the state of the latest confirmed block, and the head state (at the latest snapshot). Will not work if the node is invalidatormode. Duration varies significantly by chain and database size—for Arbitrum One, this may take multiple days on NVMe SSDs. For smaller chains, it will be much faster. If pruning takes too long, consider downloading a fresh pruned snapshot with--init.latest prunedinstead.validator: Meant to be used by validator nodes and requires an RPC URL for L1 Ethereum. This mode retains the genesis state, the state of the latest confirmed block, the latest confirmed assertion root (obtained from L1), the last locally validated block root, and the head state (at the latest snapshot). This mode is expected to take longer thanfullpruning mode.
Memory management
Under heavy RPC load or during operations such as large debug_traceBlockByNumber calls, Nitro nodes can consume significant memory. To prevent out-of-memory (OOM) crashes, consider configuring the following:
--node.resource-mgmt.mem-free-limit: Declines incoming RPC requests when free system memory drops below the specified threshold (e.g.,--node.resource-mgmt.mem-free-limit=4GiB). This helps protect the node from OOM under high request load.GOMEMLIMITenvironment variable: Sets a soft memory limit for the Go runtime garbage collector (e.g.,GOMEMLIMIT=48GiBfor a 64 GB machine), helping reduce memory spikes.
For Docker deployments, you can set these in your docker run command:
docker run ... -e GOMEMLIMIT=48GiB ... offchainlabs/nitro-node:... --node.resource-mgmt.mem-free-limit=4GiB ...
Transaction prechecker
- Enabling the transaction prechecker will add extra checks before your node forwards
eth_sendRawTransactionto the Sequencer endpoint. - Below, we list the flags to set up the prechecker:
| Flag | Description |
|---|---|
--execution.tx-pre-checker.strictness | How strict to be when checking transactions before forwarding them. 0 = accept anything, 10 = should never reject anything that'd succeed, 20 = likely won't reject anything that'd succeed, 30 = full validation which may reject transactions that would succeed (default 20) |
--execution.tx-pre-checker.required-state-age | How long ago should the storage conditions from eth_SendRawTransactionConditional be true, 0 = don't check old state (default 2) |
--execution.tx-pre-checker.required-state-max-blocks | Maximum number of blocks to look back while looking for the <required-state-age> seconds old state, 0 = don't limit the search (default 4) |
Optional parameters
Below, we listed the most commonly used parameters when running a node. You can also use the flag --help for a comprehensive list of the available parameters.
| Flag | Description |
|---|---|
--http.api | Offers APIs over the HTTP-RPC interface. Default: net,web3,eth,arb. Add debug for tracing. |
--http.corsdomain | Accepts cross-origin requests from these comma-separated domains (browser enforced). |
--http.vhosts | Accepts requests from these comma-separated virtual hostnames (server enforced). Default: localhost. Accepts *. |
--http.addr | Sets the address to bind RPC to. May require 0.0.0.0 for Docker networking. |
--execution.caching.archive | Retains past block state. For archive nodes. |
--execution.caching.state-scheme | Default: hash. This flag sets the schema used in Nitro’s state trie, inherited from Geth. Teams may choose to set this to the path to enable PathDB, but note that PathDB is not yet supported for validators. |
--execution.caching.state-history | Default: 345600 blocks, calculated as 24 hours worth of blocks at the default block speed of 250ms. Number of recent blocks of state history to retain on disk. Set to 0 for unlimited (i.e. Archive nodes). Only available for PathDB enabled nodes |
--execution.caching.pathdb-max-diff-layers | Default: 128 layers. Maximum number of diff layers kept in the node's memory before flushing to disk. Increasing the number of diff layers may cause the node to fall behind the chain head during busy periods since doing so slows down block processing speed and reduces sync speed. This configuration is primarily used to improve performance of shallow re-orgs (which are a concern on Ethereum but not on Arbitrum chains) and for efficient access to recent state. |
--node.feed.input.url=<feed address> | Sets the sequencer feed address to this URL. Default: wss://<chainName>.arbitrum.io/feed. ⚠️ One feed relay per datacenter is advised. See feed relay guide. |
--execution.forwarding-target=<RPC> | Sets the sequencer endpoint to forward requests to. |
--execution.rpc.evm-timeout | Default: 5s. Timeout for eth_call. (0 == no timeout). |
--execution.rpc.gas-cap | Default: 50000000. Gas cap for eth_call/estimateGas. (0 = no cap). |
--execution.rpc.tx-fee-cap | Default: 1. Transaction fee cap (in ether) for RPC APIs. (0 = no cap). |
--execution.tx-lookup-limit | Default: 126230400, ~1 year worth of blocks at 250ms/block. Maximum number of blocks from head whose transaction indices are reserved (for example, eth_getTransactionReceipt and eth_getTransactionByHash only return results for indexed transactions). Set to 0 to index transactions for all blocks. Changing this parameter reindexes all missing transactions without the need to resync the chain. |
--execution.rpc.classic-redirect=<RPC> | (Arbitrum One only) Redirects archive requests for pre-nitro blocks to this RPC of an Arbitrum Classic node with archive database. |
--node.resource-mgmt.mem-free-limit | Declines incoming RPC requests when free system memory (excluding page cache) drops below this threshold. Accepts values with suffixes like 4GiB, 512MiB. Helps prevent OOM crashes under heavy load. |
--ipc.path | Filename for IPC socket/pipe within datadir. 🔉 Not supported on macOS. The path is within the Docker container. |
--init.prune | Prunes the database before starting the node. Can be "full" or "validator". |
--init.url="<snapshot file>" | (Required for Arbitrum One) URL to download the genesis database from. Only required for Arbitrum One nodes, when running them for the first time. See the Nitro database snapshots guide for more information. |
--init.download-path="/path/to/dir" | Temporarily saves the downloaded database snapshot. Defaults to /tmp/. Used with --init.url. |
--init.latest | Searches for the latest snapshot of the given kind (accepted values: archive, pruned, genesis) |
--init.latest-base | Base URL used when searching for the latest snapshot. Default: "https://snapshot.arbitrum.foundation/". If you're running an Arbitrum chain, ask the chain owner for this URL. |
--init.then-quit | Allows any --init.* parameters to complete, and then the node automatically quits. It doesn't initiate pruning by itself but works in conjunction with other --init.* parameters, making it easier to script tasks like database backups after initialization processes finish. |