Jetson Orin Nano Headless Setup: NVMe Boot, SDK Flashing, and VNC (GNOME & XFCE)

Posted on Dec 25, 2025
Table of Contents

Jetson Orin Nano

If you’ve ever tried to get these boards running without a monitor, you know the pain. Half the forum posts are outdated, the other half assume you have hardware you don’t, and NVIDIA’s official docs cheerfully skip over the part where GNOME refuses to start because it can’t find a display that doesn’t exist. After too many hours lost to conflicting advice and “just plug in a monitor” non-solutions, I’m documenting what actually works. A truly headless setup from first flash to stable remote desktop, no display required at any step. This isn’t another wishful tutorial that glosses over the hard parts; it’s the battle-tested process. If you’re tired of duct-tape solutions that break on the next reboot, or you simply refuse to keep a spare monitor around for a device that’s supposed to run headless anyway, this guide is for you.

Audience & Assumptions:
This guide assumes familiarity with Linux, SSH, and systemd. It is written for developers running Jetson Orin Nano headlessly (no monitor/keyboard) who want reliable remote GUI access without fragile workarounds.

This guide walks through the complete headless workflow: flashing JetPack to an NVMe SSD using NVIDIA SDK Manager, forcing GNOME to initialize with a synthetic display using custom EDID injection and Xorg configuration, and establishing stable VNC access via x11vnc. For those who prefer a lighter desktop environment, we also cover a parallel XFCE setup using TigerVNC that sidesteps GPU dependencies entirely. Whether you’re deploying edge AI workloads in a lab or managing remote Jetson devices in production, this approach delivers reliable GUI access without fragile hacks or recurring configuration drift.

A quick note on navigation: if you’ve already got your Jetson up and running with JetPack installed and can SSH into it, feel free to jump straight to Part 2 or Part 3 depending on whether you want GNOME or XFCE for your remote desktop. Part 1 is purely about the initial OS flash and SDK installation process. If that’s already done and you’re just here for reliable VNC access, you’re not missing anything critical by skipping ahead. The VNC sections are completely independent and assume you’re starting from a working, SSH-accessible Jetson with internet connectivity.

Part 1:

Flashing OS onto Jetson Headlessly with an SSD

This section explains how to flash and boot an NVMe SSD on a Jetson Orin Nano without attaching a monitor or keyboard. The process uses NVIDIA SDK Manager , which enforces a strict host–target model. Confusing the two is the most common cause of failure, so we state them clearly.

Before proceeding, I would recommend to watch this setup guide from DroneBot Workshop on YouTube, which covers the same ground visually. Even I followed the same until acquiring the IP address of the Jetson after flashing.

Host and Target Overview

Host Machine (Required)

  • Purpose: Flashes the Jetson and installs NVIDIA SDK components
  • System: Ubuntu 20.04 or 22.04 (x86_64 PC or laptop)
  • Software: NVIDIA SDK Manager
  • Connectivity: USB-C (data cable) during flashing, Ethernet used later for SDK installation

Target Device (Jetson)

  • Device: NVIDIA Jetson Orin Nano Developer Kit
  • Storage: NVMe SSD (M.2 2230 or 2280, PCIe Gen3 only; minimum 256 GB recommended)
  • Flashing state: Recovery mode
  • Peripherals: No display or keyboard required (headless)
  • Networking: Ethernet required after flashing

The host only orchestrates the process. The target is the device being flashed and configured.

Step 1: Install NVMe SSD on the Target

Power off the Jetson Orin Nano. Install the NVMe SSD into one of the M.2 slots on the underside of the carrier board and secure it with the provided screw.

This SSD will become the boot device. No microSD card is involved.

I used 500GB NVMe from my pc, couldn’t find any reasonable priced SSDs online or offline. The prices of SSDs now are crazy expensive at the time of writing compared to few months back :( hope it gets better soon.

Step 2: Put the Target into Recovery Mode

Jetson Recovery Mode

To allow the host to flash the target, recovery mode is mandatory.

  1. Locate the 14-pin button header on the Jetson carrier board.
  2. Short FC REC and GND (pins 2 and 3) using a jumper.
  3. Leave the jumper connected.
  4. Power on the Jetson.

If recovery mode is not active, the host will not detect the target.

Step 3: Prepare the Host Machine

On the host (Ubuntu PC):

  1. Install NVIDIA SDK Manager (.deb package).
  2. Disable sleep and screen blanking.
  3. Ensure internet connectivity.

Nothing in this step affects the Jetson directly.

Step 4: Flash the Target from the Host

Jetson Type C

All actions below happen on the host, but they operate on the target.

  1. Launch SDK Manager on the host and log in.

  2. Connect the Jetson (in recovery mode) to the host using USB-C.

  3. Power on the Jetson.

  4. SDK Manager should detect the device:

    • Select Jetson Orin Nano 8GB Developer Kit
  5. Accept the license agreement.

  6. Click Download and Flash.

  7. When prompted:

    • Choose Preconfig (recommended for headless use), or
    • Choose Runtime if you prefer first-boot setup.
  8. Select nvme as the target storage device.

  9. Start flashing and wait.

Do not disconnect power or USB. If this fails, you start over.

Step 5: First Boot and Network-Based SDK Installation

  1. After flashing completes:

    • Power off the Jetson
    • Remove the recovery jumper. I used female dupont connectors for easy handling.
  2. Power on the Jetson normally (no USB flashing cable required anymore).

  3. Connect the Jetson to your network using Ethernet. This is mandatory, the remaining SDK components are installed over the network.

  4. Determine the Jetson’s IP address without using a monitor or keyboard.

    Since this is a headless setup, do not rely on local login. Use one of the following host-side methods:

    Scan the local subnet

    1. On the host, identify the active Ethernet interface and subnet:
    ip addr show eth0
    

    Example output:

    inet 10.42.0.1/24
    
    1. Scan the subnet for live devices: Install nmap if not already present: sudo apt install nmap
    nmap -sn 10.42.0.0/24
    

    Identify the Jetson by vendor or as a newly appeared IP (typically 10.42.0.xxx).

  5. Return to NVIDIA SDK Manager on the host.

  6. Provide:

    • Target IP address
    • Target username
    • Target password
  7. Resume and complete SDK component installation.

Once this step finishes, the Jetson is fully operational and no longer depends on the host.

Note: The same IP address is used for SSH, SCP, and VNC access going forward.


Part 2:

Setting Up VNC for Remote Desktop Access * GNOME Edition

GNOME VNC

NVIDIA’s readme docs suggested Vino for VNC access, but Vino is a screen-sharing tool designed for systems with monitors already attached. In truly headless setups, it either fails to start or breaks unpredictably across reboots. x11vnc directly mirrors the actual X11 display GNOME renders to, treating our synthetic display as real hardware which is exactly what we need. It’s more stable, survives updates without drift, and is the approach NVIDIA engineers themselves recommend on their forums for headless production use. GNOME expects a real display, valid EDID, and an active login session. GNOME cannot run headlessly without a valid display pipeline. This setup mirrors the real :0 display using x11vnc.

This Nvidia forum post was the inspiration for this method, but we are not doing it exactly the same way. Here they have used monitor for a setting change, we are doing it completely headless.

The approach is explicit:

  • Force GNOME to start with a valid EDID
  • Ensure a real :0 display exists
  • Attach x11vnc to that display at boot

Prerequisites

  • GNOME desktop installed and enabled
  • Root access
  • SSH access (since this is headless)
  • A known-good EDID from any working monitor (Host)
  • A VNC client on your host machine

This setup is commonly used on Jetson devices, but applies to any NVIDIA system running GNOME with the proprietary driver.

Step 1: Install and Initialize x11vnc

Install x11vnc and set a VNC password:

sudo apt update
sudo apt install x11vnc -y
x11vnc -storepasswd

x11vnc does not create a display. It attaches to an existing one. Everything that follows exists to guarantee that display.

Step 2: Force Graphical Boot Target

GNOME must start at boot, even without a monitor.

sudo systemctl set-default graphical.target
sudo reboot

If this step is skipped, x11vnc will start successfully, and attach to nothing.

Step 3: Extract EDID from a Working Monitor (Host Side)

On any Linux system with a monitor attached (your host machine):

xrandr --props

EDID Extraction

Locate the EDID: block, copy only the hex values, and paste them into a file without spaces or line breaks:

nano edid.hex

EDID Hex File

Convert it to binary:

xxd -r -p edid.hex EDID.bin

Verify the EDID:

edid-decode EDID.bin

EDID Decode You should see a valid output describing the monitor capabilities.

Step 4: Install EDID on the Target (Jetson)

⚠️ Use an EDID from a monitor that supports the resolution you intend to use. Invalid EDIDs will cause GDM to fail silently.

Copy the EDID to the Jetson:

scp EDID.bin YOUR_USER@jetson:/tmp

Move it into firmware location:

sudo mkdir -p /lib/firmware/edid
sudo cp /tmp/EDID.bin /lib/firmware/edid/EDID.bin

Step 5: Replace Xorg Configuration (Critical)

⚠️ Back up the existing config:

sudo cp /etc/X11/xorg.conf /etc/X11/xorg.conf.backup
sudo nano /etc/X11/xorg.conf

Replace the entire file with the following config:

If /etc/X11/xorg.conf does not exist, create it. Jetson images often rely on autogenerated defaults.

Section "ServerLayout"
    Identifier     "Layout0"
    Screen      0  "Screen0" 0 0
    InputDevice    "Keyboard0" "CoreKeyboard"
    InputDevice    "Mouse0" "CorePointer"
EndSection

Section "Module"
    SubSection     "extmod"
        Option         "omit xfree86-dga"
    EndSubSection
    Load           "glx"
    Disable        "dri"
EndSection

Section "InputDevice"
    Identifier     "Keyboard0"
    Driver         "kbd"
EndSection

Section "InputDevice"
    Identifier     "Mouse0"
    Driver         "mouse"
    Option         "Protocol" "auto"
    Option         "Device" "/dev/mouse"
    Option         "Emulate3Buttons" "no"
    Option         "ZAxisMapping" "4 5"
EndSection

Section "Device"
    Identifier     "Tegra0"
    Driver         "nvidia"
    Option         "AllowEmptyInitialConfiguration" "true"
    Option         "ConnectedMonitor" "DFP-0"
    Option         "CustomEDID" "DFP-0:/lib/firmware/edid/EDID.bin"
    Option         "UseDisplayDevice" "DFP-0"
    Option         "IgnoreEDIDChecksum" "DFP-0"
    Option         "ModeValidation" "AllowNonEdidModes"
EndSection

Section "Monitor"
    Identifier     "Monitor0"
    VendorName     "FakeVendor"
    ModelName      "Fake1920x1080"
    Option         "DPMS" "false"
EndSection

Section "Screen"
    Identifier     "Screen0"
    Device         "Tegra0"
    Monitor        "Monitor0"
    DefaultDepth    24
    SubSection     "Display"
        Depth       24
        Modes      "1920x1080"
    EndSubSection
EndSection

Restart GNOME:

sudo systemctl restart gdm3

Sanity check:

DISPLAY=:0 xrandr

If this fails, VNC will never work. Fix this first.

Step 6: Enable GNOME Auto-Login (Mandatory)

x11vnc requires an active user session. No login → no desktop.

Edit GDM configuration:

sudo nano /etc/gdm3/custom.conf

Enable auto-login:

[daemon]
AutomaticLoginEnable=true
AutomaticLogin=YOUR_USERNAME

Reboot:

sudo reboot

Verify again:

DISPLAY=:0 xrandr

Step 7: Create a systemd Service for x11vnc

Create the service file:

sudo nano /etc/systemd/system/x11vnc.service

Paste the following:

[Unit]
Description=Start x11vnc at startup
Requires=display-manager.service
After=display-manager.service

[Service]
Type=simple
User=YOUR_USERNAME
ExecStart=/usr/bin/x11vnc -auth guess -display WAIT:0 -loop -usepw -forever -rfbport 5900
Restart=on-failure

[Install]
WantedBy=graphical.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable x11vnc.service
sudo systemctl start x11vnc.service

Finally, Connect via VNC Viewer

Install a VNC viewer on your host machine (e.g., tigervnc-viewer, RealVNC, etc.) I recommend tigervnc-viewer for its simplicity and performance. Or RealVNC for its user-friendly interface.

From your host machine:

ssh -N -L 5900:localhost:5900 yourusername@JETSON_IP
vncviewer localhost:5900

You should now see the “real” GNOME desktop, hardware-accelerated, exactly as if a monitor were attached.

If you see a blank screen or errors, revisit the EDID and Xorg configuration steps. These are proven to be the most fragile parts of this setup. Many people reported rectifying in EDID and xorg.conf fixed their issues.


Part 3:

Setting Up VNC for Remote Desktop Access * XFCE Edition

XFCE VNC

This is completely optional. If you prefer GNOME, skip this section. I keep it here for completeness and serves as a backup plan if GNOME VNC setup fails by some updates in future.

I like XFCE so much (for now at least) that I run it on my main desktop machine as well, of course with some slick customizations. I daily drive Xubuntu 22.04 and the experience has been buttery smooth compared to GNOME and Wayland bloat. I even tried KDE but later settled with XFCE for its simplicity and speed.

If you want reliable headless VNC without GNOME’s complexity, XFCE is the pragmatic choice. XFCE is lightweight, predictable, and works cleanly with TigerVNC using a user-level systemd service. No EDID hacks, no display manager dependencies, no GPU quirks.

Why XFCE + TigerVNC

  • No dependency on a physical display
  • No Xorg/Wayland/GDM edge cases
  • Clean separation from system display managers
  • Works perfectly over SSH tunnels
  • Ideal for Jetson, servers, and lab machines

Step 1: Install XFCE (Target Machine)

Install the XFCE desktop environment:

sudo apt update
sudo apt install -y xfce4 xfce4-goodies

Step 2: Install TigerVNC

sudo apt install -y tigervnc-standalone-server tigervnc-common

Set a VNC password:

vncpasswd

This creates ~/.vnc/passwd, which TigerVNC will use for authentication.

Step 3: Quick Sanity Test

Start a temporary VNC server:

tigervncserver :1

If this launches without errors, the core components are working. Stop it afterward:

tigervncserver -kill :1

XFCE runs on a separate X display (:1) and does not interfere with GNOME (:0). Both can coexist safely. Also that the port 5900 is for GNOME VNC, while 5901 is for XFCE VNC. Adjust accordingly based on which desktop you are using.

Step 4: Disable Legacy VNC Startup Scripts

Remove or empty any existing VNC startup files to avoid race conditions with systemd:

rm -f ~/.vnc/xstartup

TigerVNC will be launched and managed only via systemd from this point onward.

Step 5: Create a User-Level systemd Service

Create the user systemd directory if it doesn’t exist:

mkdir -p ~/.config/systemd/user
nano ~/.config/systemd/user/tigervnc.service

Paste the following service definition (adjust username if needed):

[Unit]
Description=TigerVNC server (Xfce headless)
After=network.target

[Service]
Type=simple
Environment=DISPLAY=:1

ExecStart=/usr/bin/Xtigervnc \
  :1 \
  -localhost \
  -SecurityTypes VncAuth \
  -rfbauth /home/yourusername/.vnc/passwd \
  -geometry 1920x1080

ExecStartPost=/bin/sh -c 'sleep 2; DISPLAY=:1 dbus-launch --exit-with-session xfce4-session & disown'

Restart=on-failure
RestartSec=3

[Install]
WantedBy=default.target

Why this works

  • Xtigervnc creates a real X server
  • XFCE runs inside that server
  • dbus-launch ensures session services work correctly
  • No dependency on login managers or GPUs

Step 6: Enable User Lingering (Critical)

By default, user services do not start unless the user logs in. Headless systems need lingering enabled.

sudo loginctl enable-linger yourusername

Verify:

loginctl show-user yourusername | grep Linger

Expected output:

Linger=yes

If this is not set, your VNC server will not start after reboot.

Step 7: Enable and Start the Service

Reload user services and enable TigerVNC:

systemctl --user daemon-reload
systemctl --user enable tigervnc
systemctl --user start tigervnc

Check status:

systemctl --user status tigervnc

Reboot to confirm persistence:

sudo reboot

Step 8: Access VNC Securely Over SSH

Same here, use an SSH tunnel to secure your VNC connection.

From your host machine:

ssh -N -L 5901:localhost:5901 yourusername@JETSON_IP
vncviewer localhost:5901

This keeps VNC off the network and avoids exposing port 5901.


Final Notes

Troubleshooting

If you encounter issues, First do a google search to see if there are nvidia forum posts already discussing your problem. Keep LLM’s assistance as the last resort. as often I see it can go crazy and overcomplicate things. Its easy to brick the firmware if you blindly follow instructions without understanding them with all those sudo commands and no backups.

Ports & Displays

Display :0  → GNOME + x11vnc → TCP 5900
Display :1  → XFCE + TigerVNC → TCP 5901

Security Note

Do not expose VNC ports directly to the network. Always use SSH tunneling.