Setting up a Raspberry Pi NFS server
raspberrypinfsssd2020-05-31 - carpie

If you need to share files on your home network, setting up an NFS server using a Raspberry Pi and an external hard drive or SSD is a quick and easy way to do just that. If you've been following my Kubernetes on Raspberry Pi series, the server setup we will create in this article can act as your persistent storage provider as well. Let's build it!

Materials needed

To follow along with this article, you will need a Raspberry Pi with Raspbian installed and the Pi set to a static IP. You will also need an external hard drive or SSD with a USB interface. For performance reasons, I suggest using a Raspberry Pi 4 and a drive with USB 3.1. Also, the drive used in this article is formatted with the ext4 file system. You are free to use other file systems if you prefer. Just be aware you may have to change some file system options at various points in the article if you do.

Mounting the external drive

With those materials gathered, let's get started. First, we shell in to the Pi with ssh. My Pi's static IP is 192.168.0.3 so that's the IP I'll be using in the article.

We need to make a directory to mount the drive on. Let's use /opt/nfs.

mkdir -p /opt/nfs

Next we want to drive to be automatically mounted when the Pi boots. So we need to add it's information to /etc/fstab. Let's get it's partition identifier.

$ sudo blkid /dev/sda1
/dev/sda1: LABEL="nfsdata" UUID="e595cb3f-d87a-4c2b-a12a-c3765430f683" TYPE="ext4" PARTLABEL="primary" PARTUIID="29affd63-bcbe-4923-a56f-58031b6a855c"

Copy the PARTUUID.

Now edit /etc/fstab and add in a line to mount the drive.

PARTUIID="29affd63-bcbe-4923-a56f-58031b6a855c" /opt/nfs ext4 defauls,noatime,nodiratime 0 2

We could reboot and the drive would be mounted, but we can also mount manually just this once. Let's do that to save a bit of time.

$ sudo mount /opt/nfs
$ ls /opt/nfs
lost+found

Ok. Looks good.

Let's install the NFS server.

sudo apt install -y nfs-kernel-server

Easy enough.

Now we need some things to export. Let's make a few sample export directories and files.

sudo mkdir -p /opt/nfs/everyone
echo "Hello world!" | sudo tee /opt/nfs/everyone/hi_there.txt
sudo mkdir -p /opt/nfs/restricted
echo "Top secret!" | sudo tee /opt/nfs/restricted/ima_secret.txt

Ok, now we need to tell NFS that we want these directories exported. We do that by putting entries in /etc/exports.

/opt/nfs/everyone *(rw,sync,no_subtree_check)
/opt/nfs/restricted 192.168.0.148(rw,sync,no_subtree_check)

The asterisk in the first line means that we want to export the everyone directory to, well, everyone. The rw flag means we want the export to be readable and writable. We could have used ro to make it read only. The sync and no_subtree_check flags are defaults, but at some point NFS switched the default values for these flags, so it'll will gripe at us if we don't specify them.

By specifying the host address for the second export, we are limiting access through NFS to a particular host. We could also have used host names and/or CIDR notation to limit access to a particular network or network subsection. If you are interested in the types of restrictions, run man exports on your NFS server Pi.

Now we need to tell NFS that we've changed the exports file and that it needs to re-evaluate what it's exporting.

sudo exportfs -ar

That's all there is to it!

Let's switch back to our PC and test the exports.

In order to mount NFS file systems (at least on Ubuntu-based systems) you need to install the nfs-common package. Without it, you'll get an error suggesting that you need a helper program when you try to mount.

sudo apt install -y nfs-common

With that installed, we can temporarily mount the everyone export.

$ sudo mount -t nfs 192.168.0.x:/opt/nfs/everyone /mnt
$ cat /mnt/hi_there.txt
Hello world!

We can now see the files from the export!

We mounted the directory read/write, so let's try to write a file.

$ echo "foo" >  /mnt/foo.txt
bash: /mnt/foo.txt: Permission denied

Hmm, that didn't work. So what's going on here? This is where things get a little complicated. By default, the NFS permissions are the standard Linux file access permission that limit access by user and group ids. So, in order to allow a write, the directory on the NFS server has to be writable by the user ID from the client. In other words, users and groups between the server and client must match.

So what can we do? Well, one option is to just make the directory on the NFS server world writable with something like chmod 777. That would work. If that trips your internal security senses, if you are managing mostly single user systems, we can take advantage of the fact that many distributions start user IDs off at 1000.

If you run id -u on the Pi, you'll notice that the pi user's id is 1000. On my computer, my carpie id is 1000 as well. This is because pi and carpie are the first end user ids created and both Raspbian and Ubuntu start users at 1000. So, we can just make the everyone directory owned by the pi user on the server.

sudo chown -R pi /opt/everyone/nfs

Now we can write.

$ echo "foo" >  /mnt/foo.txt
$ cat /mnt/foo.txt
foo

If you are managing multiple users, you have a couple of options. The world writable thing is an option. Also, you could create a group on the NFS server and on each client machine with the same id. You would then set the directory you wanted writeable to be group writeable and owned by the new group. On the client machines, you'd create the same group, with the same id, and add any users with access to the group. That would work too. If you wanted to get real fancy, you could make the server use something like LDAP to manage users. I'm going to leave those options as exercises to the reader.

Ok, that's the unrestricted export. Let's unmount that and then try the restricted mount on the PC.

$ sudo mount -t nfs 192.168.0.3:/opt/nfs/restricted /mnt
mount.nfs: access denied by server while mounting 192.168.0.3:/opt/nfs/restricted

Good. It was restricted to an IP that is not my IP so it won't let me mount it. Let's update the export on the server to use my IP.

/opt/nfs/restricted 192.168.0.107(rw,sync,no_subtree_check)

Tell NFS to update the export list.

sudo exportfs -ar

And now try that restricted mount again on the PC.

$ sudo mount -t nfs 192.168.0.3:/opt/nfs/restricted /mnt
$ cat /mnt/ima_secret.txt
Top Secret!
$ sudo umount /mnt

Cool, now I have access, so the restrictions are working.

Making the mount permanent

So that's how we can mount the export temporarily. What if we want it mounted all the time. We just need to put an entry in /etc/fstab for that case.

# NFS mounts
192.168.0.3:/opt/nfs/everyone /srv nfs rw,user,soft 0 0

So we mount the everyone export on /srv this time, just to change things up. You can mount on almost and directory you want. We use type nfs. We want to mount read/write, we specify user so that a normal non-root user can mount or unmount the share. And we'll specify soft so that our connections don't hang if the server crashes or we lose connection. Finally we don't want to dump the file system and we don't need fsck to check it, so we use 0 and 0 for the final two parameters.

Now we can mount as a normal user just by saying mount /srv. Also, the share will be mounted automatically when we reboot.

If we wanted the easy mount /srv mounting but didn't want to mount automatically on boot, we could add the noauto option in the fstab line. If you want to know more about the available options, try running man nfs.

There we go! We have a working NFS server. That's all for this article. Thanks for reading!