# Installing Ubuntu Server on M.2 Drives for Pi5 SBCs.

Updated: Tuesday 14th October 2025

## TL;DR.

This post is a guide to upgrading Pi5 SBCs (Raspberry Pi 5 Single Board Computers) from using microSD cards to employing M.2 NVMe drives.

> **Attributions:**
> 
> ***None ↗.***

---

## An Introduction.

Upgrading from using a microSD card on a Pi5 SBC to using an M.2 NVMe drive on the same device can really open up the storage capability of the Raspberry Pi platform:

> The purpose of this post is to demonstrate how to install an OS onto an M.2 NVMe drive for a Pi5 SBC.

---

## The Big Picture.

There is a redundancy requirement when assembling an HA (High Availability) cluster. Many different flavours of Kubernetes (K8s, K3s, MicroK8s, K0s, and even Minikube) support HA clustering and I will use my Homelab hardware, such as it is, to practice the skills of deploying HA solutions. Using Pi5 SBCs as HA Master Nodes (sometimes called Control Planes) is fine due to the low resource requirements. Worker Nodes (sometimes called Data Planes), on the other hand, consumes many resources (CPU cycles, RAM, bandwidth, etc.) due to processing resource requests. Not only do Worker Nodes need to send a LOT of data, but they must also spin up more nodes when the demand increases. More nodes means more resources consumed.

For M.2 NVMe drives, there is a speed difference between the (older) SATA interface and the (newer) NVMe interface. The drives I use in this post are [NVMe PCIe Gen3 x4](https://www.transcend-info.com/product/internal-ssd/mte400s) ***↗.*** The M.2 NVMe enclosures I use also support NVMe Drives. Also, the hats that are installed in the Pi5 SBCs are also NVMe compatible.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750298647369/f9bca2be-1ddf-4760-937d-8121403d925d.jpeg align="center")

For now, my focus is on installing a Linux distro on M.2 NVMe drives that will be used by Pi5 SBCs.

---

## Prerequisites.

* A Debian-based Linux distro (I use Ubuntu),
    
* 3 x Pi5 single board computers,
    
* 3 x Pi5 active coolers,
    
* 3 x M.2 NVMe drives,
    
* 3 x Pi5 NVMe hats,
    
* An M.2 NVMe enclosure,
    
* 3 x CAT-5 Ethernet cables, and
    
* Access to an Internet router, or a LAN switch, that is connected to the Internet router.
    

---

## Updating my Base System.

* From the (base) terminal, I update my (base) system:
    

```python
sudo apt clean && \
sudo apt update && \
sudo apt dist-upgrade -y && \
sudo apt --fix-broken install && \
sudo apt autoclean && \
sudo apt autoremove -y
```

> NOTE: The Ollama LLM manager is already installed on my (base) system.

---

## What is the Raspberry Pi Imager?

Raspberry Pi Imager is a user-friendly application that allows me to easily install Raspberry Pi OS, or any other compatible operating system, onto a microSD card for use with Raspberry Pi devices. It simplifies the process of selecting the OS and writing it to the card, ensuring that the installation is done correctly.

In my case, I will be installing Ubuntu Server LTS 24.04 onto an M.2 NVMe drive using an M.2 NVMe enclosure.

---

## Installing Pi Imager.

* From the terminal, I install the Raspberry Imager:
    

```bash
sudo apt install -y rpi-imager
```

---

## Creating an Ubuntu Server M.2 NVMe Drive.

> NOTE: For this process, I use the Raspberry Imager software, 3 x M.2 NVMe drives, an M.2 NVMe enclosure, 3 x M.2 NVMe hats for Pi5 SBCs, and 3 x Pi5 SBCs.

* I purchase three (3) M.2 NVMe drives, 1 for each Pi5, from my [local computer store](https://www.pbtech.co.nz/):
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237202424/f1a06ee3-f772-4aec-8edd-617e0502c884.jpeg align="center")

* I install an M.2 NVMe drive into an M.2 NVMe enclosure:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237554508/f5b10a96-6a71-4772-8eaa-12f8e4679ea2.jpeg align="center")

* I connect the M.2 NVMe enclosure to my PC:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237248190/5a859512-6a0b-4872-aedc-f5232ea0d709.jpeg align="center")

* I start the Raspberry Pi Imager:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237638020/1b09806f-86be-4a80-9115-6325022ef641.png align="center")

* I select `Raspberry Pi 5` as my `Raspberry Pi Device`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237716571/eed81614-975b-4b6a-9899-0d52ebcaf01b.png align="center")

* I select `Ubuntu Server 24.04.2 LTS (64-bit)` as the `Operating System`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750237860148/d82dbc0b-a471-45cf-ae39-8220aaa6a899.png align="center")

* I select the M.2 NVMe enclosure (`Realtek TS512GMTE440S`) as the `Storage`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238178870/85b94c7e-279b-4735-948f-3cce1d169969.png align="center")

> NOTE: `TS512GMTE440S` is the product ID for the M.2 NVMe drive.

* I click the `NEXT` button:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238234718/6366d384-bcf4-406b-ba4f-109a33e9e868.png align="center")

* From the `Use OS customisation?` dialog, I click the `EDIT SETTINGS` button to open the `OS Customisation` dialog:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238326574/7eef9532-d867-4a26-99e9-6044bbdbfeec.png align="center")

* In the `GENERAL` tab, I use the following settings:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238431013/a499f217-9ee3-46bd-82c9-2f7635ca8a4b.png align="center")

* In the `SERVICES` tab, I use the following settings:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238539770/8973443b-f604-4108-8a03-04df31adfa72.png align="center")

> NOTE: Setting the public key encryption for SSH will be described in a later post.

* In the `OPTIONS` tab, I use the following settings:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238705997/00586088-d71e-4797-a5a3-2f300b7efc50.png align="center")

* I click the `SAVE` button to return to the previous dialog.
    
* Back in the `Use OS customisation?` dialog, I click the `YES` button.
    
* I read the `Warning` dialog, then click the `YES` button:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750239099595/57ae6f33-fd6e-4952-a4c5-bb7eb573d255.png align="center")

* The Raspberry Pi Imager installs Ubuntu Server 24.4.2 LTS (64-bit) onto the M.2 NVMe drive.
    
* After the image is written to the M.2 NVMe drive, the M.2 NVMe enclosure is automatically ejected from my PC:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750239587367/10048509-d223-40c3-a97e-128d3e057a48.png align="center")

> NOTE: The M.2 NVMe enclosure is automatically ejected from my PC due to this setting that was enabled earlier:
> 
> ![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750238705997/00586088-d71e-4797-a5a3-2f300b7efc50.png align="left")

* I unplug the M.2 NVMe enclosure from my PC.
    
* I remove the M.2 NVMe drive from the M.2 NVMe enclosure.
    
* I install the M.2 NVMe drive into the M.2 NVMe hat that I previously added to my Pi5 SBC.
    
* I repeat this process 2 more times (along with making appropriate changes to the settings):
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750240797053/5e0f9434-799f-4faf-a678-a607f83db6a1.jpeg align="center")

> NOTE: Now that the M.2 NVMe drives have been imaged and installed, I now need to provide the Pi5 SBCs with a new boot order.

---

## Creating a MicroSD Card Bootloader.

> NOTE: For this process, I use the Raspberry Imager software, a microSD card and a USB card reader.

* I insert a microSD card into the USB card reader.
    
* I insert the USB card reader into my PC.
    
* I start the Raspberry Pi Imager:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753067748978/5a7ba162-401a-4952-bf03-da3972ef502b.png align="center")

* I set the `Raspberry Pi Device` to `Raspberry Pi 5`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753072466146/ba250138-712d-4107-88f7-b3f8710bfd8a.png align="center")

* I set the `Operating System` to `Misc utility images > Bootloader (Pi 5 family) > SD Card Boot`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753072665479/8c46e23a-2afc-4a62-8b51-c0d0adf8342f.png align="center")

* I set the `Storage` to the `SD Card Reader - 31.3 GB`:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753072746388/817d11f1-b8ba-40c1-a903-a648a690d41b.png align="center")

> NOTE: 31.3 GB is the usable capacity of the microSD card inserted in the SD Card Reader.

* I click the `NEXT` button:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753072843724/00e79116-ba7e-4624-af60-9354b58f0baa.png align="center")

* I read the `Warning` dialog and then click the `YES` button:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753074354974/d6621b7d-2e5e-4340-95fe-c2005b28c6a1.png align="center")

* It takes less than 20-seconds for the Raspberry Pi Imager to finish:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1753074136559/0c1f2717-b912-4296-8c57-f685b0700b7e.png align="center")

* I remove the USB reader from my PC.
    
* I remove the microSD card from the USB card reader.
    

---

## Changing the Pi5 Boot Order.

* I insert the microSD card into the Pi5 microSD card slot.
    
* I power on the Pi5.
    
* I wait for the microSD card to rewrite the bootloader in the EEPROM of the Pi5.
    

> NOTE: Rewriting the settings for the bootloader only takes a few seconds.

* A successful rewrite is shown by a blinking, green LED and a green screen displayed on the monitor. (A failed process is denoted by a blinking, red LED and a red screen displayed on the monitor.)
    
* I repeat this process for the remaining Pi5 SBCs.
    

> NOTE: If there is no bootable microSD card in the Pi5, then it will try to boot from an M.2 NVMe drive. If there is no bootable M.2 NVMe drive, the it will try to boot from a USB drive.

---

## What is a Raspberry Pi?

A Raspberry Pi is a single-board computer, or SBC, that is a little larger than a credit card, specifically 85mm x 56mm in size. It looks like a miniature motherboard but all the components, like the CPU, memory, wireless module, USB ports, and the network port are already (and permanently) installed. Hardware upgrades are enabled through the use of ‘hats’. A hat is a circuit board that is installed on top of, or below, the Raspberry Pi. Hats have pins that electrically connect to the header of the Pi, and may also include a ribbon cable that also provides a connection to the SBC.

---

## Assembling a Pi5 that Supports an M.2 NVMe Drive.

* If installed, I remove the passive heat sink from the Pi5 CPU.
    
* I install the active cooler and attach the fan cable to the fan header of the Pi5.
    
* I remove a specific screw from the active cooler fan.
    
* I connect the NVMe hat to the Pi5 header and use the included long screw to attach it to the active cooler fan.
    
* I use the NVMe ribbon cable to connect the NVMe hat to the Pi5.
    
* I install the M.2 NVMe drive, the drive with Ubuntu Server 24.4 LTS installed, into the NVMe hat and use the included short screw to secure the drive.
    
* On the case, I pop out the case fan because it will get in the way of the NVMe hat.
    
* I install the assembled Pi5 into the Pi case.
    
* I repeat this process 2 more times:
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1750298738801/ee9af3c0-a2ab-4773-be2e-5fed79ad57a7.jpeg align="center")

---

## Setting Up the Local Terminal.

The following describes setting up the remote PiLab servers, two of which will become the control plane nodes of the MicroK8s cluster. These settings must also be applied to the [eight NucLab containers](https://solodev.app/installing-proxmox-ve-on-an-intel-nuc-10#heading-creating-a-new-account-for-the-container) that will become the worker nodes of the MicroK8s cluster.

---

## Creating an RSA Key Pair on the Local PC.

* From my local, PC terminal (`CTRL` + `ALT` + `T`), I start the ssh-agent:
    

```bash
eval "$(ssh-agent -s)"
```

* I generate a pair of RSA keys called "/home/brian/.ssh/key-name" (where I replace "key-name" with the name of the remote server):
    

```bash
ssh-keygen -b 4096
```

> NOTE: It is my convention to name RSA keys after the remote server on which they will be used.

* I add the SSH key to my workstation account (where I replace "key-name" with the *actual* name of the ssh key):
    

```bash
ssh-add /home/brian/.ssh/key-name
```

---

## Uploading the Public Key to the Remote Server.

* From the `workstation` terminal (`CTRL` + `ALT` + `T`), I use "ssh-copy-id" to upload the locally-generated public key to the remote container (where I replace "container-name" with the *actual* name of the container):
    

```bash
ssh-copy-id -i /home/brian/.ssh/container-name.pub yt@192.168.?.?
```

> NOTE: I replace the "?" with the actual IP address for the remote server.

---

## Logging In to the Remote Server.

* From the terminal (CTRL + ALT + T), I login to the account of the remote server:
    

```bash
ssh 'yt@192.168.?.?'
```

> NOTE: I replace the "?" with the actual IP address for the remote server.

---

## Updating the Remote Server.

* From the terminal, I update the remote server:
    

```python
sudo apt clean && \
sudo apt update && \
sudo apt dist-upgrade -y && \
sudo apt --fix-broken install && \
sudo apt autoclean && \
sudo apt autoremove -y
```

---

## Hardening the Remote Server.

* From the terminal (CTRL + ALT + T) that is connected to the remote server, I open the "sshd\_config" file:
    

```bash
sudo nano /etc/ssh/sshd_config
```

* I add (CTRL + V) the following to the bottom of the "sshd\_config" page, save (CTRL + S) the changes, and exit (CTRL + X) the Nano text editor:
    

```bash
PasswordAuthentication no
PermitRootLogin no
Protocol 2
```

* I restart the "ssh" service:
    

```bash
sudo systemctl restart ssh.service
```

---

## Enabling, and Setting Up, UFW on the Remote Server.

* From the PC terminal (`CTRL` + `ALT` + `T`) that is connected to the remote server, I check the UFW status:
    

```bash
sudo ufw status
```

* I enable the UFW:
    

```bash
sudo ufw enable
```

* I install a UFW rule:
    

```bash
sudo ufw allow from 192.168.?.?
```

> NOTE: I can use `ip a` or `ip addr` in my local PC terminal to find my IP address. ***I replace the IP address above with the actual address for the*** `workstation`***, e.g. 192.168.188.41.***

* I check the status of the UFW and list the rules by number:
    

```bash
sudo ufw status numbered
```

> NOTE 1: UFW will, by default, block all incoming traffic, including SSH and HTTP.
> 
> NOTE 2: I will update the UFW rules as I deploy other services to the remote server.

* I can delete a UFW rule by number if needed:
    

```bash
sudo ufw delete 1
```

* I can also disable UFW if needed:
    

```bash
sudo ufw disable
```

---

## Installing, and Setting Up, Fail2Ban on the Remote Server.

* From the terminal (CTRL + ALT + T) that is connected to the remote server, I install Fail2Ban:
    

```bash
sudo apt install -y fail2ban
```

* I copy the `jail.conf` file as `jail.local`:
    

```bash
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
```

* I open the `jail.local` file in Nano:
    

```bash
sudo nano /etc/fail2ban/jail.local
```

* I make the following changes to a few (SSH-centric) settings in the `jail.local` file, then I save (CTRL + S) those changes, and exit (CTRL + X) the Nano text editor:
    

```bash
[DEFAULT]
⋮
bantime = 1d
maxretry = 3
⋮
[sshd]
enabled = true
port = ssh,22
```

* I restart Fail2Ban:
    

```bash
sudo systemctl restart fail2ban
```

* I check the status of Fail2Ban:
    

```bash
sudo systemctl status fail2ban
```

* I enable Fail2Ban to auto-start on boot:
    

```bash
sudo systemctl enable fail2ban
```

* I reboot the remote server:
    

```bash
sudo reboot
```

---

## My Use Case.

My plan is to use PiLab51 as the primary control plane, PiLab52 as the secondary control plane, and PiLab53 as a general server for hosting GitLab CE, PiHole, and other services. The NucLab systems will host ProxMox VE which, themselves, will host four containers each. These eight containers (total) will act as the worker nodes which, themselves, will host the pods. Although a pod is the smallest deployable unit that can contain one or more containers, the three PiLab SBCs, along with the eight ProxMox containers, collectively make up my local cluster.

---

## The Results.

Transitioning from microSD cards to M.2 NVMe drives for my Pi5 SBCs significantly enhanced the storage capabilities and performance of my Raspberry Pi setup. By following the steps outlined in this post, I successfully installed Ubuntu Server 24.04 LTS on multiple M.2 NVMe drives, paving the way for more robust and efficient use of the Pi5 platform in various applications, including HA (high availability) clustering. This upgrade not only improved data handling and processing speed but also provided a more reliable and scalable solution for my projects. As I continue to explore and implement these technologies, I will adapt and customize my setup to meet my specific needs and objectives.

---

## In Conclusion.

I learned how to enhance the storage capabilities my Raspberry Pi 5 SBCs by replacing microSD cards with M.2 NVMe drives. This post covered my prerequisites, the installation steps, and suggested a practical application where Pi5 SBCs can be used in an HA cluster. The Pi5 platform is a potentially strong and flexible, though expensive, resource for Homelab enthusiasts.

Until next time: Be safe, be kind, be awesome.

---

## Hash Tags.

#RaspberryPi #Pi5 #SingleBoardComputer #M2Drive #UbuntuServer #ServerInstallation

#Kubernetes #HighAvailability #Homelab #StorageSolutions #OpenSource #DIYTech

#TechTutorial #TechUpgrade #TechEnthusiast
