How to create an NFS server

Server-side

Mount new block storage to the server (optional)

If you want to use existing storage in the server. This step can be skipped.

# create new partition:
# NOTE: this will erase existing data in the block storage

parted -s /dev/vdb mklabel gpt
parted -s /dev/vdb unit mib mkpart primary 0% 100%

# Create filesystem:
mkfs.ext4 /dev/vdb1

# Mount the partition
mkdir /mnt/nfs
echo >> /etc/
echo /dev/vdb1               /mnt/nfs       ext4    defaults,noatime,nofail 0 0 >> /etc/fstab 
mount /mnt/nfs
Source

Now the block storage is mounted at /mnt/nfs. Next, we will share this directory as an NFS volume.

Configure a static IP address for private network

Source

If the NFS server is to serve via a public network interface with a static public IP, you can skip this step.

Firstly, attach the private network to the server at your control panel. Suppose the private network has subnet 10.25.96.0/20, the server has MAC address 5a:00:03:e6:4e:78 in this private network.

From the screenshot, the server is dynamically assigned with IP 10.25.96.7. We want to assign it another static IP, for instance 10.25.96.100.

Run ip addr to get the network interface enp6s0.

root@nfs-stag:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq state UP group default qlen 1000
    link/ether 56:00:03:e6:4e:78 brd ff:ff:ff:ff:ff:ff
    inet 45.76.111.10/23 metric 100 brd 45.76.111.255 scope global dynamic enp1s0
       valid_lft 59605sec preferred_lft 59605sec
    inet6 2401:c080:1000:40c5:5400:3ff:fee6:4e78/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 2591710sec preferred_lft 604510sec
    inet6 fe80::5400:3ff:fee6:4e78/64 scope link 
       valid_lft forever preferred_lft forever
3: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc mq state UP group default qlen 1000
    link/ether 5a:00:03:e6:4e:78 brd ff:ff:ff:ff:ff:ff
    inet 10.25.96.7/20 brd 10.25.111.255 scope global enp6s0
       valid_lft forever preferred_lft forever
    inet6 fe80::5800:3ff:fee6:4e78/64 scope link 
       valid_lft forever preferred_lft forever

Create a new netplan config at /etc/netplan/10-enp6s0.yaml. Note: replace enp6s0, macaddresses, addresses appropriately with your information.

network:
  version: 2
  renderer: networkd
  ethernets:
    enp6s0:
      match:
        macaddress: 5a:00:03:e6:4e:78
      mtu: 1450
      dhcp4: no
      addresses: [10.25.96.100/20]

Run netplan apply to apply the new config. Then, run ip addr to confirm the new configuration.

root@nfs-stag:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq state UP group default qlen 1000
    link/ether 56:00:03:e6:4e:78 brd ff:ff:ff:ff:ff:ff
    inet 45.76.111.10/23 metric 100 brd 45.76.111.255 scope global dynamic enp1s0
       valid_lft 86380sec preferred_lft 86380sec
    inet6 2401:c080:1000:40c5:5400:3ff:fee6:4e78/64 scope global dynamic mngtmpaddr noprefixroute 
       valid_lft 2591981sec preferred_lft 604781sec
    inet6 fe80::5400:3ff:fee6:4e78/64 scope link 
       valid_lft forever preferred_lft forever
3: enp6s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc mq state UP group default qlen 1000
    link/ether 5a:00:03:e6:4e:78 brd ff:ff:ff:ff:ff:ff
    inet 10.25.96.100/20 brd 10.25.111.255 scope global enp6s0
       valid_lft forever preferred_lft forever
    inet 10.25.96.7/20 brd 10.25.111.255 scope global secondary enp6s0
       valid_lft forever preferred_lft forever
    inet6 fe80::5800:3ff:fee6:4e78/64 scope link 
       valid_lft forever preferred_lft forever
New static IP has been assigned

Setup NFS service

# install NFS server
apt install -y nfs-kernel-server

# start the service
systemctl start nfs-kernel-server

# enable the service in startup
systemctl enable nfs-kernel-server

# update permission on the shared direction
chown nobody:nogroup /mnt/nfs
chmod 777 /mnt/nfs
Source

Configure NFS permission by opening /etc/exports and add the following lines:

# allow access from private network
/mnt/nfs 10.25.96.0/20(rw,sync,no_subtree_check)

For an explanation of flags: rw, sync, no_subtree_check, refer to this DO tutorial.

Refresh the NFS server with the new config by exportfs -rva.

Open port for NFS serving

Method 1: with ufw

ufw allow from 10.25.96.0/20 to any port 2049 proto tcp

Method 2: with iptables

Note: iptables changes are not persisted after a restart. You need to create a startup script to run the command. Refer to this post for more info.

iptables -A INPUT -p tcp -m tcp --dport 2049 -j ACCEPT -i enp6s0 -s 10.25.96.0/20
  • enp6s0 is the network interface name.
  • -s: is source IP range.
  • -A: append rule.
  • -p: protocol.
  • --dport: port range.
  • -j: jump to the ACCEPT target.

For some old versions, other ports are used.

# iptables -A INPUT -p tcp -m tcp --dport 111 -j ACCEPT
# iptables -A INPUT -p tcp -m tcp --dport 2049 -j ACCEPT
# iptables -A INPUT -p tcp -m tcp --dport 20048 -j ACCEPT
# iptables -A INPUT -p udp -m udp --dport 111 -j ACCEPT
# iptables -A INPUT -p udp -m udp --dport 2049 -j ACCEPT
# iptables -A INPUT -p udp -m udp --dport 20048 -j ACCEPT

Client-side

Install the NFS mount util

# Ubuntu
apt install -y nfs-common

# Archlinux
pacman -Syu nfs-utils

Otherwise, you will see the errors:

mount: /opt/nfs: cannot mount 10.25.96.100:/mnt/nfs read-only.
# create client directory to mount
mkdir /opt/nfs

# to mount
mount 10.25.96.100:/mnt/nfs /opt/nfs

# to unmount
umount /opt/nfs

Some debug commands:

Check port status:

# rpcinfo -p 10.25.96.100
   program vers proto   port  service
    100000    4   tcp    111  portmapper
    100000    3   tcp    111  portmapper
    100000    2   tcp    111  portmapper
    100000    4   udp    111  portmapper
    100000    3   udp    111  portmapper
    100000    2   udp    111  portmapper
    100024    1   udp  52554  status
    100024    1   tcp  39317  status
    100005    1   udp  54399  mountd
    100005    1   tcp  53751  mountd
    100005    2   udp  33947  mountd
    100005    2   tcp  52335  mountd
    100005    3   udp  59418  mountd
    100005    3   tcp  46289  mountd
    100003    3   tcp   2049  nfs
    100003    4   tcp   2049  nfs
    100227    3   tcp   2049  nfs_acl
    100021    1   udp  36258  nlockmgr
    100021    3   udp  36258  nlockmgr
    100021    4   udp  36258  nlockmgr
    100021    1   tcp  42535  nlockmgr
    100021    3   tcp  42535  nlockmgr
    100021    4   tcp  42535  nlockmgr

Check port with nmap:

nmap -P -p 2049 10.25.96.100

Check the NFS server PID

NFS server is a kernel process and does not stay in the userspace. To list the PID for NFS.

# ps axf
# ps axf | grep nfs
   5450 ?        I<     0:00  \_ [nfsiod]
   6044 ?        S      0:00  \_ [nfsd]
   6045 ?        S      0:00  \_ [nfsd]
   6046 ?        S      0:00  \_ [nfsd]
   6047 ?        S      0:00  \_ [nfsd]
   6048 ?        S      0:00  \_ [nfsd]
   6049 ?        S      0:00  \_ [nfsd]
   6050 ?        S      0:00  \_ [nfsd]
   6051 ?        S      0:00  \_ [nfsd]
    562 ?        Ss     0:00 /usr/sbin/nfsdcld
   6919 pts/0    S+     0:00          \_ grep --color=auto nfs
# cat /proc/fs/nfsd/threads
8
# cat /proc/fs/nfsd/versions # check NFS server version
-2 +3 +4 +4.1 +4.2

To debug the mount command use -vvv flag.

NFS in docker container

Refer to these dockers for reference:

GitHub - ehough/docker-nfs-server: A lightweight, robust, flexible, and containerized NFS server.
A lightweight, robust, flexible, and containerized NFS server. - GitHub - ehough/docker-nfs-server: A lightweight, robust, flexible, and containerized NFS server.

https://github.com/sjiveson/nfs-server-alpine

If you are using kubernetes:

apiVersion: v1
kind: Pod
metadata:
  name: nfs-server
  labels:
    app: nfs-server
spec:
  containers:
  - name: nfs-server
    image: itsthenetwork/nfs-server-alpine:latest
    ports:
      - containerPort: 2049
        name: nfs
    # https://github.com/sjiveson/nfs-server-alpine/issues/8#issuecomment-576065566
    volumeMounts:
      - name: nfs
        mountPath: /opt/nfs
    securityContext:
      privileged: true
      capabilities:
        add:
          - NET_ADMIN
          - SETPCAP
  restartPolicy: Always
  volumes:
    - name: nfs
      emptyDir: {}

---

apiVersion: v1
kind: Service
metadata:
  name: nfs-server
spec:
  ports:
    - port: 2049
  selector:
    app: nfs-server