# Enable WireGuard on NVIDIA Jetson

By Toshihito Kikuchi

In [the earlier article](https://docs.kinesis.network/blog/another-day-of-debugging), I mentioned Dynamo supports ARM64 devices. At that time, I tested Amazon EC2 [T4g](https://aws.amazon.com/ec2/instance-types/t4/) Instances and [Raspberry Pi 5](https://www.raspberrypi.com/products/raspberry-pi-5/). As a third ARM64 device to test, I purchased [NVIDIA Jetson Orin Nano](https://developer.nvidia.com/embedded/learn/get-started-jetson-orin-nano-devkit).

After landing several patches, I was able to run Dynamo on Jetson. However, you need to do one extra step before installing Dynamo on your device: Build and install WireGuard on your end. Well, I could provide pre-built binaries, but I’d like to encourage people to build linux kernel by yourself. It’s fun. And Jetson as a Kinesis Node won’t be a mainstream scenario anyway.

I saw several forums discussing this topic too. Hopefully this article will help non-Kinesis users as well.

### Devices needed

I purchased:

1. NVIDIA Jetson Orin Nano Super Developer Kit ([Amazon](https://www.amazon.com/dp/B0BZJTQ5YP?ref=ppx_yo2ov_dt_b_fed_asin_title\&th=1))
2. Memory Card (I bought 64GB UHS-1 via [Amazon](https://www.amazon.com/dp/B0CYT352MW?ref=ppx_yo2ov_dt_b_fed_asin_title\&th=1). You pick any product.)

You also need a display supporting Display Port and a USB keyboard if you don’t have any.

### OS setup

Here's the official Getting Started Guide provided by NVIDIA:\
<https://developer.nvidia.com/embedded/learn/get-started-jetson-orin-nano-devkit>

In my case, the firmware was already up-to-date (36.4.x), so I directly downloaded SD Card Image of [JetPack 6.2.1](https://developer.nvidia.com/embedded/jetpack-sdk-621), but yours may not be. Don’t skip to check your firmware version. Please note that the latest version JetPack 7.0 is for Thor, not for Jetson Orin Nano (see <https://developer.nvidia.com/embedded/jetpack-archive>).

You can use your favorite writer to write an image to SD Card. I used [balena Etcher](https://etcher.balena.io/). Once it’s done, just insert your card and power on your Jetson. No drama here.

### Build WireGuard module

Let's double check WireGuard is not included in your device. You might be lucky to have it somehow. If it exists, it would be in `/lib/modules/$(uname -r)/kernel/drivers/net/wireguard`.

```
jetson@jetson:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.5 LTS
Release:        22.04
Codename:       jammy

jetson@jetson:~$ uname -a
Linux jetson 5.15.148-tegra #1 SMP PREEMPT Thu Sep 18 15:08:33 PDT 2025 aarch64 aarch64 aarch64 GNU/Linux

jetson@jetson:~$ ls -l /lib/modules/$(uname -r)/kernel/drivers/net
total 220
drwxr-xr-x  4 root root  4096 Oct  7 23:14 can
drwxr-xr-x  3 root root  4096 Jun 17 23:26 dsa
-rw-r--r--  1 root root 13265 Sep 18 15:40 dummy.ko
drwxr-xr-x 10 root root  4096 Jun 17 23:26 ethernet
drwxr-xr-x  2 root root  4096 Oct  7 23:14 ipa
-rw-r--r--  1 root root 39633 Sep 18 15:40 macvlan.ko
-rw-r--r--  1 root root 12177 Sep 18 15:40 macvtap.ko
drwxr-xr-x  2 root root  4096 Oct  7 23:14 mdio
-rw-r--r--  1 root root 10321 Sep 18 15:40 mdio.ko
drwxr-xr-x  2 root root  4096 Oct  7 23:14 pcs
drwxr-xr-x  2 root root  4096 Oct  7 23:14 phy
-rw-r--r--  1 root root 51305 Sep 18 15:40 tap.ko
drwxr-xr-x  2 root root  4096 Oct  7 23:14 usb
-rw-r--r--  1 root root 46089 Sep 18 15:40 veth.ko
drwxr-xr-x  2 root root  4096 Oct  7 23:14 vxlan
drwxr-xr-x  6 root root  4096 Oct  7 23:14 wireless
```

Since Linux 5.6, WireGuard is a [part](https://github.com/torvalds/linux/tree/master/drivers/net/wireguard) of kernel code in the mainstream, so you can get wireguard module by building linux kernel. Fortunately, NVIDIA provides [a guide](https://docs.nvidia.com/jetson/archives/r38.2/DeveloperGuide/SD/Kernel/KernelCustomization.html) on how to customize kernel.

The guide says we can download the kernel source with a script named “source\_sync.sh”. A weird thing is it doesn’t tell us where it is. It’s not included in the SD Card Image, JetPack. The answer is [here](https://forums.developer.nvidia.com/t/where-is-source-sync-sh/75149). It’s included in Driver Package. You can go to [Jetson Linux](https://developer.nvidia.com/embedded/jetson-linux) page and download "Driver Package (BSP)”. As of now, the package file is Jetson\_Linux\_r36.4.4\_aarch64.tbz2, which includes ./Linux\_for\_Tegra/source/source\_sync.sh.

To run source\_sync.sh, you need to specify “release-tag”, which the guide says is included in the release notes. But which release note should we check? AI tells me to do

```
jetson@jetson:/work$ head -n1 /etc/nv_tegra_release
# R36 (release), REVISION: 4.7, GCID: 42132812, BOARD: generic, EABI: aarch64, DATE: Thu Sep 18 22:54:44 UTC 2025
```

In the output above, the release is 36.4.7, but there is no corresponding release note. Given that the release note of [36.4.4](https://docs.nvidia.com/jetson/archives/r36.4.4/ReleaseNotes/Jetson_Linux_Release_Notes_r36.4.4.pdf) says its release tag is “jetson\_36.4.4”, we can make a guess. The source repo is <https://nv-tegra.nvidia.com/r/admin/repos/3rdparty/canonical/linux-jammy,tags>, so you can check the list of tags there.

```
jetson@jetson:/work/Linux_for_Tegra/source$ ./source_sync.sh -k -t jetson_36.4.7
Downloading default kernel/kernel-jammy-src source...
Cloning into '/work/Linux_for_Tegra/source/kernel/kernel-jammy-src'...
remote: Enumerating objects: 8617818, done.
remote: Counting objects: 100% (8617818/8617818), done.
remote: Compressing objects: 100% (1256098/1256098), done.
Receiving objects: 100% (8617818/8617818), 1.89 GiB | 14.65 MiB/s, done.
remote: Total 8617818 (delta 7322045), reused 8605598 (delta 7309852), pack-reused 0 (from 0)
Resolving deltas: 100% (7322045/7322045), done.
Checking objects: 100% (33554432/33554432), done.
The default kernel/kernel-jammy-src source is downloaded in: /work/Linux_for_Tegra/source/kernel/kernel-jammy-src
Syncing up with tag jetson_36.4.7...
Updating files: 100% (74252/74252), done.
Switched to a new branch 'mybranch_2025-10-08-1759908244'
/work/Linux_for_Tegra/source/kernel/kernel-jammy-src source sync'ed to tag jetson_36.4.7 successfully!
```

When we build linux kernel, we usually generate .config to customize build options with `menuconfig` or other tools. For Jetson Linux, however, the guide suggests to use `make -C kernel` to build, where Makefile applies [defconfig](https://nv-tegra.nvidia.com/r/plugins/gitiles/3rdparty/canonical/linux-jammy/+/refs/tags/jetson_36.4.7/arch/arm64/configs/defconfig) to build by default. Even if we run `menuconfig`, Makefile overrides .config with defconfig.

As you can see, `CONFIG_WIREGUARD` is not defined there.

```bash
jetson@jetson:/work/Linux_for_Tegra/source$ grep WIRE kernel/kernel-jammy-src/arch/arm64/configs/defconfig
CONFIG_SOUNDWIRE=m
CONFIG_SOUNDWIRE_QCOM=m
```

Let’s simply add `CONFIG_WIREGUARD=m` at the end of the file and kick off build. Once build is done, wireguard.ko is generated.

```bash
jetson@jetson:/work/Linux_for_Tegra/source$ tail -5 kernel/kernel-jammy-src/arch/arm64/configs/defconfig
# CONFIG_FUNCTION_GRAPH_TRACER is not set
# CONFIG_DYNAMIC_FTRACE is not set
CONFIG_MEMTEST=y

CONFIG_WIREGUARD=m

# Install prereq packages
jetson@jetson:/work/Linux_for_Tegra/source$ sudo apt install -y build-essential bc flex bison libssl-dev zstd libncurses
-dev

# Build! (Makefile specifies -j option accordingly)
jetson@jetson:/work/Linux_for_Tegra/source$ make -C kernel
...

jetson@jetson:/work/Linux_for_Tegra/source$ find . -name wireguard.ko
./kernel/kernel-jammy-src/drivers/net/wireguard/wireguard.ko
```

### Install WireGuard module

To install a single module, you simply copy a file under /lib/modules, generate dependencies, and load with `modprobe`.

```sh
sudo mkdir /lib/modules/$(uname -r)/kernel/drivers/net/wireguard/
sudo cp kernel/kernel-jammy-src/drivers/net/wireguard/wireguard.ko \
  /lib/modules/$(uname -r)/kernel/drivers/net/wireguard/
sudo depmod -a
sudo modprobe wireguard
```

Oops, the last `modprobe` command failed with the following error.

```bash
jetson@jetson:/work/Linux_for_Tegra/source$ sudo modprobe wireguard
modprobe: ERROR: could not insert 'wireguard': Unknown symbol in module, or unknown parameter (see dmesg)
```

Don’t panic. You can see more details with `dmesg`.

```bash
jetson@jetson:~$ sudo dmesg | tail
...
[10962.061383] wireguard: Unknown symbol chacha20poly1305_encrypt_sg_inplace (err -2)
[10962.061509] wireguard: Unknown symbol chacha20poly1305_encrypt (err -2)
[10962.061715] wireguard: Unknown symbol chacha20poly1305_decrypt_sg_inplace (err -2)
[10962.061798] wireguard: Unknown symbol xchacha20poly1305_encrypt (err -2)
[10962.061820] wireguard: Unknown symbol xchacha20poly1305_decrypt (err -2)
[10962.061913] wireguard: Unknown symbol chacha20poly1305_decrypt (err -2)
```

These are simple dependency errors. Searching code, you can easily find these symbols are implemented in libchacha20poly1305.ko, which is not installed in the kernel.

```
jetson@jetson:/work/Linux_for_Tegra/source/kernel/kernel-jammy-src/lib/crypto$ grep -nr chacha20poly1305_encrypt_sg_inplace
grep: libchacha20poly1305.o: binary file matches
chacha20poly1305-selftest.c:8928:		ret = chacha20poly1305_encrypt_sg_inplace(sg_src,
chacha20poly1305-selftest.c:9043:				if (!chacha20poly1305_encrypt_sg_inplace(sg_src,
grep: libchacha20poly1305.ko: binary file matches
grep: chacha20poly1305.o: binary file matches
chacha20poly1305.c:333:bool chacha20poly1305_encrypt_sg_inplace(struct scatterlist *src, size_t src_len,
chacha20poly1305.c:341:EXPORT_SYMBOL(chacha20poly1305_encrypt_sg_inplace);

jetson@jetson:/work/Linux_for_Tegra/source$ nm -g kernel/kernel-jammy-src/lib/crypto/libchacha20poly1305.ko | grep chacha20poly1305_encrypt_sg_inplace
0000000000000bd0 T chacha20poly1305_encrypt_sg_inplace
0000000037b34b92 A __crc_chacha20poly1305_encrypt_sg_inplace

jetson@jetson:/work/Linux_for_Tegra/source$ find /sys/module/$(uname -r) -name libchacha*
find: ‘/sys/module/5.15.148-tegra’: No such file or directory
```

Let’s install this module and try to load wireguard again.

```bash
sudo cp ./kernel/kernel-jammy-src/lib/crypto/libchacha20poly1305.ko \
  /lib/modules/$(uname -r)/kernel/lib/crypto/
sudo depmod -a
sudo modprobe wireguard
```

You’ll get the same error from `modprobe`, but it’s caused by different symbols.

```bash
[11355.067989] libchacha20poly1305: Unknown symbol poly1305_final_arch (err -2)
[11355.068039] libchacha20poly1305: Unknown symbol poly1305_init_arch (err -2)
[11355.068089] libchacha20poly1305: Unknown symbol poly1305_update_arch (err -2)
```

Repeat the same thing, searching code, identifying a missing module implementing the symbols. This time it’s poly1305-neon.ko. Neon is SIMD extension for ARM.

```bash
jetson@jetson:/work/Linux_for_Tegra/source/kernel/kernel-jammy-src/arch/arm64/crypto$ grep -nr poly1305_final_arch
grep: poly1305-neon.o: binary file matches
grep: poly1305-neon.ko: binary file matches
grep: poly1305-glue.o: binary file matches
poly1305-glue.c:170:void poly1305_final_arch(struct poly1305_desc_ctx *dctx, u8 *dst)
poly1305-glue.c:182:EXPORT_SYMBOL(poly1305_final_arch);
poly1305-glue.c:191:	poly1305_final_arch(dctx, dst);

jetson@jetson:/work/Linux_for_Tegra/source$ nm -g kernel/kernel-jammy-src/arch/arm64/crypto/poly1305-neon.ko | grep poly1305_final_arch
00000000f39f5240 A __crc_poly1305_final_arch
0000000000000e00 T poly1305_final_arch
```

Let’s just copy and try to load wireguard.ko once again.

```bash
sudo cp ./kernel/kernel-jammy-src/arch/arm64/crypto/poly1305-neon.ko \
  /lib/modules/$(uname -r)/kernel/arch/arm64/crypto/
sudo depmod -a
sudo modprobe wireguard
```

It should work this time, and you’ll see it’s loaded as below.

```bash
jetson@jetson:/work/Linux_for_Tegra/source$ lsmod | grep wire
wireguard              77824  0
libchacha20poly1305    16384  1 wireguard
ip6_udp_tunnel         20480  1 wireguard
udp_tunnel             24576  1 wireguard
libcurve25519_generic    36864  1 wireguard
ipv6                  471040  94 bridge,wireguard
```

### Verification

Dynamo uses wireguard through [our gateway container](https://hub.docker.com/r/kinesisorg/gateway-container). You can simulate this scenario by creating a minimum wireguard conf and running a container as follows. An endpoint can be random which doesn’t need to be valid for this verification purpose.

```bash
jetson@jetson:/work$ cat <<EOF > ${PWD}/wg-test.conf
[Interface]
PrivateKey = 6KvR4DZ1+uZom3S4VOBlbMtDpuZGGnbNEIFJfD4cjUg=
Address = 10.200.100.1/24
[Peer]
PublicKey = xFpe/+vmrUzQwtqpvVLB4AXygknLQo3f/qRR0AFXflQ=
AllowedIPs = 10.200.100.2/32
Endpoint = 1.2.3.4:51820
PersistentKeepalive = 25
EOF
jetson@jetson:/work$ docker run -d \
  --cap-add=NET_ADMIN \
  --device /dev/net/tun \
  --sysctl net.ipv4.ip_forward=1 \
  -v ${PWD}/wg-test.conf:/etc/wireguard/wg0.conf:ro \
  kinesisorg/gateway-container
250d35ac39c902515501ef863cf0d4cb91a2b407b75b6feab2fd6a41dc686c91
jetson@jetson:/work$ docker exec -it $(docker ps -q -f ancestor=kinesisorg/gateway-container) wg show
interface: wg0
  public key: i0JM5N5EVUDJ/08h5N4mt6bohSPrtXJZcNFqrsOULzA=
  private key: (hidden)
  listening port: 49594

peer: xFpe/+vmrUzQwtqpvVLB4AXygknLQo3f/qRR0AFXflQ=
  endpoint: 1.2.3.4:51820
  allowed ips: 10.200.100.2/32
  transfer: 0 B received, 592 B sent
  persistent keepalive: every 25 seconds
```

### Conclusion

Congratulations! Your Jetson device is ready to run Dynamo and join the Kinesis Network as a Global Node. In short, you need to clone Jetson Linux Kernel from NVIDIA’s repo, build it, and copy wireguard.ko along with a couple of dependent (crypto-related) modules. Pretty straightforward.

You might be interested in installing the kernel itself and debugging it. That’s good ambition. Let’s discuss it as another topic!


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.kinesis.network/blog/enable-wireguard-on-nvidia-jetson.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
