> ## Documentation Index
> Fetch the complete documentation index at: https://optimism-373f39ad-soyboy-docs-deploy-a-contract.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Building and running an OP Stack node from source

> Build and run an OP Stack node (op-reth + op-node) from source code for full nodes and archive nodes.

<Warning>
  **op-geth reaches end-of-support on 2026-05-31 and will not support the L1 Glamsterdam hardfork.** This tutorial builds and runs op-reth (the primary supported execution client) and Nethermind. op-geth instructions are retained in a legacy section at the bottom for operators mid-migration. See the [op-geth deprecation notice](/notices/op-geth-deprecation) for the full migration plan.
</Warning>

This tutorial walks through the full process of building and running an OP Stack node from source — op-node plus an execution client. Building from source is a flexible alternative to using pre-built Docker images and is useful if you need a specific architecture or want to inspect what you're running.

For permissionless chains setting up historical proofs for withdrawal proving, follow this tutorial first to get a working node, then continue with [Running op-reth with Historical Proofs](/node-operators/tutorials/reth-historical-proofs).

## Hardware requirements

Hardware requirements for OP Mainnet nodes can vary depending on the type of node you plan to run.
Archive nodes generally require significantly more resources than full nodes.
Below are suggested minimum hardware requirements for each type of node.

* 16GB RAM
* Reasonably modern CPU

### SSD capacity requirements

Given the growing size of the blockchain state, choosing the right SSD size is important.
Below are the storage needs as of June 2025:

* **Full Node:** The snapshot size for a full node is approximately 700GB, with the data directory's capacity increasing by about 100GB every six months.
* **Archive Node:** The snapshot size for an archive node is approximately 14TB, with the data directory's capacity increasing by about 3.5TB every six months. A local SSD with a NVME interface is recommended for archive nodes.

Plan for future storage needs and choose SSDs that can handle these increasing requirements.

## Software dependencies

The build environment is managed through [mise](https://mise.jdx.dev/), which installs and manages the toolchains (Rust, Go) needed to build the monorepo. The mise configuration lives in [`mise.toml`](https://github.com/ethereum-optimism/optimism/blob/develop/mise.toml) at the monorepo root.

## Build op-node and the execution client

The Optimism Monorepo does **coordinated releases** — each release commit is tagged simultaneously across components (op-node, op-reth, kona-node, etc.). Pinning to one release tag gives you a known-good combination of all of them built from the same source state. Look up the latest tags on the [Optimism releases page](https://github.com/ethereum-optimism/optimism/releases).

<Steps>
  <Step title="Clone the Optimism Monorepo">
    The monorepo contains the source for both `op-node` and `op-reth`.

    ```bash theme={null}
    git clone https://github.com/ethereum-optimism/optimism.git
    cd optimism
    ```
  </Step>

  <Step title="Pin to a release commit">
    Check out a recent op-node release tag — the same commit carries the matching op-reth tag. For example, `op-node/v1.18.2` is co-tagged with `op-reth/v2.2.4`:

    ```bash theme={null}
    git checkout op-node/v1.18.2
    ```

    <Info>
      `op-reth/v2.2.3` or later is required to enable the historical proof store v2 (`--proofs-history.storage-version=v2`). The op-node/v1.18.x release line carries op-reth ≥ v2.2.3, so any of those tags satisfies that requirement.
    </Info>
  </Step>

  <Step title="Build op-node">
    ```bash theme={null}
    cd op-node && just && cd ..
    ```

    Binary: `op-node/bin/op-node` (relative to the monorepo root).
  </Step>

  <Step title="Build the execution client">
    <Tabs items={['op-reth', 'Nethermind']}>
      <Tab title="op-reth">
        op-reth lives inside the monorepo at `rust/op-reth/` — no second `git clone` needed.

        ```bash theme={null}
        cd rust/op-reth && cargo build --release --bin op-reth && cd ../..
        ```

        Binary: `rust/target/release/op-reth` (relative to the monorepo root). `rust/` is a Cargo workspace, so the `target/` directory lives at the workspace root, not inside the individual crate. Build outputs are gitignored, so they persist across any future `git checkout`.
      </Tab>

      <Tab title="Nethermind">
        `Nethermind` is an alternative execution client written in .NET. **Prerequisites:** [.NET SDK](https://aka.ms/dotnet/download) 9 or later.

        ```bash theme={null}
        git clone --recursive https://github.com/nethermindeth/nethermind.git
        dotnet build nethermind/src/Nethermind/Nethermind.sln -c release
        ```

        Build artifacts: `nethermind/src/Nethermind/artifacts/bin/Nethermind.Runner/release/`. See the [Nethermind documentation](https://docs.nethermind.io/get-started/running-node/) for more.
      </Tab>
    </Tabs>
  </Step>
</Steps>

## Assess blob archiver

Assess if you need to configure a blob archiver service by reading [Configure a Blob Archiver](/node-operators/guides/management/blobs#configure-a-blob-archiver).

## Create a JWT secret

The execution client and `op-node` communicate over the engine API authrpc, secured with a shared 32-byte hex secret. Both binaries must read the **same** secret file. The commands below use a relative path (`./jwt.txt`) for each binary, so the same content must exist at both launch directories.

<Steps>
  <Step title="Generate the secret">
    From the monorepo root:

    ```bash theme={null}
    openssl rand -hex 32 > jwt.txt
    ```

    This creates `optimism/jwt.txt`.
  </Step>

  <Step title="Copy to the op-node launch directory">
    ```bash theme={null}
    cp jwt.txt op-node/jwt.txt
    ```
  </Step>

  <Step title="Copy to the op-reth launch directory">
    The op-reth binary lives at `rust/target/release/op-reth` and the tutorial launches it from `rust/`, so place the JWT there:

    ```bash theme={null}
    cp jwt.txt rust/jwt.txt
    ```
  </Step>
</Steps>

The two copies must remain identical — if you regenerate one side, copy it over to the other before restarting.

## Start the execution client

It's generally easier to start the execution client before `op-node`. The EL will simply not receive any blocks until `op-node` is started.

<Tabs items={['op-reth', 'Nethermind']}>
  <Tab title="op-reth">
    <Steps>
      <Step title="Navigate to the rust workspace directory">
        op-reth is built into the workspace target dir, so launch from `optimism/rust/`:

        ```bash theme={null}
        cd /path/to/optimism/rust
        ```

        The binary is then at `./target/release/op-reth`.
      </Step>

      <Step title="Set environment variables">
        ```bash theme={null}
        export DATADIR_PATH=... # Path to the desired data directory for op-reth
        ```
      </Step>

      <Step title="Start op-reth">
        <Info>
          For archive-node configuration (historical state for withdrawal proving), see the [OP Mainnet archive nodes](#op-mainnet-archive-nodes) section.
        </Info>

        The JSON-RPC API will become available on port 8545. See the [op-reth configuration reference](/node-operators/reference/op-reth-config) for the full flag set.

        ```bash theme={null}
        ./target/release/op-reth node \
          --chain=optimism_sepolia \
          --datadir=$DATADIR_PATH \
          --http \
          --ws \
          --authrpc.jwtsecret=./jwt.txt \
          --rollup.sequencer=https://sepolia-sequencer.optimism.io
        ```

        For OP Mainnet, set `--chain=optimism` and `--rollup.sequencer=https://mainnet-sequencer.optimism.io`.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Nethermind">
    <Steps>
      <Step title="Navigate to your Nethermind directory">
        Find the directory where you built the `Nethermind` binary.
      </Step>

      <Step title="Start Nethermind">
        <Info>
          For an archive node, use the `op-sepolia_archive` configuration instead of `op-sepolia`. For OP Mainnet, use `op-mainnet` or `op-mainnet_archive` respectively.
        </Info>

        The JSON-RPC API will become available on port 8545. See the [execution clients configuration guide](/node-operators/guides/configuration/execution-clients) for more options.

        ```bash theme={null}
        ./Nethermind.Runner \
          -c op-sepolia \
          --data-dir path/to/data/dir \
          --JsonRpc.JwtSecretFile=./jwt.txt
        ```

        This uses the built-in `op-sepolia` configuration which includes JSON-RPC endpoints, network ports, sequencer URL, and other OP Stack-specific settings.
      </Step>
    </Steps>
  </Tab>
</Tabs>

## Start op-node

Once your execution client is running, start `op-node`. It will connect to the EL and begin synchronizing the chain.

<Steps>
  <Step title="Navigate to the op-node directory">
    op-node is built into its own crate directory, so launch from `optimism/op-node/`:

    ```bash theme={null}
    cd /path/to/optimism/op-node
    ```

    The binary is then at `./bin/op-node`.
  </Step>

  <Step title="Set environment variables">
    ```bash theme={null}
    export L1_RPC_URL=...  # URL for the L1 node. Local default: http://127.0.0.1:8545
    export L1_RPC_KIND=... # alchemy, quicknode, infura, parity, nethermind, debug_geth, erigon, basic, any
    export L1_BEACON_URL=... # URL for the L1 Beacon HTTP endpoint. Local default: http://127.0.0.1:3500
    ```
  </Step>

  <Step title="Start op-node">
    <Info>
      The `op-node` RPC should not be exposed publicly. If left exposed, it could accidentally expose admin controls to the public internet.
    </Info>

    <Info>
      `--syncmode=execution-layer` enables [snap sync](/node-operators/guides/management/snap-sync), which works for both `op-reth` and `Nethermind` and removes the need to initialize the node with a data directory.
    </Info>

    <Info>
      `--l2.enginekind=reth` is required when pairing with op-reth. The binary still defaults to `geth` for backward compatibility — set `--l2.enginekind=geth` if you are still running op-geth.
    </Info>

    ```bash theme={null}
    ./bin/op-node \
      --l1=$L1_RPC_URL \
      --l1.rpckind=$L1_RPC_KIND \
      --l1.beacon=$L1_BEACON_URL \
      --l2=ws://localhost:8551 \
      --l2.jwt-secret=./jwt.txt \
      --network=op-sepolia \
      --syncmode=execution-layer \
      --l2.enginekind=reth
    ```

    <Info>
      Some L1 nodes (e.g. Erigon) do not support `eth_getProof`. Add `--l1.trustrpc` if your L1 doesn't support it — this means op-node trusts the L1 node to provide correct data.
    </Info>
  </Step>
</Steps>

## Synchronization verification

Once the EL and op-node are running, you should see them begin to communicate and synchronize.

### Snap sync (default)

Initial synchronization can take several hours. At the start of snap sync, `op-node` will log:

```text theme={null}
INFO [03-06|10:56:55.602] Starting EL sync
INFO [03-06|10:56:55.615] Sync progress                            reason="unsafe payload from sequencer while in EL sync" l2_finalized=000000..000000:0 l2_safe=000000..000000:0 l2_pending_safe=000000..000000:0 l2_unsafe=4284ab..7e7e84:117076319 l2_time=1,709,751,415 l1_derived=000000..000000:0
INFO [03-06|10:56:57.567] Optimistically inserting unsafe L2 execution payload to drive EL sync id=4ac160..df4d12:117076320
```

`Starting EL sync` is shown once and the sync-progress / inserting logs repeat until done. `op-node` will log the following when finished:

```text theme={null}
lvl=info msg="Finished EL sync" sync_duration=23h25m0.370558429s finalized_block=0x4f69e83ff1407f2e2882f2526ee8a154ac326590799889cede3af04a7742f18d:116817417
```

The execution client logs its own header- and state-download progress in parallel:

<Tabs items={['op-reth', 'Nethermind']}>
  <Tab title="op-reth">
    `op-reth` logs sync stages (headers, bodies, execution, state root) with periodic progress lines. Monitor the op-reth logs to confirm it is advancing through stages alongside the op-node `Sync progress` lines.
  </Tab>

  <Tab title="Nethermind">
    Snap sync in `Nethermind` works by:

    1. Downloading only the leaf nodes of the state tree
    2. Generating intermediate nodes locally
    3. Verifying the state root matches

    This approach is up to 10 times faster than traditional full sync.
  </Tab>
</Tabs>

### Full sync

Full sync rebuilds the chain from genesis and can take days to weeks on mature networks. Most operators should use snap sync (above) or [bootstrap from a pre-synced snapshot](https://datadirs.optimism.io/). Full-sync configuration is client-specific:

<Tabs items={['op-reth', 'Nethermind']}>
  <Tab title="op-reth">
    op-reth's snap sync is the recommended initialization path. For alternative sync configurations, see the [op-reth configuration reference](/node-operators/reference/op-reth-config).
  </Tab>

  <Tab title="Nethermind">
    To use full sync with `Nethermind`, set:

    ```bash theme={null}
    ./Nethermind.Runner \
      -c op-sepolia \
      --Sync.SnapSync=false \
      --Sync.FastSync=false \
      --data-dir path/to/data/dir \
      --JsonRpc.JwtSecretFile=./jwt.txt
    ```

    Full sync will download and verify every block from genesis, which takes significantly longer than snap sync but provides the strongest security guarantees.
  </Tab>
</Tabs>

After the initial sync, `op-node` derives L1 batches into L2 blocks and feeds them to the execution client. You'll see logs like:

```text theme={null}
INFO [06-26|13:31:20.389] Advancing bq origin                      origin=17171d..1bc69b:8300332 originBehind=false
INFO [06-26|14:00:59.460] Sync progress                            reason="processed safe block derived from L1" l2_safe=7fe3f6..900127:4068014 l2_unsafe=7fe3f6..900127:4068014 l1_derived=6079cd..be4231:8301091
INFO [06-26|14:00:59.461] generated attributes in payload queue    txs=1  timestamp=1,673,564,098
INFO [06-26|14:00:59.463] inserted block                           hash=e80dc4..72a759 number=4,068,015 update_safe=true
```

The execution client logs its own block-import progress in parallel; refer to its documentation for log specifics.

## OP Mainnet archive nodes

<Info>
  You only need an archive node if you need historical state. Most node operators should default to full nodes.
</Info>

For op-reth, historical state for withdrawal proving is configured via either `--rpc.eth-proof-window` (no separate proofs database; size to your dispute-game cadence) or `--proofs-history` (bounded memory, larger storage). See:

* [Running op-reth with Historical Proofs](/node-operators/tutorials/reth-historical-proofs) — full setup for `--proofs-history` (v2) on op-reth v2.2.3+.
* [Archive node guide](/node-operators/guides/management/archive-node) — flag overview for both paths.
* [OP Mainnet snapshots](https://datadirs.optimism.io/) — pre-synced op-reth datadirs to bootstrap from.

### Legacy Geth (pre-Bedrock, optional)

Blocks and transactions included in OP Mainnet before the Bedrock Upgrade cannot be executed by modern OP Mainnet nodes. Modern nodes serve these blocks but cannot run stateful queries like `eth_call` against them. For complete archive coverage of pre-Bedrock OP Mainnet state, run a Legacy Geth (`l2geth`) node alongside your modern node. This is **only** relevant to OP Mainnet archive nodes; skip if running a full node or OP Sepolia.

<Steps>
  <Step title="Build l2geth">
    ```bash theme={null}
    git clone https://github.com/ethereum-optimism/optimism-legacy.git
    cd optimism-legacy/l2geth
    make
    ```
  </Step>

  <Step title="Download the legacy Geth data directory">
    Download the snapshot from [Legacy Geth Data Directory (2.9TB)](/node-operators/guides/management/snapshots#op-mainnet-legacy) and verify:

    ```bash theme={null}
    sha256sum mainnet-legacy-archival.tar.zst
    # Expected: 4adedb61125b81b55f9bdccc2e85092050c65ef2253c86e2b79569732b772829
    ```

    Then extract:

    ```bash theme={null}
    tar xvf mainnet-legacy-archival.tar.zst
    ```
  </Step>

  <Step title="Start l2geth">
    ```bash theme={null}
    USING_OVM=true \
      ETH1_SYNC_SERVICE_ENABLE=false \
      RPC_API=eth,rollup,net,web3,debug \
      RPC_ENABLE=true \
      RPC_PORT=8546 \
      ./build/bin/geth --datadir /path/to/l2geth-datadir
    ```
  </Step>
</Steps>

## op-geth (legacy — end of support 2026-05-31)

<Warning>
  op-geth support ends on **2026-05-31** and **will not support the Glamsterdam hardfork**. For new deployments, use op-reth (above). See the [op-geth deprecation notice](/notices/op-geth-deprecation) for the migration plan.
</Warning>

<Accordion title="Build and run op-geth (legacy)">
  Pair op-geth with op-node by setting `--l2.enginekind=geth` (the binary default) instead of `reth` in the op-node command above.

  ### Build op-geth

  <Steps>
    <Step title="Clone op-geth">
      ```bash theme={null}
      git clone https://github.com/ethereum-optimism/op-geth.git
      cd op-geth
      ```
    </Step>

    <Step title="Check out the required release branch">
      Check the [op-geth releases page](https://github.com/ethereum-optimism/op-geth/releases) for the correct branch.

      ```bash theme={null}
      git checkout <name of release branch>
      ```
    </Step>

    <Step title="Build op-geth">
      ```bash theme={null}
      make geth
      ```
    </Step>
  </Steps>

  ### Start op-geth

  For an archive node, also set `--gcmode=archive`.

  ```bash theme={null}
  ./build/bin/geth \
    --http \
    --http.port=8545 \
    --http.addr=localhost \
    --authrpc.addr=localhost \
    --authrpc.jwtsecret=./jwt.txt \
    --verbosity=3 \
    --rollup.sequencerhttp=https://sepolia-sequencer.optimism.io/ \
    --op-network=op-sepolia \
    --datadir=$DATADIR_PATH
  ```

  See the [op-geth configuration reference](/node-operators/reference/op-geth-config) for the full flag set.

  ### op-geth snap-sync stages

  op-geth's snap sync runs in two stages — header download then state download:

  ```text theme={null}
  lvl=info msg="Syncing beacon headers" downloaded=116775778 left=1162878 eta=53.182s
  ```

  ```text theme={null}
  lvl=info msg="Syncing: state download in progress" synced=99.75% state="191.33 GiB" accounts=124,983,227@25.62GiB slots=806,829,266@165.16GiB codes=78965@566.74MiB eta=-2m7.602s
  ```

  ```text theme={null}
  msg="Syncing: chain download in progress" synced=100.00% chain="176.01 GiB" headers=116,817,399@45.82GiB bodies=116,817,286@52.87GiB receipts=116,817,286@77.32GiB eta=77.430ms
  ```

  Once synced, op-geth logs block imports as op-node feeds payloads:

  ```text theme={null}
  INFO [06-26|14:02:12.974] Imported new potential chain segment     number=4,068,194 hash=a334a0..609a83 blocks=1 txs=1
  INFO [06-26|14:02:12.976] Chain head was updated                   number=4,068,194 hash=a334a0..609a83
  ```
</Accordion>

## Next steps

* If your node is up and running, check the [Node Metrics and Monitoring Guide](/node-operators/guides/monitoring/metrics) to keep tabs on it.
* If you run into problems, see the [Node Troubleshooting Guide](/node-operators/guides/troubleshooting).
* For permissionless chains setting up withdrawal proving, continue with [Running op-reth with Historical Proofs](/node-operators/tutorials/reth-historical-proofs).
