Fixing NVIDIA Drivers Under Secure Boot and Preventing Windows from Breaking EFI Boot Order

Running Linux with Secure Boot in a multi-boot machine often turns into a struggle. In my case the system is set up with KDE Neon as the primary OS, Kubuntu for testing, and Windows as the third option. Windows assumes it should control the EFI boot order and sometimes even clears MOK keys when it writes to firmware. At the same time, NVIDIA drivers do not always cooperate with Secure Boot because the modules are unsigned. The result is that Linux may fail to load the GPU driver after every reboot into Windows, and boot order often shifts away from Neon. This tutorial explains the full process I followed to solve these issues in a reliable way.

1. Identify the correct NVIDIA driver and install a DKMS build.
 
The first requirement is to get the correct NVIDIA driver installed. Using a DKMS package is important because DKMS automatically rebuilds the modules whenever the kernel changes. Without DKMS, every kernel upgrade would break the driver until you reinstall it. Installing the recommended driver through Ubuntu’s tools saves guesswork. Once installed, you have a working baseline driver that can later be signed for Secure Boot.

List suggestions from Ubuntu:
ubuntu-drivers list
Install the recommended one automatically:
sudo ubuntu-drivers autoinstall
Or install a specific version, replace XXX with the version number:
sudo apt install nvidia-driver-XXX
Verify the installed package:
dpkg -l | grep nvidia-driver

2. Pin the driver so apt does not change it later.
 
Ubuntu and its derivatives update packages frequently, and sometimes this means the NVIDIA driver gets replaced with another version. If that happens you may lose stability or break Secure Boot signing. Placing the driver package on hold tells apt to stop upgrading or switching it automatically. You can still change the version manually later if you need to. For now it locks in the version that you know works correctly.

Replace XXX with your version.
sudo apt-mark hold nvidia-driver-XXX
Check that hold is active:
apt-mark showhold

3. Create and enroll a MOK key for Secure Boot module signing.

 
Secure Boot will only load modules that are signed by a trusted key. DKMS builds the NVIDIA modules locally, so they are unsigned and the kernel rejects them. Create your own X.509 key pair and enroll the public key in the MOK database via shim’s MokManager. This writes the key to UEFI MOK variables and shim imports it into the kernel’s trusted keyring at boot.

I keep keys in ~/mok and then copy to /etc/secureboot.
Generate the keypair in your home directory::
mkdir -p ~/mok 
cd ~/mok 
openssl req -new -x509 -newkey rsa:2048 -keyout MOK.priv -outform DER -out MOK.der -nodes -days 36500 -subj "/CN=Local Secure Boot MOK/"
Enroll the public key with shim. You will confirm on next reboot in the blue MOK manager screen:
sudo mokutil --import MOK.der
After enrolling and rebooting, verify state:
mokutil --sb-state 
mokutil --list-enrolled | grep -A2 "Local Secure Boot MOK"

Copy the keypair to a secure system location so the signing script can always find it.
Preferred location:
sudo cp ~/mok/MOK.priv ~/mok/MOK.der /etc/secureboot/
sudo chmod 600 /etc/secureboot/MOK.* 
Alternative fallback location:
sudo cp ~/mok/MOK.* /root/ 
sudo chmod 600 /root/MOK.* 
4. Install the signing script that signs NVIDIA DKMS modules after builds.

Manually signing every module after a kernel update is time-consuming and easy to forget. A simple shell script can automate the task by scanning the NVIDIA modules and signing them with your MOK key. It also handles compressed files, so it works across Ubuntu versions. Once in place you can run it by hand whenever you need. Later it will be connected to DKMS so the process happens automatically.

This script handles both .ko and .ko.zst. It uses the kernel's sign-file helper.
Create /usr/local/bin/sign-nvidia-modules:
sudo tee /usr/local/bin/sign-nvidia-modules >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

MOK_PRIV="${MOK_PRIV:-/etc/secureboot/MOK.priv}"
MOK_DER="${MOK_DER:-/etc/secureboot/MOK.der}"

# Fallback if keys are still in root:
if [[ ! -f "$MOK_PRIV" || ! -f "$MOK_DER" ]]; then
  if [[ -f "/root/MOK.priv" && -f "/root/MOK.der" ]]; then
    MOK_PRIV="/root/MOK.priv"
    MOK_DER="/root/MOK.der"
  fi
fi

# Find sign-file
SIGN_FILE=""
for CAND in "/usr/src/linux-headers-$(uname -r)/scripts/sign-file" "/lib/modules/$(uname -r)/build/scripts/sign-file"; do
  [[ -x "$CAND" ]] && SIGN_FILE="$CAND" && break
done
if [[ -z "$SIGN_FILE" ]]; then
  echo "sign-file helper not found. Install linux-headers-$(uname -r)." >&2
  exit 1
fi

if [[ ! -f "$MOK_PRIV" || ! -f "$MOK_DER" ]]; then
  echo "MOK keypair not found at $MOK_PRIV / $MOK_DER" >&2
  exit 1
fi

KVER="${1:-$(uname -r)}"
MODDIR="/lib/modules/${KVER}/updates/dkms"
[[ -d "$MODDIR" ]] || { echo "No DKMS dir at $MODDIR. Nothing to sign."; exit 0; }

shopt -s nullglob
mapfile -t MODS < <(find "$MODDIR" -type f \( -name "*.ko" -o -name "*.ko.zst" \) | sort)

if (( ${#MODS[@]} == 0 )); then
  echo "No modules to sign under $MODDIR."
  exit 0
fi

echo "[nvidia-sign] Using: $SIGN_FILE"
for m in "${MODS[@]}"; do
  if [[ "$m" == *.ko.zst ]]; then
    echo "[nvidia-sign] Decompressing $m"
    /usr/bin/zstd -d --rm "$m"
    m="${m%.zst}"
  fi
  echo "[nvidia-sign] Signing $(basename "$m")"
  "$SIGN_FILE" sha256 "$MOK_PRIV" "$MOK_DER" "$m"
  if command -v zstd >/dev/null; then
    /usr/bin/zstd -T0 -q -f "$m"
  fi
done

depmod "$KVER"
echo "[nvidia-sign] Done for $KVER"
EOF
Secure the keys and script permissions:
sudo mkdir -p /etc/secureboot
sudo cp ~/mok/MOK.* /etc/secureboot/
sudo chmod 600 /etc/secureboot/MOK.*
sudo chmod 755 /usr/local/bin/sign-nvidia-modules

5. Hook DKMS so every build is auto-signed. 
 
DKMS has the ability to call scripts after it finishes building a module. By placing the signing script as a post-build hook, every new NVIDIA module is signed the moment it is compiled. This ensures that the driver remains usable after every kernel update without you having to remember anything. The hook is only a small wrapper but it saves a lot of manual work. It is also safer because you avoid forgetting to sign and then booting into a broken system.

Create a post-build hook:
sudo install -d -m 755 /etc/dkms/post-build.d
sudo tee /etc/dkms/post-build.d/zz-nvidia-sign >/dev/null <<'EOF'
#!/usr/bin/env bash
# DKMS sets kernelver or KERNELRELEASE
KV="${kernelver:-${KERNELRELEASE:-}}"
if [[ -n "$KV" ]]; then
  /usr/local/bin/sign-nvidia-modules "$KV"
else
  /usr/local/bin/sign-nvidia-modules
fi
EOF
sudo chmod 755 /etc/dkms/post-build.d/zz-nvidia-sign
Force a DKMS rebuild to test the hook:
sudo dkms autoinstall

6. Verify signatures and module load.
 
After setting up automatic signing you should confirm that it is working. The command modinfo shows the digital signature on each module, including the name of the signer. In your case it should display the MOK you enrolled earlier. You should also check with lsmod that the NVIDIA modules are active in memory. This double check makes sure both the signing and the driver loading are functioning properly.

Check signature fields:
modinfo /lib/modules/$(uname -r)/updates/dkms/nvidia.ko 2>/dev/null | grep -E "signer|sig_key|sig_hash"
Expected signer:
signer: Local Secure Boot MOK
Confirm modules are present:
lsmod | grep -E "^nvidia|nvidia_drm|nvidia_modeset"

7. Prevent Windows from changing EFI BootOrder and MOK lists.

The biggest external problem is Windows, which writes to EFI variables every time it boots. This can reset your boot order and sometimes even clear your enrolled MOK keys. Disabling Fast Startup reduces this risk but does not always eliminate it. A stronger option is to lock the EFI variables from Linux so Windows cannot change them. By freezing BootOrder and MOK variables you make sure your Linux system always starts first and the keys remain intact.


In Windows, disable Fast Startup:
powercfg /h off
Back in Linux, set the correct boot order and then freeze EFI variables. First, list and note IDs like Boot0002 for neon and Boot0000 for Windows:
sudo efibootmgr -v
Set neon first, example only:
sudo efibootmgr -o 0002,0000
Make variables immutable so Windows cannot rewrite them:
sudo chattr +i /sys/firmware/efi/efivars/BootOrder-*
sudo chattr +i /sys/firmware/efi/efivars/Boot0002-*
sudo chattr +i /sys/firmware/efi/efivars/Boot0000-*
Protect MOK lists too:
sudo chattr +i /sys/firmware/efi/efivars/MokList*
sudo chattr +i /sys/firmware/efi/efivars/MokSB*
If you need to edit later, remove immutability first:
sudo chattr -i /sys/firmware/efi/efivars/BootOrder-*

8. Common problems and fixes.
 
Even with everything set up, small issues can appear after major updates. Sometimes kernel headers are missing which prevents the modules from compiling. Other times the DKMS hook does not run because of a permission problem. The fixes are usually simple, such as reinstalling headers or rerunning the signing script. Knowing the common problems helps avoid panic when something goes wrong.

Secure Boot denies NVIDIA after a kernel update:
mokutil --list-enrolled | grep -A2 "Local Secure Boot MOK"
sudo apt install "linux-headers-$(uname -r)"
sudo /usr/local/bin/sign-nvidia-modules
DKMS hook did not run:
sudo chmod 755 /etc/dkms/post-build.d/zz-nvidia-sign
sudo dkms autoinstall
Following these steps ensures that NVIDIA drivers load under Secure Boot and remain stable across updates. You keep control over which driver version is used, and you have a trusted key that allows the modules to load. Automatic signing through DKMS prevents future problems after kernel upgrades. Protecting EFI variables stops Windows from undoing your work. Together these measures create a reliable dual-boot setup where both Linux and Windows function without conflict.

Comments