NervesHubLink + Shared Secret on NervesCloud: WS upgrade 401 (TLS OK) on fresh app (RPi 5)

Context

I’m following the official tutorial to bring up a minimal Nerves app and connect it to NervesCloud using Shared Secret auth (no Phoenix, no extras). The device boots fine and has network, but the NervesHubLink fails the WebSocket upgrade with 401 even though TLS handshake succeeds.

Environment

  • Device: Raspberry Pi 5 (target rpi5)
  • Nerves: 1.11.3
  • nerves_hub_link: 2.7.3
  • VintageNet: 0.13.7
  • Host CLI:
    • nh config set uri "https://manage.nervescloud.com/"
    • nh user auth
    • Org: ***, Product: my_pi (empty list of devices so far)

What I did (repro)

  1. New project per tutorial (minimal app, no Phoenix).
  2. target.exs:
config :nerves_hub_link,
  host: "devices.nervescloud.com",
  remote_iex: true,
  shared_secret: [
    product_key: "NHP_...REDACTED...",     # freshly rotated in UI
    product_secret: "...REDACTED..."
  ]
  1. Built for MIX_TARGET=rpi5, burned, booted.
  2. SSH device console:
Nerves.Runtime.serial_number()
"9e5**********" # <- non-empty

:application.get_all_env(:nerves_hub_link)
[
  host: "devices.nervescloud.com",
  shared_secret: [
    product_key: "nhp_............******....",
    product_secret: "............******...."
  ],
  remote_iex: true,
  fwup_public_keys: []
]

iex(8)> :ssl.connect('devices.nervescloud.com', 443,
...(8)>   [verify: :verify_peer, cacerts: :public_key.cacerts_get()], 5000)
{:ok,
 {:sslsocket, {:gen_tcp, #Port<0.196>, :tls_connection, :undefined},
  [#PID<0.1633.0>, #PID<0.1632.0>]}}

Application.get_env(:nerves_hub_link, :host)
"devices.nervescloud.com"

Error reapeted:

[NervesHubLink] error: {:error, {:upgrade_failure,
  %{reason: %Mint.WebSocket.UpgradeFailureError{
      status_code: 401,
      headers: [
        {"date","Thu, 14 Aug 2025 14:11:08 GMT"},
        {"content-length","0"},
        {"cache-control","max-age=0, private, must-revalidate"}]},
    status_code: 401}}}

:alarm_handler: {:set, {NervesHubLink.Disconnected, [...]}}

I did setup secret keys few times, it is valid

The issue is in something else, I cant figure out it what
Does anyone have a clue what it can be?

Hi @marinakr

Josh from NervesCloud here.

Your config looks correct. Since its a 401 error, I believe its a problem with either the shared secret config, or a device with the serial number either exists in another product, or has been soft deleted but not destroyed.

Could you please share the serial number of the device and I can dive into our logs?

Hi John, thanks for reply!
The device serial number is 9e567721e291747e
I have few test products and repositories (it is empty “hello world” repos/simple cruds from tutorial), non of them is production or at least used for now, I just play with nerve hub, and I have one raspberry pi 5 device for test (this one)
However, this device did not appear in any product
So if needed I can even provide a product key-secret pair since it is test projects
Let me check if this device appears for another product
UPD: yes, it appears
Let me delete it for prev product and test again
I ll let you know if delete this device from another product helps

Hey Marina

Thank you for sharing the full device serial number.

I’ve found error messages in our logs which confirm the case of a device with that id already existing in our system (device identifiers are unique across the platform).

I’ve also checked our DB and see that a device with that identifier is associated with a product and org. Could you DM me the name of the Org and Product the device was previously connected to?

Thanks

Josh

2 Likes

I have deleted my previous products (it was test empty apps)
and created new one with better name
Now I am facing this issue again

Looks like you have a bug with product deletion

cmd("fw_setenv nerves_serial_number 9e567721e291747e-alt")
Nerves.Runtime.KV.reload()
:ok = Application.stop(:nerves_hub_link)
Process.sleep(1200)
{:ok, _} = Application.ensure_all_started(:nerves_hub_link)

Device with -alt appears:

connected 9e567721e291747e-alt 0.1.0 rpi5

So you keep old device for deleted product
But the issue is I can’t delete it, because I can’t find deleted product

$ nh product list --org CoCoCo 
NervesHub Host: manage.nervescloud.com
Organization:   CoCoCo

Products:
------------
  name: coco_press_agent

Devices output:

$ nh device list
NervesHub Host: manage.nervescloud.com
Organization:   CoCoCo
NervesHub product: coco_press_agent
+-----------------------------------------------------------------------------------------------------------------------------------+
|                                               Devices for CoCoCo / coco_press_agent                                               |
+----------------------+------+---------+--------------------------------------+--------+-----------------------------+-------------+
| Identifier           | Tags | Version | Firmware UUID                        | Status | Last connected              | Description |
+----------------------+------+---------+--------------------------------------+--------+-----------------------------+-------------+
| 9e567721e291747e-alt |      | 0.1.0   | fb5befc9-ee41-55ff-2c33-ca23b8b1703e |        | 2025-08-18 18:32:49.847960Z |             |
+----------------------+------+---------+--------------------------------------+--------+-----------------------------+-------------+

Total devices displayed: 1
Total devices: 1

Yeah, there isn’t UI for it but if you sent the details to Josh in DM we can tidy that up for you on our end. We definitely plan to fix this and make UI but there’s been a bunch of weird corners like this to work on :slight_smile:

Thanks for the extra info @marinakr

I think the core of the issue is that Devices are initially soft deleted (recoverable) but need to be ‘destroyed’ after soft deleted.

Or you need to ‘move’ devices from one Product to another, which can be done on the Device list page.

None the less, this is an improvement we need to make on our end.

I’ll get the stuck devices removed ready for you to try again in the morning.

1 Like