Porting Erlang to QNX: a small nerd-snipe that actually worked

A few weeks ago, I had a conversation at a conference that stuck with me.

I was talking to someone who works on automotive systems and uses Erlang in production. Not in the car itself, though. Only on the server side. When I asked why, the answer was very pragmatic: there is no QNX support. QNX is widely adopted in automotive systems, especially in areas where reliability, safety, and real-time behavior matter. If you operate in that space, QNX is often part of the picture.

That answer nerd-sniped me a bit.

Not because it was wrong, but because it raised a simple question in my head: is this a fundamental limitation, or just something nobody has tried to seriously tackle yet?

I am a seasoned Erlang and OTP developer, but I am very much not an embedded or close-to-the-hardware person. I have edited C code and Makefiles before, but I had no clear idea what the actual path to “Erlang on QNX” would look like. I expected a rabbit hole. I also expected a lot of pain.

So I decided to try.

After about three days of focused work, I ended up with this patch: https://github.com/erlang/otp/commit/d7e0b82835312a0458e276f4663bb432574a26d6.patch

More importantly, I could boot Erlang on QNX, start a shell, and successfully make an HTTPS request using the crypto application under the hood.

Hello world achieved. With TLS. On QNX.

What this is (and what it is not)

This is an experimental patch. It is not clean, and I would not upstream it in its current shape. It is a proof of concept.

If you are seriously considering running Erlang inside a car or another safety-critical system, this post alone is obviously not enough. But it should demonstrate that the topic itself is not a hard blocker. If there is real production interest, this is something that can be worked on properly.

I am not planning to continue this effort by default. If there is interest, possibly. If not, this remains an exploration.

The actual goal

I kept the goal deliberately small and concrete:

  • Build Erlang/OTP on QNX
  • Start the Erlang shell
  • Make an HTTPS request
  • Verify that crypto works end to end

If you can do that, a lot of the scary questions are already answered.

The setup (this took longer than expected)

By far the hardest part was not Erlang. It was getting a usable QNX environment in the first place.

I am working on a MacBook with Apple Silicon. ARM, not Intel. That already complicates things. The setup that eventually worked for me looked roughly like this:

  1. Download an older QNX Software Download Center
  2. Activate a license and install the QNX SDP (QNX SDP 8 in my case)
  3. Use Docker to run QNX tooling, following this guide: https://devblog.qnx.com/how-to-use-macos-to-develop-for-qnx-8-0/
  4. Enable the SDK environment source qnx800/qnxsdp-env.sh
  5. Cross-compile Erlang using OTP’s existing xcomp support
  6. Create a QNX image using mkqnximage
  7. Add Erlang to the image
  8. Run the image in QEMU (via UTM)
  9. Open a serial console and start Erlang

One of the main pain points here was the feedback loop. I was cross-compiling inside Docker, targeting x86_64 QNX, while Docker itself was emulating x64 Linux on an ARM host. Every small change meant a rebuild, a new image, a reboot in QEMU, and another test run. The roundtrip time was slow, and that probably accounted for a good chunk of the total effort.

Still, this was friction, not a blocker.

The build script

For reference, this is the stripped-down version of the build script I used. The original was more verbose and logged heavily, but this shows the essential steps:

#!/usr/bin/env bash
set -e

source /home/maennchen/qnx800/qnxsdp-env.sh

./otp_build configure \
  --xcomp-conf="$PWD/xcomp/erl-xcomp-x86_64-qnx.conf"

./otp_build boot -a
./otp_build release -a _stage
./otp_build tests _tests

To build the QNX image:

mkqnximage --data-size=4096 --sys-size=2048 --clean --build

To include Erlang in the image, I added the following file:

output/option_files/system_files.custom

With these contents:

# Erlang/OTP - include entire staging directory recursively
/usr/lib/erlang=[Erlang Root]/_stage
/usr/lib/erlang-test=[Erlang Root]/_tests

That was enough to boot into QNX and start Erlang from the serial console.

The moment it worked

At that point, I wanted something that was undeniably “real Erlang on QNX”. Not a mock, not a stub, not a hand-wavy claim.

This is the serial console transcript from booting QNX, starting Erlang, checking the platform, running a small concurrency example, inspecting crypto support, and finally making an HTTPS request.

Booting from Hard Disk...

...
Startup complete
QNX noname 8.0.0 2025/07/30-19:24:08EDT x86pc x86_64

# /system/usr/lib/erlang/bin/erl
Erlang/OTP 28 [erts-16.2] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1]

Eshell V16.2 (press Ctrl+G to abort, type help(). for help)
1> %% Basic system identification
   erlang:system_info(system_architecture).
"x86_64-unknown-nto-qnx"
2> erlang:system_info(os_type).
{unix,qnx}
3>
   %% Classic Erlang concurrency sanity check
   Self = self(),
   spawn(fun() -> Self ! {hello, qnx} end),
   receive Msg -> Msg end.
{hello,qnx}
4>
   %% Crypto capabilities (shows real crypto support on QNX)
   crypto:supports().
[{hashs,[blake2s,blake2b,sm3,shake256,shake128,sha3_512,
         sha3_384,sha3_256,sha3_224,sha512_256,sha512_224,sha512,
         sha384,sha256,sha224,sha,ripemd160,md5]},
 ...]
5>
   %% HTTPS HEAD request without certificate verification
   inets:start(),
   ssl:start(),
   httpc:request(
     head,
     {"https://erlang.org", []},
     [{ssl, [{verify, verify_none}]}],
     []
   ).
{ok,{{"HTTP/1.1",200,"OK"},
     [{"cache-control","public,max-age=0,must-revalidate"},
      {"date","Wed, 07 Jan 2026 17:56:44 GMT"},
      {"server","Netlify"},
      {"content-type","text/html; charset=UTF-8"},
      ...],
     []}}
6>

For me, that was the point where this stopped being theoretical.

The port itself

The actual porting work was surprisingly reasonable.

Most of it came down to:

  • Filling in missing platform definitions
  • Handling small libc and syscall differences
  • Teaching OTP that QNX exists and how it behaves

Nothing here felt fundamentally hard. It was iterative and sometimes a bit tedious, but never overwhelming. More importantly, it did not require deep embedded expertise or intimate hardware knowledge.

Closing thoughts

Porting Erlang to a new platform can sound intimidating, especially when that platform lives in safety-critical or embedded environments. This experiment shows that, at least technically, it does not have to be a major obstacle.

Getting to a working baseline took a few days of focused effort, most of which went into tooling and setup rather than OTP itself. For an organization that is already serious about deploying software in environments like automotive systems, this is a relatively small investment.

If there is real interest in running Erlang on QNX in production, this is a solvable problem. The hard part is usually not whether it is possible, but whether someone is willing to take the first step and try.