When a server runs out of space or you want to keep application data apart from the operating system, the usual answer is a second disk. Before Linux can use it, the disk needs a partition table, a filesystem, a mount point and an entry in /etc/fstab so it comes back after a reboot. In this tutorial you will do all of that on Ubuntu 24.04, ending with a new ext4 disk mounted at /mnt/data that survives reboots and does not block the boot if it ever goes missing.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with a second, empty disk attached. The same commands work on Debian 12 and Rocky Linux 9.
- A non-root user with
sudoprivileges. - A backup of any important data. Formatting the wrong device destroys its contents, so read every device name twice before pressing Enter.
WarningEvery command in Steps 2 and 3 erases the target disk. Only run them against the new, empty disk you identify in Step 1.
Step 1 - Identifying the new disk
Linux names disks by the driver that exposes them. On KVM virtual machines using VirtIO they appear as /dev/vda, /dev/vdb and so on; on SCSI, SATA or many cloud platforms they are /dev/sda, /dev/sdb; NVMe drives appear as /dev/nvme0n1, /dev/nvme1n1. List the disks without their partitions:
lsblk -d -o NAME,SIZE,TYPE,MODEL,SERIAL
NAME SIZE TYPE MODEL SERIAL
vda 40G disk
vdb 100G disk
The new disk is the one with the expected size and no partitions or mount points. Confirm that it is empty with lsblk -f, which also shows filesystems:
lsblk -f /dev/vdb
NAME FSTYPE FSVER LABEL UUID FSAVAIL FSUSE% MOUNTPOINTS
vdb
An empty FSTYPE column and no child partitions mean the disk has never been formatted. If you see a filesystem or partitions here, stop and make sure this is really the disk you want to erase.
This guide uses /dev/vdb from now on. Replace it with your own device name in every command.
NoteIf the new disk does not show up at all, see the Troubleshooting section at the end for how to rescan the bus.
Step 2 - Creating a partition
You could put a filesystem directly on /dev/vdb, but a partition table makes the disk self-describing: other tools and people can see at a glance that it is in use. Use a GPT table, which supports disks larger than 2 TB and is the default on modern systems.
Install parted if it is missing (it is included in standard Ubuntu server images):
sudo apt update
sudo apt install parted
Create a GPT partition table and a single partition that spans the whole disk:
sudo parted --script /dev/vdb mklabel gpt mkpart data ext4 0% 100%
Using 0% and 100% lets parted align the partition to the disk's optimal boundaries. In mkpart, data is the GPT partition name and ext4 is only a type hint; it does not format anything.
Tell the kernel to reread the partition table and check the result:
sudo partprobe /dev/vdb
lsblk -l /dev/vdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
vdb 253:16 0 100G 0 disk
vdb1 253:17 0 100G 0 part
The new partition is /dev/vdb1. On NVMe disks the partition name has a p, for example /dev/nvme1n1p1.
Step 3 - Creating the filesystem
ext4 is the default Ubuntu filesystem and a good general choice. XFS is a solid alternative for very large volumes and databases; the rest of this guide works the same with mkfs.xfs, except that you would use xfs as the type in /etc/fstab.
Format the partition with ext4 and give it the label data:
sudo mkfs.ext4 -L data /dev/vdb1
mke2fs 1.47.0 (5-Feb-2023)
Discarding device blocks: done
Creating filesystem with 26213888 4k blocks and 6553600 inodes
...
Writing superblocks and filesystem accounting information: done
Read the filesystem UUID. You will use it in /etc/fstab, because device names such as /dev/vdb1 can change when disks are added or removed, while the UUID stays fixed:
sudo blkid /dev/vdb1
/dev/vdb1: LABEL="data" UUID="3f1c2b8e-6a7d-4e5f-9c1a-2b3d4e5f6a7b" BLOCK_SIZE="4096" TYPE="ext4" PARTLABEL="data" PARTUUID="8d2e..."
Copy the UUID value. The value above is only an example.
Step 4 - Mounting the disk manually
Create the directory where the disk will be attached. /mnt/data is used here; /srv/data or a path under /var/lib are also common, depending on what will live on the disk.
sudo mkdir -p /mnt/data
Mount the partition and check it:
sudo mount /dev/vdb1 /mnt/data
df -h /mnt/data
Filesystem Size Used Avail Use% Mounted on
/dev/vdb1 98G 24K 93G 1% /mnt/data
The size is a little lower than the disk size because of filesystem metadata and the 5% of blocks ext4 reserves for root. On a pure data disk you can release that reserve with sudo tune2fs -m 1 /dev/vdb1, which keeps 1%.
A new filesystem is owned by root. Give your user ownership so you can write to it without sudo, then test a write:
sudo chown your_user:your_user /mnt/data
touch /mnt/data/testfile && ls -l /mnt/data
total 16
drwx------ 2 root root 16384 Sep 25 10:12 lost+found
-rw-rw-r-- 1 your_user your_user 0 Sep 25 10:14 testfile
Replace your_user with the account that should own the data. If a service such as PostgreSQL or Docker will use the disk, set ownership to that service's user instead. The lost+found directory is created by ext4 and should be left in place.
Remove the test file and unmount the disk. The next step mounts it again from /etc/fstab, which proves that the entry is correct:
rm /mnt/data/testfile
sudo umount /mnt/data
Step 5 - Mounting the disk at boot with /etc/fstab
A manual mount is lost on reboot. To make it permanent, add a line to /etc/fstab. Back up the file first, because a broken fstab can drop the server into emergency mode:
sudo cp /etc/fstab /etc/fstab.bak
Open the file:
sudo nano /etc/fstab
Add this line at the end, using the UUID you copied in Step 3:
UUID=3f1c2b8e-6a7d-4e5f-9c1a-2b3d4e5f6a7b /mnt/data ext4 defaults,nofail 0 2
The six fields are:
| Field | Value | Meaning |
|---|---|---|
| Device | UUID=... | The filesystem to mount, identified by UUID |
| Mount point | /mnt/data | Where it appears in the directory tree |
| Type | ext4 | Filesystem type (xfs if you formatted with XFS) |
| Options | defaults,nofail | Standard options; nofail lets the server boot even if the disk is missing |
| Dump | 0 | Legacy dump backup flag, leave at 0 |
| Pass | 2 | Order for fsck at boot: 1 is only for /, 2 for other ext4 disks, 0 disables it (use 0 for XFS) |
Save and close the file. Check the syntax before relying on it:
sudo findmnt --verify
Success, no errors or warnings detected
If findmnt reports an error, fix it now or restore /etc/fstab.bak. Then reload systemd, which generates mount units from /etc/fstab, and mount everything listed in the file:
sudo systemctl daemon-reload
sudo mount -a
findmnt /mnt/data
TARGET SOURCE FSTYPE OPTIONS
/mnt/data /dev/vdb1 ext4 rw,relatime
Step 6 - Verifying after a reboot
The real test is a reboot. Do it now, while you still remember what changed:
sudo reboot
Once you reconnect, confirm the disk is mounted and writable:
df -h /mnt/data
touch /mnt/data/after-reboot && rm /mnt/data/after-reboot
If df shows /dev/vdb1 on /mnt/data, the disk is ready for use.
Troubleshooting
The new disk does not appear in lsblk. VirtIO and NVMe disks are usually detected as soon as they are attached. For SCSI disks, ask the kernel to rescan every host adapter, then check the kernel log:
echo "- - -" | sudo tee /sys/class/scsi_host/host*/scan
sudo dmesg | tail -n 20
If it still does not appear, reboot the server.
umount: target is busy. A process has a file open or a shell is sitting in the directory. Find it with:
sudo fuser -vm /mnt/data
Stop that process or cd out of the directory, then unmount again.
The server boots into emergency mode after editing /etc/fstab. This happens with a typo in the UUID or type when nofail is missing. Log in through your provider's web console, remount the root filesystem read-write, restore the backup and reboot:
mount -o remount,rw /
cp /etc/fstab.bak /etc/fstab
reboot
Permission denied when writing to the disk. The mount point belongs to root. Run the chown command from Step 4 with the correct user.
Conclusion
You identified a new disk, created a GPT partition and an ext4 filesystem, and mounted it at /mnt/data through a UUID-based /etc/fstab entry that tolerates a missing disk at boot. From here you can move application data onto it, for example a database data directory or Docker's /var/lib/docker, share it over the network with NFS or Samba, or limit how much each user can store on it with disk quotas.
