< blog

Cloudflare Workers on NixOS

2026-03-26 · ZERO

pnpm dev on a fresh Cloudflare Workers project. Wrangler starts, then crashes. No such file or directory. Welcome to NixOS.

Two things break. Both are fixable with a shell.nix.

PROBLEM 1: BINARY npm workerd (ELF) /lib64/ld-linux.so ✗ not found NixOS has no /lib64. Dynamic linker lives in /nix/store. PROBLEM 2: CERTS workerd (BoringSSL) SSL_CERT_DIR/{hash}.0 ✗ no hash files NixOS bundles all certs into one ca-bundle.crt file.

Fix 1: Symlink the binary

The workerd binary that npm installs is a standard ELF binary linked against /lib64/ld-linux-x86-64.so.2. NixOS doesn't have that path. But the nix-packaged wrangler includes a properly patched workerd. Symlink over the broken one:

# Find the nix-patched workerd
_nix_workerd=$(find ${pkgs.nodePackages.wrangler}/lib \
  -path "*/workerd-linux-64/bin/workerd" -type f)

# Replace npm's broken binary with a symlink
for broken in $(find node_modules \
  -path "*/workerd-linux-64/bin/workerd" ! -type l); do
  ln -sf "$_nix_workerd" "$broken"
done

Fix 2: Build cert hashes

Workerd uses BoringSSL, which looks for certificates as individual files named by their hash (a1b2c3d4.0). NixOS ships one big ca-bundle.crt. Split it and hash it:

certDir = pkgs.runCommand "workerd-certs" {
  buildInputs = [ pkgs.openssl ];
} ''
  mkdir -p $out && cd $out

  # Split bundle into individual PEM files
  awk 'BEGIN {n=0}
    /-----BEGIN CERTIFICATE-----/ {n++; f=sprintf("cert-%03d.pem",n)}
    f {print > f}' ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt

  # Create hash-based symlinks
  for f in cert-*.pem; do
    hash=$(openssl x509 -hash -noout -in "$f")
    ln -s "$f" "''${hash}.0"
  done
'';

Set SSL_CERT_DIR to this directory and workerd finds every CA certificate it needs.

Put it together

A shell.nix that does both. The binary patch runs in shellHook (after pnpm install creates node_modules). The cert directory is built once by nix and cached.

If you use Turborepo, add SSL_CERT_DIR to globalPassThroughEnv in turbo.json so it reaches the wrangler process.


Full shell.nix on GitHub