> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nerves-hub.org/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: Your First OTA Update

> Go from a new Nerves project to your first over-the-air firmware update on NervesCloud in minutes using Shared Secret authentication.

This quickstart gets a Nerves device receiving an OTA update from NervesCloud using the simplest setup: **shared-secret auth**. The end result is a Nerves project connected to NervesHub, a device visible in the web UI, signed firmware uploaded, and a deployment group that ships the update.

<Note>
  Shared secrets suit hobby and R\&D projects. For production, use certificate auth. See [X.509 device certificates](/auth/device-certificates).
</Note>

## Prerequisites

* A working [Nerves setup](https://hexdocs.pm/nerves/installation.html) (Elixir, Nerves bootstrap, `fwup`).
* A supported target (e.g. Raspberry Pi) and an SD card.
* An account on [NervesCloud](https://manage.nervescloud.com) or a self-hosted instance.

This guide uses `manage.nervescloud.com` as the host. Swap in your own if self-hosting.

<Steps>
  <Step title="Install the NervesHub CLI">
    The `nh` CLI is the primary tool for uploading firmware and managing rollouts from your terminal or CI pipeline. Install it now so you can create a signing key before building your first firmware image.

    <Tabs>
      <Tab title="macOS (Homebrew)">
        ```bash theme={null}
        brew install nerves-hub/tap/nh
        ```
      </Tab>

      <Tab title="Linux / macOS (curl)">
        ```bash theme={null}
        curl -sL https://nerves-hub.org/install/nh.sh | sh
        ```
      </Tab>
    </Tabs>

    After installing, authenticate with your NervesCloud account:

    ```bash theme={null}
    nh user auth
    ```

    This opens a browser window to complete authentication and stores a token locally for subsequent CLI commands.
  </Step>

  <Step title="Create a Firmware Signing Key">
    NervesHub requires firmware to be signed before it can be uploaded. Create a signing key pair with the CLI:

    ```bash theme={null}
    nh key create my-key
    ```

    This generates a key pair and registers the **public key** with NervesCloud. The private key is stored locally and is never transmitted.

    Next, retrieve the base64-encoded public key so you can embed it in your firmware image. Devices use this key to independently verify every OTA update before applying it:

    ```bash theme={null}
    nh key list
    ```

    Copy the **Public Key** value shown for `my-key` — you'll add it to your project configuration in the next step.

    <Warning>
      Back up your signing key's private key material. If you lose it, you cannot sign future firmware updates with the same key, and you may need to provision a new key to your devices out-of-band.
    </Warning>
  </Step>

  <Step title="Create the project">
    ```bash theme={null}
    mix nerves.new my_app

    cd my_app

    export MIX_TARGET=rpi0_2   # your target
    ```
  </Step>

  <Step title="Create a product and shared secret">
    In the web UI:

    1. Create or choose an **organization**.
    2. Create a **product** (name it `my_app` to match, for convenience).
    3. **Settings → Shared Secrets → New.** Copy the **product key** and **product secret**.
  </Step>

  <Step title="Add NervesHubLink">
    In `mix.exs`, add it to your target deps:

    ```elixir theme={null}
    {:nerves_hub_link, "~> 2.2"},
    ```
  </Step>

  <Step title="Configure the shared secret">
    In `config/target.exs`:

    ```elixir theme={null}
    config :nerves_hub_link,
      host: "devices.nervescloud.com",
      shared_secret: [
        product_key: "<product_key>",
        product_secret: "<product_secret>"
      ]
    ```

    ```elixir theme={null}
    # config/target.exs
    config :nerves_hub_link,
      host: "devices.nervescloud.com",
      remote_iex: true,
      fwup_public_keys: ["YOUR_BASE64_PUBLIC_KEY"],
      shared_secret: [
        product_key: "YOUR_PRODUCT_KEY",
        product_secret: "YOUR_PRODUCT_SECRET"
      ]
    ```

    Replace `YOUR_BASE64_PUBLIC_KEY` with the public key value from `nh key list`, and replace `YOUR_PRODUCT_KEY` / `YOUR_PRODUCT_SECRET` with the values you copied from NervesCloud.

    The `fwup_public_keys` list tells the device which signing keys to trust when verifying OTA firmware. A device will reject any firmware update whose signature does not match a key in this list.

    <Tip>
      Keep real secrets out of version control. Read them from the environment for anything beyond a quick test.
    </Tip>
  </Step>

  <Step title="Build and burn">
    ```bash theme={null}
    mix deps.get

    mix firmware

    mix burn
    ```

    `mix firmware` compiles your project and packages it as a signed `.fw` file using **fwup**. `mix burn` writes the image to your connected storage device. The public key you embedded in `fwup_public_keys` is now compiled into the device's firmware, enabling it to verify all future OTA updates.

    Insert the SD card, power on. The device boots, connects, and opens a websocket to NervesHub.
  </Step>

  <Step title="Confirm the device connected">
    Web UI → your product → **Devices**. The device appears and shows as connected within a few moments.
  </Step>

  <Step title="Install the CLI">
    ```bash theme={null}
    brew install nerves-hub/tap/nh-cli
    ```

    Or grab a prebuilt binary from the [latest release](https://github.com/nerves-hub/nh/releases/latest) and put it on your `PATH`.

    Self-hosting? Point it at your instance first:

    ```bash theme={null}
    nh config set uri "https://my.selfhosted.instance/"
    ```

    Then authenticate:

    ```bash theme={null}
    nh user auth
    ```
  </Step>

  <Step title="Create firmware signing keys">
    Firmware must be signed. Create a key pair:

    ```bash theme={null}
    nh key create my_app_key
    ```

    This registers the public key with your org so NervesHub can verify signatures. **Keep the private key safe:** you need it to sign every image. (You can also manage keys in the web UI under **Settings → Signing Keys**.)
  </Step>

  <Step title="Build, Sign, and Upload Updated Firmware">
    Make a visible change to your project (for example, update the `@version` field in `mix.exs` to `"0.2.0"`), then build and upload the firmware:

    ```bash theme={null}
    mix firmware
    nh firmware upload ./_build/rpi4_dev/nerves/images/my_project.fw --key my-key
    ```

    The `nh firmware upload` command signs the `.fw` file with `my-key`, uploads it to NervesCloud object storage, and registers it as a new firmware artifact. You'll receive a firmware **UUID** in the output — note it for the next step.

    <Tip>
      Use the `--deploy` flag to combine uploading and shipping in a single command — ideal for CI pipelines:

      ```bash theme={null}
      nh firmware upload ./_build/rpi4_dev/nerves/images/my_project.fw \
        --key my-key \
        --deploy production \
        --tag main
      ```
    </Tip>
  </Step>

  <Step title="Create and Activate a Deployment Group">
    A **deployment group** tells NervesHub which devices should receive a firmware update and under what conditions. Create one targeting devices tagged `main`, then activate it:

    ```bash theme={null}
    nh deployment create \
      --name "production" \
      --firmware <UUID> \
      --tag "main"

    nh deployment update "production" state on
    ```

    Replace `<UUID>` with the firmware UUID from the previous step. The group will target all devices in the product that carry the `main` tag.

    <Note>
      The web console calls these **deployment groups**, while the `nh` CLI still uses the `deployment` command name. They are the same object.
    </Note>
  </Step>

  <Step title="Watch Your Device Auto-Update">
    With the group active, your online device will receive the update notification over its WebSocket connection to NervesCloud. It will:

    1. Download the new firmware.
    2. Verify the signature against the embedded public key.
    3. Apply the update using **fwup**.
    4. Reboot into the new firmware.

    Monitor the update progress from the **Devices** view in NervesCloud — you'll see the device go offline briefly during the reboot and come back online running the new firmware version.
  </Step>

  <Step title="Point your project at the signing key">
    Provide the keys via environment variables:

    ```bash theme={null}
    export NERVES_CLOUD_FW_PRIVATE_KEY=$(cat ~/.nerves-cloud/keys/my_app_key.priv)
    export NERVES_CLOUD_FW_PUBLIC_KEY=$(cat ~/.nerves-cloud/keys/my_app_key.pub)
    ```

    <Info>
      Paths and variable names vary by CLI version and platform. See the [CLI reference](/cli/reference). The `NERVES_HUB_*` equivalents also work.
    </Info>
  </Step>

  <Step title="Build and publish signed firmware">
    Make a visible change (e.g. a log line) and bump the version in `mix.exs`, then:

    ```bash theme={null}
    mix firmware
    nh firmware publish
    ```

    Or upload in the web UI under **Firmware → Upload**.
  </Step>

  <Step title="Create a deployment group">
    Web UI → **Deployment Groups → New**:

    1. Select the firmware you just published as the **release**.
    2. Set targeting **conditions** (version + tags). Leave tags empty for now so it matches your device.
    3. Save and mark it **active**.

    Full options: [Deployment groups](/setup/deployments), including staged rollouts with [workflows](/concepts/deployment-workflows).
  </Step>

  <Step title="Watch it update">
    With an active group pointing at newer firmware, NervesHub offers the update. The device downloads it, verifies the signature, applies it, and reboots into the new version. Track progress on the device's page.

    That's the full round trip: **build → sign → publish → deploy → update.** 🎉
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Device Certificates" icon="certificate" href="/auth/device-certificates">
    Upgrade from Shared Secret to X.509 mTLS device certificates for production-grade security.
  </Card>

  <Card title="Delta Updates" icon="arrows-minimize" href="/management/delta-updates">
    Reduce update bandwidth dramatically by sending only the binary diff between firmware versions.
  </Card>

  <Card title="Deployment Groups" icon="rocket" href="/setup/deployments">
    Learn how to configure concurrency limits, failure thresholds, and staged rollouts.
  </Card>

  <Card title="Remote Console" icon="terminal" href="/management/remote-console">
    Access a live IEx session on any connected device directly from the NervesCloud UI.
  </Card>
</CardGroup>
