Raspberry Pi Documentation

Remote Access

Introduction to Remote Access

Sometimes you need to access a Raspberry Pi without connecting it to a monitor. Perhaps the Raspberry Pi is embedded in something like a robot, or you may want to view some information from it from elsewhere. Or perhaps you simply don’t have a spare monitor!

You can connect to your Raspberry Pi from another machine. But in order to do so you’ll need to know its IP Address.

Any device connected to a Local Area Network is assigned an IP address. In order to connect to your Raspberry Pi from another machine using SSH or VNC, you need to know the Raspberry Pi’s IP address. This is easy if you have a display connected, and there are a number of methods for finding it remotely from another machine on the network.

How to Find your IP Address

It is possible to find the IP address of your Raspberry Pi without connecting to a screen using one of the following methods:

Note
If you are using a display with your Raspberry Pi and if you boot to the command line instead of the desktop, your IP address should be shown in the last few messages before the login prompt. Otherwise open a Terminal window and type hostname -I which will reveal your Raspberry Pi’s IP address.

Router devices list

In a web browser navigate to your router’s IP address e.g. http://192.168.1.1, which is usually printed on a label on your router; this will take you to a control panel. Then log in using your credentials, which is usually also printed on the router or sent to you in the accompanying paperwork. Browse to the list of connected devices or similar (all routers are different), and you should see some devices you recognise. Some devices are detected as PCs, tablets, phones, printers, etc. so you should recognise some and rule them out to figure out which is your Raspberry Pi. Also note the connection type; if your Raspberry Pi is connected with a wire there should be fewer devices to choose from.

Resolving raspberrypi.local with mDNS

On Raspberry Pi OS, multicast DNS is supported out-of-the-box by the Avahi service.

If your device supports mDNS, you can reach your Raspberry Pi by using its hostname and the .local suffix. The default hostname on a fresh Raspberry Pi OS install is raspberrypi, so by default any Raspberry Pi running Raspberry Pi OS responds to:

ping raspberrypi.local

If the Raspberry Pi is reachable, ping will show its IP address:

PING raspberrypi.local (192.168.1.131): 56 data bytes
64 bytes from 192.168.1.131: icmp_seq=0 ttl=255 time=2.618 ms

If you change the system hostname of the Raspberry Pi (e.g., by editing /etc/hostname), Avahi will also change the .local mDNS address.

If you don’t remember the hostname of the Raspberry Pi, but have a system with Avahi installed, you can browse all the hosts and services on the LAN with the avahi-browse command.

nmap command

The nmap command (Network Mapper) is a free and open-source tool for network discovery, available for Linux, macOS, and Windows.

  • To install on Linux, install the nmap package e.g. apt install nmap.

  • To install on macOS or Windows, see the nmap.org download page.

To use nmap to scan the devices on your network, you need to know the subnet you are connected to. First find your own IP address, in other words the one of the computer you’re using to find your Raspberry Pi’s IP address:

  • On Linux, type hostname -I into a terminal window

  • On macOS, go to System Preferences then Network and select your active network connection to view the IP address

  • On Windows, go to the Control Panel, then under Network and Sharing Center, click View network connections, select your active network connection and click View status of this connection to view the IP address

Now you have the IP address of your computer, you will scan the whole subnet for other devices. For example, if your IP address is 192.168.1.5, other devices will be at addresses like 192.168.1.2, 192.168.1.3, 192.168.1.4, etc. The notation of this subnet range is 192.168.1.0/24 (this covers 192.168.1.0 to 192.168.1.255).

Now use the nmap command with the -sn flag (ping scan) on the whole subnet range. This may take a few seconds:

nmap -sn 192.168.1.0/24

Ping scan just pings all the IP addresses to see if they respond. For each device that responds to the ping, the output shows the hostname and IP address like so:

Starting Nmap 6.40 ( http://nmap.org ) at 2014-03-10 12:46 GMT
Nmap scan report for hpprinter (192.168.1.2)
Host is up (0.00044s latency).
Nmap scan report for Gordons-MBP (192.168.1.4)
Host is up (0.0010s latency).
Nmap scan report for ubuntu (192.168.1.5)
Host is up (0.0010s latency).
Nmap scan report for raspberrypi (192.168.1.8)
Host is up (0.0030s latency).
Nmap done: 256 IP addresses (4 hosts up) scanned in 2.41 seconds

Here you can see a device with hostname raspberrypi has IP address 192.168.1.8. Note, to see the hostnames, you must run nmap as root by prepending sudo to the command.

Getting IPv6 addresses by pinging from a second device

First find your own IP address(es), in other words the one of the computer you’re using to find your Raspberry Pi’s IP address by hostname -I

fd00::ba27:ebff:feb6:f293 2001:db8:494:9d01:ba27:ebff:feb6:f293

The example shows two IP addresses. The first one is a so called unique local unicast address(fc00::/7). The second one is the global unicast address(2000::/3). It is also possible to see only one of them depending on your network (router) configuration. Both addresses are valid for reaching the Raspberry Pi within your LAN. The address out of 2000::/3 is accessible world wide, provided your router’s firewall is opened.

Now use one of IPs from the first step to ping all local nodes:

ping -c 2 -I 2001:db8:494:9d01:ba27:ebff:feb6:f293  ff02::1
ping -c 2 -I 2001:db8:494:9d01:ba27:ebff:feb6:f293  ff02::1%eth0

-c 2 stands for sending two echo requests

-I with the IP address, it sets the interface and the source address of the echo request, it is necessary to choose the interface’s IP address, eth0 isn’t sufficient - the answer would be the local link address(fe80::/10), we need the global or local unicast address

ff02::1 is a well known multicast address for all nodes on the link, so it behaves like a local broadcast, usually it is defined in /etc/hosts so you can also use the name (ip6-allnodes or ipv6-allnodes) instead of the literal address

Some newer systems expect the interface ID behind the multicast address.

ping -c 2 -I 2001:db8:494:9d01:ba27:ebff:feb6:f293 ip6-allnodes
PING ip6-allnodes(ip6-allnodes (ff02::1)) from 2001:db8:494:9d01:ba27:ebff:feb6:f293 : 56 data bytes
64 bytes from 2001:db8:494:9d01:ba27:ebff:feb6:f293: icmp_seq=1 ttl=64 time=0.597 ms
64 bytes from witz.fritz.box (2001:db8:494:9d01:728b:cdff:fe7d:a2e): icmp_seq=1 ttl=255 time=1.05 ms (DUP!)
64 bytes from raspberrypi4.fritz.box (2001:db8:494:9d01:dea6:32ff:fe23:6be1): icmp_seq=1 ttl=64 time=1.05 ms (DUP!)
64 bytes from 2001:db8:494:9d01:da37:beff:fefd:f09d (2001:db8:494:9d01:da37:beff:fefd:f09d): icmp_seq=1 ttl=255 time=1.05 ms (DUP!)
64 bytes from fusion.fritz.box (2001:db8:494:9d01:1e6f:65ff:fec9:8746): icmp_seq=1 ttl=255 time=2.12 ms (DUP!)
64 bytes from fritz.box (2001:db8:494:9d01:464e:6dff:fe72:8a08): icmp_seq=1 ttl=64 time=2.62 ms (DUP!)
64 bytes from raspberrypi.fritz.box (2001:db8:494:9d01:ba27:ebff:feb6:f293): icmp_seq=2 ttl=64 time=0.480 ms

--- ip6-allnodes ping statistics ---
2 packets transmitted, 2 received, +5 duplicates, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.480/1.283/2.623/0.735 ms

This should result in replies from all the nodes on your (W)LAN link, with associated DNS names.

Exclude your own IP( here 2001:db8:494:9d01:ba27:ebff:feb6:f293 ), then check the others by trying to connect them via SSH.

ssh pi@2001:db8:494:9d01:dea6:32ff:fe23:6be1
The authenticity of host '2001:db8:494:9d01:dea6:32ff:fe23:6be1 (2001:db8:494:9d01:dea6:32ff:fe23:6be1)' can't be established.
ECDSA key fingerprint is SHA256:DAW68oen42TdWDyrOycDZ1+y5ZV5D81kaVoi5FnpvoM.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '2001:db8:494:9d01:dea6:32ff:fe23:6be1' (ECDSA) to the list of known hosts.
pi@2001:db8:494:9d01:dea6:32ff:fe23:6be1's password:
Linux raspberrypi4 4.19.75-v7l+ #1270 SMP Tue Sep 24 18:51:41 BST 2019 armv7l

...

pi@raspberrypi4:~ $

Getting the IP address of a Raspberry Pi using your smartphone

The Fing app is a free network scanner for smartphones. It is available for Android and iOS.

Your phone and your Raspberry Pi have to be on the same network, so connect your phone to the correct wireless network.

When you open the Fing app, touch the refresh button in the upper right-hand corner of the screen. After a few seconds you will get a list with all the devices connected to your network. Scroll down to the entry with the manufacturer "Raspberry Pi". You will see the IP address in the bottom left-hand corner, and the MAC address in the bottom right-hand corner of the entry.

Setting up an SSH Server

You can access the command line of a Raspberry Pi remotely from another computer or device on the same network using the Secure Shell (SSH) protocol.

You will only have access to the command line, not the full desktop environment. For a full remote desktop, see VNC.

Set up your Local Network

Make sure your Raspberry Pi is properly set up and connected. If you are using wireless networking, this can be enabled via the desktop user interface, or using from the command line. If you are not using wireless connectivity, plug your Raspberry Pi directly into the router.

Note
You will need to note down the IP address of your Raspberry Pi in order to connect to it later. Using the ifconfig command will display information about the current network status, including the IP address, or you can use hostname -I to display the IP addresses associated with the device.

Enabling the Server

Raspberry Pi OS has the SSH server disabled by default. It can be enabled manually from the desktop:

  1. Launch Raspberry Pi Configuration from the Preferences menu

  2. Navigate to the Interfaces tab

  3. Select Enabled next to SSH

  4. Click OK

Alternatively you can enable it from the terminal using the raspi-config application,

  1. Enter sudo raspi-config in a terminal window

  2. Select Interfacing Options

  3. Navigate to and select SSH

  4. Choose Yes

  5. Select Ok

  6. Choose Finish

Note
For headless setup, SSH can be enabled by placing a file named ssh, without any extension, onto the boot partition of the SD Card. When the Raspberry Pi boots, it looks for the ssh file. If it is found, SSH is enabled and the file is deleted. The content of the file does not matter; it could contain text, or nothing at all.
Warning
When enabling SSH on a Raspberry Pi that may be connected to the internet, you should change the default password to ensure that it remains secure.

Secure Shell from Linux or Mac OS

You can use SSH to connect to your Raspberry Pi from a Linux desktop, another Raspberry Pi, or from an Apple Mac without installing additional software.

Open a terminal window on your computer replacing <IP> with the IP address of the Raspberry Pi you’re trying to connect to,

ssh pi@<IP>

When the connection works you will see a security/authenticity warning. Type yes to continue. You will only see this warning the first time you connect.

Note
If you receive a connection timed out error it is likely that you have entered the wrong IP address for the Raspberry Pi.
Warning
In the event your Raspberry Pi has taken the IP address of a device to which your computer has connected before (even if this was on another network), you may be given a warning and asked to clear the record from your list of known devices. Following this instruction and trying the ssh command again should be successful.

Next you will be prompted for the password for the pi login: the default password on Raspberry Pi OS is raspberry.

For security reasons it is highly recommended to change the default password on the Raspberry Pi (also, you can not login through ssh if the password is blank). You should now be able to see the Raspberry Pi prompt, which will be identical to the one found on the Raspberry Pi itself.

If you have set up another user on the Raspberry Pi, you can connect to it in the same way, replacing the username with your own, e.g. eben@192.168.1.5

pi@raspberrypi ~ $

You are now connected to the Raspberry Pi remotely, and can execute commands.

Forwarding X11

You can also forward your X session over SSH, to allow the use of graphical applications, by using the -Y flag:

ssh -Y pi@192.168.1.5
Note
X11 is no longer installed by default on macOS, so you will have to download and install it.

Now you are on the command line as before, but you have the ability to open up graphical windows. For example, typing:

geany &

will open up the Geany editor in a window on your local desktop.

Secure Shell from Windows 10

You can use SSH to connect to your Raspberry Pi from a Windows 10 computer that is using October 2018 Update or later without having to use third-party clients.

Open a terminal window on your computer replacing <IP> with the IP address of the Raspberry Pi you’re trying to connect to,

ssh pi@<IP>

When the connection works you will see a security/authenticity warning. Type yes to continue. You will only see this warning the first time you connect.

Note
If you receive a connection timed out error it is likely that you have entered the wrong IP address for the Raspberry Pi.
Warning
In the event your Raspberry Pi has taken the IP address of a device to which your computer has connected before (even if this was on another network), you may be given a warning and asked to clear the record from your list of known devices. Following this instruction and trying the ssh command again should be successful.

Next you will be prompted for the password for the pi login: the default password on Raspberry Pi OS is raspberry.

For security reasons it is highly recommended to change the default password on the Raspberry Pi (also, you can not login through ssh if the password is blank). You should now be able to see the Raspberry Pi prompt, which will be identical to the one found on the Raspberry Pi itself.

If you have set up another user on the Raspberry Pi, you can connect to it in the same way, replacing the username with your own, e.g. eben@192.168.1.5

pi@raspberrypi ~ $

You are now connected to the Raspberry Pi remotely, and can execute commands.

Passwordless SSH Access

It is possible to configure your Raspberry Pi to allow access from another computer without needing to provide a password each time you connect. To do this, you need to use an SSH key instead of a password. To generate an SSH key:

Checking for Existing SSH Keys

First, check whether there are already keys on the computer you are using to connect to the Raspberry Pi:

ls ~/.ssh

If you see files named id_rsa.pub or id_dsa.pub then you have keys set up already, so you can skip the 'Generate new SSH keys' step below.

Generate new SSH Keys

To generate new SSH keys enter the following command:

ssh-keygen

Upon entering this command, you will be asked where to save the key. We suggest saving it in the default location (~/.ssh/id_rsa) by pressing Enter.

You will also be asked to enter a passphrase, which is optional. The passphrase is used to encrypt the private SSH key, so that if someone else copied the key, they could not impersonate you to gain access. If you choose to use a passphrase, type it here and press Enter, then type it again when prompted. Leave the field empty for no passphrase.

Now look inside your .ssh directory:

ls ~/.ssh

and you should see the files id_rsa and id_rsa.pub:

authorized_keys  id_rsa  id_rsa.pub  known_hosts

The id_rsa file is your private key. Keep this on your computer.

The id_rsa.pub file is your public key. This is what you share with machines that you connect to: in this case your Raspberry Pi. When the machine you try to connect to matches up your public and private key, it will allow you to connect.

Take a look at your public key to see what it looks like:

cat ~/.ssh/id_rsa.pub

It should be in the form:

ssh-rsa <REALLY LONG STRING OF RANDOM CHARACTERS> user@host

Copy your Key to your Raspberry Pi

Using the computer which you will be connecting from, append the public key to your authorized_keys file on the Raspberry Pi by sending it over SSH:

ssh-copy-id <USERNAME>@<IP-ADDRESS>
Note
During this step you will need to authenticate with your password.

Alternatively, if ssh-copy-id is not available on your system, you can copy the file manually over SSH:

cat ~/.ssh/id_rsa.pub | ssh <USERNAME>@<IP-ADDRESS> 'mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys'

If you see the message ssh: connect to host <IP-ADDRESS> port 22: Connection refused and you know the IP-ADDRESS is correct, then you may not have enabled SSH on your Raspberry Pi. Run sudo raspi-config in the Raspberry Pi’s terminal window, enable SSH, then try to copy the files again.

Now try ssh <USER>@<IP-ADDRESS> and you should connect without a password prompt.

If you see a message "Agent admitted failure to sign using the key" then add your RSA or DSA identities to the authentication agent ssh-agent then execute the following command:

ssh-add
Note
You can also send files over SSH using the scp (secure copy) command.

Adjust Directory Permissions

If you can’t establish a connection after following the steps above there might be a problem with your directory permissions. First, you want to check the logs for any errors:

tail -f /var/log/secure
# might return:
Nov 23 12:31:26 raspberrypi sshd[9146]: Authentication refused: bad ownership or modes for directory /home/pi

If the log says Authentication refused: bad ownership or modes for directory /home/pi there is a permission problem regarding your home directory. SSH needs your home and ~/.ssh directory to not have group write access. You can adjust the permissions using chmod:

chmod g-w $HOME
chmod 700 $HOME/.ssh
chmod 600 $HOME/.ssh/authorized_keys

Now only the user itself has access to .ssh and .ssh/authorized_keys in which the public keys of your remote machines are stored.

Storing the passphrase in the macOS keychain

If you are using macOS, and after verifying that your new key allows you to connect, you have the option of storing the passphrase for your key in the macOS keychain. This allows you to connect to your Raspberry Pi without entering the passphrase.

Run the following command to store it in your keychain:

ssh-add -K ~/.ssh/id_rsa
Note

From macOS Monterey onwards the -K flag has been deprecated and been replaced by the --apple-use-keychain flag.

Using Secure Copy

Secure Copy (scp) is a command for sending files over SSH. This means you can copy files between computers, say from your Raspberry Pi to your desktop or laptop, or vice-versa.

First of all, you’ll need to know your Raspberry Pi’s IP address.

Copying Files to your Raspberry Pi

Copy the file myfile.txt from your computer to the pi user’s home folder of your Raspberry Pi at the IP address 192.168.1.3 with the following command:

scp myfile.txt pi@192.168.1.3:

Copy the file to the /home/pi/project/ directory on your Raspberry Pi (the project folder must already exist):

scp myfile.txt pi@192.168.1.3:project/

Copying Files from your Raspberry Pi

Copy the file myfile.txt from your Raspberry Pi to the current directory on your other computer:

scp pi@192.168.1.3:myfile.txt .

Copying Multiple Files

Copy multiple files by separating them with spaces:

scp myfile.txt myfile2.txt pi@192.168.1.3:

Alternatively, use a wildcard to copy all files matching a particular search with:

scp *.txt pi@192.168.1.3:

(all files ending in .txt)

scp m* pi@192.168.1.3:

(all files starting with m)

scp m*.txt pi@192.168.1.3:

(all files starting with m and ending in .txt)

Note

Some of the examples above will not work for file names containing spaces. Names like this need to be enclosed in quotes:

scp "my file.txt" pi@192.168.1.3:

Copying a Whole Directory

Copy the directory project/ from your computer to the pi user’s home folder of your Raspberry Pi at the IP address 192.168.1.3 with the following command:

scp -r project/ pi@192.168.1.3:

Using rsync

You can use the tool rsync to synchronise folders between computers. You might want to transfer some files from your desktop computer or laptop to your Raspberry Pi, for example, and for them to be kept up to date, or you might want the pictures taken by your Raspberry Pi transferred to your computer automatically.

Using rsync over SSH allows you to transfer files to your computer automatically.

Here is an example of how to set up the sync of a folder of pictures on your Raspberry Pi to your computer:

On your computer, create a folder called camera:

mkdir camera

Look up the Raspberry Pi’s IP address by logging in to it and running hostname -I. In this example, the Raspberry Pi is creating a timelapse by capturing a photo every minute, and saving the picture with a timestamp in the local folder camera on its SD card.

Now run the following command (substituting your own Raspberry Pi’s IP address):

rsync -avz -e ssh pi@192.168.1.10:camera/ camera/

This will copy all files from the Raspberry Pi’s camera folder to your computer’s new camera folder.

Network File System (NFS)

Network File System (NFS) allows you to share a directory located on one networked computer with other computers or devices on the same network. The computer where the directory is located is called the server, and computers or devices connecting to that server are called clients. Clients usually mount the shared directory to make it a part of their own directory structure. The shared directory is an example of a shared resource or network share.

For smaller networks, an NFS is perfect for creating a simple NAS (Network-attached storage) in a Linux/Unix environment.

An NFS is perhaps best suited to more permanent network-mounted directories, such as /home directories or regularly-accessed shared resources. If you want a network share that guest users can easily connect to, Samba is better suited to the task. This is because tools to temporarily mount and detach from Samba shares are more readily available across old and proprietary operating systems.

Before deploying an NFS, you should be familiar with:

  • Linux file and directory permissions

  • mounting and unmounting filesystems

Setting up a Basic NFS Server

Install the packages required using the command below:

sudo apt install nfs-kernel-server

For easier maintenance, we will isolate all NFS exports in single directory, into which the real directories will be mounted with the --bind option.

Suppose we want to export our users' home directories, which are in /home/users. First we create the export filesystem:

sudo mkdir -p /export/users

Note that /export and /export/users will need 777 permissions, as we will be accessing the NFS share from the client without LDAP/NIS authentication. This will not apply if using authentication (see below). Now mount the real users directory with:

sudo mount --bind /home/users /export/users

To save us from retyping this after every reboot, we add the following line to /etc/fstab:

/home/users    /export/users   none    bind  0  0

There are three configuration files that relate to an NFS server:

  1. /etc/default/nfs-kernel-server

  2. /etc/default/nfs-common

  3. /etc/exports

The only important option in /etc/default/nfs-kernel-server for now is NEED_SVCGSSD. It is set to "no" by default, which is fine, because we are not activating NFSv4 security this time.

In order for the ID names to be automatically mapped, the file /etc/idmapd.conf must exist on both the client and the server with the same contents and with the correct domain names. Furthermore, this file should have the following lines in the Mapping section:

[Mapping]

Nobody-User = nobody
Nobody-Group = nogroup

However, note that the client may have different requirements for the Nobody-User and Nobody-Group. For example, on RedHat variants, it is nfsnobody for both. If you’re not sure, check via the following commands to see if nobody and nogroup are there:

cat /etc/passwd
cat /etc/group

This way, server and client do not need the users to share same UID/GUID. For those who use LDAP-based authentication, add the following lines to the idmapd.conf of your clients:

[Translation]

Method = nsswitch

This will cause idmapd to know to look at nsswitch.conf to determine where it should look for credential information. If you have LDAP authentication already working, nsswitch shouldn’t require further explanation.

To export our directories to a local network 192.168.1.0/24, we add the following two lines to /etc/exports:

/export       192.168.1.0/24(rw,fsid=0,insecure,no_subtree_check,async)
/export/users 192.168.1.0/24(rw,nohide,insecure,no_subtree_check,async)

Portmap lockdown (optional)

The files on your NFS are open to anyone on the network. As a security measure, you can restrict access to specified clients.

Add the following line to /etc/hosts.deny:

rpcbind mountd nfsd statd lockd rquotad : ALL

By blocking all clients first, only clients in /etc/hosts.allow (added below) will be allowed to access the server.

Now add the following line to /etc/hosts.allow:

rpcbind mountd nfsd statd lockd rquotad : <list of IPv4s>

where <list of IPv4s> is a list of the IP addresses of the server and all clients. (These have to be IP addresses because of a limitation in rpcbind, which doesn’t like hostnames.) Note that if you have NIS set up, you can just add these to the same line.

Please ensure that the list of authorised IP addresses includes the localhost address (127.0.0.1), as the startup scripts in recent versions of Ubuntu use the rpcinfo command to discover NFSv3 support, and this will be disabled if localhost is unable to connect.

Finally, to make your changes take effect, restart the service:

sudo systemctl restart nfs-kernel-server

Configuring an NFS Client

Now that your server is running, you need to set up any clients to be able to access it. To start, install the required packages:

sudo apt install nfs-common

On the client, we can mount the complete export tree with one command:

mount -t nfs -o proto=tcp,port=2049 <nfs-server-IP>:/ /mnt

You can also specify the NFS server hostname instead of its IP address, but in this case you need to ensure that the hostname can be resolved to an IP on the client side. A robust way of ensuring that this will always resolve is to use the /etc/hosts file.

Note that <nfs-server-IP>:/export is not necessary in NFSv4, as it was in NFSv3. The root export :/ defaults to export with fsid=0.

We can also mount an exported subtree with:

mount -t nfs -o proto=tcp,port=2049 <nfs-server-IP>:/users /home/users

To ensure this is mounted on every reboot, add the following line to /etc/fstab:

<nfs-server-IP>:/   /mnt   nfs    auto  0  0

If, after mounting, the entry in /proc/mounts appears as <nfs-server-IP>:// (with two slashes), then you might need to specify two slashes in /etc/fstab, or else umount might complain that it cannot find the mount.

Portmap lockdown (optional)

Add the following line to /etc/hosts.deny:

rpcbind : ALL

By blocking all clients first, only clients in /etc/hosts.allow (added below) will be allowed to access the server.

Now add the following line to /etc/hosts.allow:

rpcbind : <NFS server IP address>

where <NFS server IP address> is the IP address of the server.

A More Complex NFS Server

NFS user permissions are based on user ID (UID). UIDs of any users on the client must match those on the server in order for the users to have access. The typical ways of doing this are:

  • Manual password file synchronisation

  • Use of LDAP

  • Use of DNS

  • Use of NIS

Note that you have to be careful on systems where the main user has root access: that user can change UIDs on the system to allow themselves access to anyone’s files. This page assumes that the administrative team is the only group with root access and that they are all trusted. Anything else represents a more advanced configuration, and will not be addressed here.

Group permissions

A user’s file access is determined by their membership of groups on the client, not on the server. However, there is an important limitation: a maximum of 16 groups are passed from the client to the server, and if a user is member of more than 16 groups on the client, some files or directories might be unexpectedly inaccessible.

DNS (optional, only if using DNS)

Add any client name and IP addresses to /etc/hosts. (The IP address of the server should already be there.) This ensures that NFS will still work even if DNS goes down. Alternatively you can rely on DNS if you want - it’s up to you.

NIS (optional, only if using NIS)

This applies to clients using NIS. Otherwise you can’t use netgroups, and should specify individual IPs or hostnames in /etc/exports. Read the BUGS section in man netgroup for more information.

First, edit /etc/netgroup and add a line to classify your clients (this step is not necessary, but is for convenience):

myclients (client1,,) (client2,,) ...

where myclients is the netgroup name.

Next run this command to rebuild the NIS database:

sudo make -C /var/yp

The filename yp refers to Yellow Pages, the former name of NIS.

Portmap lockdown (optional)

Add the following line to /etc/hosts.deny:

rpcbind mountd nfsd statd lockd rquotad : ALL

By blocking all clients first, only clients in /etc/hosts.allow (added below) will be allowed to access the server.

Consider adding the following line to /etc/hosts.allow:

rpcbind mountd nfsd statd lockd rquotad : <list of IPs>

where <list of IPs> is a list of the IP addresses of the server and all clients. These have to be IP addresses because of a limitation in rpcbind. Note that if you have NIS set up, you can just add these to the same line.

Package installation and configuration

Install the necessary packages:

sudo apt install rpcbind nfs-kernel-server

Edit /etc/exports and add the shares:

/home @myclients(rw,sync,no_subtree_check)
/usr/local @myclients(rw,sync,no_subtree_check)

The example above shares /home and /usr/local to all clients in the myclients netgroup.

/home 192.168.0.10(rw,sync,no_subtree_check) 192.168.0.11(rw,sync,no_subtree_check)
/usr/local 192.168.0.10(rw,sync,no_subtree_check) 192.168.0.11(rw,sync,no_subtree_check)

The example above shares /home and /usr/local to two clients with static IP addresses. If you want instead to allow access to all clients in the private network falling within a designated IP address range, consider the following:

/home 192.168.0.0/255.255.255.0(rw,sync,no_subtree_check)
/usr/local 192.168.0.0/255.255.255.0(rw,sync,no_subtree_check)

Here, rw makes the share read/write, and sync requires the server to only reply to requests once any changes have been flushed to disk. This is the safest option; async is faster, but dangerous. It is strongly recommended that you read man exports if you are considering other options.

After setting up /etc/exports, export the shares:

sudo exportfs -ra

You’ll want to run this command whenever /etc/exports is modified.

Restart services

By default, rpcbind only binds to the loopback interface. To enable access to rpcbind from remote machines, you need to change /etc/conf.d/rpcbind to get rid of either -l or -i 127.0.0.1.

If any changes are made, rpcbind and NFS will need to be restarted:

sudo systemctl restart rpcbind
sudo systemctl restart nfs-kernel-server

Security items to consider

Aside from the UID issues discussed above, it should be noted that an attacker could potentially masquerade as a machine that is allowed to map the share, which allows them to create arbitrary UIDs to access your files. One potential solution to this is IPSec. You can set up all your domain members to talk to each other only over IPSec, which will effectively authenticate that your client is who it says it is.

IPSec works by encrypting traffic to the server with the server’s public key, and the server sends back all replies encrypted with the client’s public key. The traffic is decrypted with the respective private keys. If the client doesn’t have the keys that it is supposed to have, it can’t send or receive data.

An alternative to IPSec is physically separate networks. This requires a separate network switch and separate Ethernet cards, and physical security of that network.

Troubleshooting

Mounting an NFS share inside an encrypted home directory will only work after you are successfully logged in and your home is decrypted. This means that using /etc/fstab to mount NFS shares on boot will not work, because your home has not been decrypted at the time of mounting. There is a simple way around this using symbolic links:

  1. Create an alternative directory to mount the NFS shares in:

sudo mkdir /nfs
sudo mkdir /nfs/music
  1. Edit /etc/fstab to mount the NFS share into that directory instead:

nfsServer:music    /nfs/music    nfs    auto    0 0
  1. Create a symbolic link inside your home, pointing to the actual mount location. For example, and in this case deleting the Music directory already existing there first:

rmdir /home/user/Music
ln -s /nfs/music/ /home/user/Music

Samba (SMB/CIFS)

Samba is an implementation of the SMB/CIFS networking protocol that is used by Microsoft Windows devices to provide shared access to files, printers, and serial ports.

You can use Samba to mount a folder shared from a Windows machine so it appears on your Raspberry Pi, or to share a folder from your Raspberry Pi so it can be accessed by your Windows machine.

Installing Samba Support

By default, Raspberry Pi OS does not include CIFS/Samba support, but this can be added. The following commands will install all the required components for using Samba as a server or a client.

sudo apt update
sudo apt install samba samba-common-bin smbclient cifs-utils

Mount a Folder Shared from Windows

First, you need to share a folder on your Windows device. This is quite a convoluted process!

Turn on sharing

  1. Open the Networking and Sharing Centre by right-clicking on the system tray and selecting it

  2. Click on Change advanced sharing settings

  3. Select Turn on network discovery

  4. Select Turn on file and printer sharing

  5. Save changes

Share the folder

You can share any folder you want, but for this example, simply create a folder called share.

  1. Create the folder share on your desktop.

  2. Right-click on the new folder, and select Properties.

  3. Click on the Sharing tab, and then the Advanced Sharing button

  4. Select Share this folder; by default, the share name is the name of the folder

  5. Click on the Permissions button

  6. For this example, select Everyone and Full Control (you can limit access to specific users if required); click OK when done, then OK again to leave the Advanced Sharing page

  7. Click on the Security tab, as we now need to configure the same permissions

  8. Select the same settings as the Permissions tab, adding the chosen user if necessary

  9. Click OK

The folder should now be shared.

Windows 10 Sharing Wizard

On Windows 10 there is a Sharing Wizard that helps with some of these steps.

  1. Run the Computer Management application from the Start Bar

  2. Select Shared Folders, then Shares

  3. Right-click and select New Share, which will start up the Sharing Wizard; click Next

  4. Select the folder you wish to share, and click Next

  5. Click Next to use all the sharing defaults

  6. Select Custom and set the required permissions, and click OK, then Finish

Mount the folder on the Raspberry Pi

Mounting in Linux is the process of attaching a folder to a location, so firstly we need that location.

mkdir windowshare

Now, we need to mount the remote folder to that location. The remote folder is the host name or IP address of the Windows PC, and the share name used when sharing it. We also need to provide the Windows username that will be used to access the remote machine.

sudo mount.cifs //<hostname or IP address>/share /home/pi/windowshare -o user=<name>

You should now be able to view the content of the Windows share on your Raspberry Pi.

cd windowshare
ls

"Host is down" error

This error is caused by a combination of two things: A SMB protocol version mismatch, and the CIFS client on Linux returning a misleading error message. In order to fix this a version entry needs to be added to the mount command. By default Raspberry Pi OS will only use versions 2.1 and above, which are compatible with Windows 7 and later. Older devices, including some NAS, may require version 1.0:

sudo mount.cifs //IP/share /mnt/point -o user=<uname>,vers=1.0

You may need to try different versions to match up with the server version. Possible values are:

Version Description

1.0

Classic CIFS/SMBv1 protocol

2.0

The SMBv2.002 protocol. Windows Vista Service Pack 1, and Windows Server 2008

2.1

The SMBv2.1 protocol. Microsoft Windows 7 and Windows Server 2008R2

3.0

The SMBv3.0 protocol. Microsoft Windows 8 and Windows Server 2012

3.02

The SMBv3.0.2 protocol. Microsoft Windows 8.1 and Windows Server 2012R2

3.11

The SMBv3.1.1 protocol. Microsoft Windows 10 and Windows Server 2016

3

The SMBv3.0 protocol version and above

Sharing a Folder from your Raspberry Pi

Firstly, create a folder to share. This example creates a folder called shared in the home folder of the current user, and assumes the current user is pi.

cd ~
mkdir shared
chmod 0740 shared

Now we need to tell Samba that there is a pi user when accessing that folder. When asked, enter the password of the pi user - this can be the default password, but that is well known and should be changed for better security.

sudo smbpasswd -a pi

Now we need to tell Samba to share this folder, using the Samba configuration file.

sudo nano /etc/samba/smb.conf

At the end of the file, add the following to share the folder, giving the remote user read/write permissions:

[share]
    path = /home/pi/shared
    read only = no
    public = yes
    writable = yes

In the same file, find the workgroup line, and if necessary, change it to the name of the workgroup of your local Windows network.

workgroup = <your workgroup name here>

That should be enough to share the folder. On your Windows device, when you browse the network, the folder should appear and you should be able to connect to it.

Virtual Network Computing (VNC)

Sometimes it is not convenient to work directly on the Raspberry Pi. Maybe you would like to work on it from another device by remote control.

VNC is a graphical desktop sharing system that allows you to remotely control the desktop interface of one computer (running VNC Server) from another computer or mobile device (running VNC Viewer). VNC Viewer transmits the keyboard and either mouse or touch events to VNC Server, and receives updates to the screen in return.

You will see the desktop of the Raspberry Pi inside a window on your computer or mobile device. You’ll be able to control it as though you were working on the Raspberry Pi itself.

VNC Connect from RealVNC is included with Raspberry Pi OS. It consists of both VNC Server, which allows you to control your Raspberry Pi remotely, and VNC Viewer, which allows you to control desktop computers remotely from your Raspberry Pi should you want to.

You must enable VNC Server before you can use it. By default, VNC Server gives you remote access to the graphical desktop that is running on your Raspberry Pi, as though you were sitting in front of it.

However, you can also use VNC Server to gain graphical remote access to your Raspberry Pi if it is headless or not running a graphical desktop. For more information on this, see Creating a virtual desktop, further below.

Installing VNC on Raspberry Pi

VNC is already installed on the full Raspberry Pi OS image, and can be installed via Recommended Software from the Preferences menu on other versions.

If you are not using a desktop you can install it from the command line as follows:

sudo apt update
sudo apt install realvnc-vnc-server realvnc-vnc-viewer

Enabling the VNC Server

You can do this graphically or at the command line.

Enabling VNC Server graphically

  • On your Raspberry Pi, boot into the graphical desktop.

  • Select Menu  Preferences  Raspberry Pi Configuration  Interfaces.

  • Ensure VNC is Enabled.

Enabling VNC Server at the command line

You can enable VNC Server at the command line using raspi-config:

sudo raspi-config

Now, enable VNC Server by doing the following:

  • Navigate to Interfacing Options.

  • Scroll down and select VNC  Yes.

Connecting to your Raspberry Pi

There are two ways to connect to your Raspberry Pi. You can use either or both, depending on what works best for you.

Establishing a direct connection

Direct connections are quick and simple providing you’re joined to the same private local network as your Raspberry Pi. For example, this might be a wired or wireless network at home, at school, or in the office.

  • On your Raspberry Pi (using a terminal window or via SSH) use these instructions or run ifconfig to discover your private IP address.

  • On the device you’ll use to take control, download VNC Viewer. For best results, use the compatible app from RealVNC.

  • Enter your Raspberry Pi’s private IP address into VNC Viewer:

Establishing a cloud connection

You are entitled to use RealVNC’s cloud service for free, provided that remote access is for educational or non-commercial purposes only.

Cloud connections are convenient and encrypted end-to-end. They are highly recommended for connecting to your Raspberry Pi over the internet. There’s no firewall or router reconfiguration, and you don’t need to know the IP address of your Raspberry Pi, or provide a static one.

  • Sign up for a RealVNC account here: it’s free and it only takes a few seconds.

  • On your Raspberry Pi, sign in to VNC Server using your new RealVNC account credentials:

  • On the device you’ll use to take control, download VNC Viewer. You must use the compatible app from RealVNC.

  • Sign in to VNC Viewer using the same RealVNC account credentials, and then either tap or click to connect to your Raspberry Pi:

Authenticating to VNC Server

To complete either a direct or cloud connection, you must authenticate to VNC Server.

If you’re connecting from the compatible VNC Viewer app from RealVNC, enter the user name and password you normally use to log in to your user account on the Raspberry Pi. By default, these credentials are pi and raspberry.

If you’re connecting from a non-RealVNC Viewer app, you’ll first need to downgrade VNC Server’s authentication scheme, specify a password unique to VNC Server, and then enter that instead.

  • If you are in front of your Raspberry Pi and can see its screen, open the VNC Server dialog on your Raspberry Pi, select Menu  Options  Security, and choose VNC password from the Authentication dropdown.

  • Or if you’re configuring your Raspberry Pi remotely from the command line, then to make the changes for Service Mode (the default configuration for the Raspberry Pi):

    • Open the /root/.vnc/config.d/vncserver-x11 config file.

    • Replace Authentication=SystemAuth with Authentication=VncAuth and save the file.

    • In the command line, run sudo vncpasswd -service. This will prompt you to set a password, and will insert it for you in the right config file for VNC Server running in Service Mode.

    • Restart VNC Server.

Using Directly Rendered Applications

You can remotely access apps which use a directly rendered overlay such as; the text console, the Raspberry Pi Camera Module, and others.

To turn this feature on:

  • On your Raspberry Pi, open the VNC Server dialog.

  • Navigate to Menu  Options  Troubleshooting and select Enable experimental direct capture mode.

  • On the device you’ll use to take control, run VNC Viewer and connect.

    Note
    Existing connections must be restarted in order for these changes to take effect.

Please note that direct screen capture is an experimental feature. If you’re connecting from a desktop computer and mouse movements seem erratic, try pressing F8 to open the VNC Viewer shortcut menu and selecting Relative Pointer Motion.

Creating a Virtual Desktop

If your Raspberry Pi is headless (i.e. not plugged into a monitor) or controlling a robot, it is unlikely to be running a graphical desktop.

VNC Server can create a virtual desktop for you, giving you graphical remote access on demand. This virtual desktop exists only in your Raspberry Pi’s memory:

To create and connect to a virtual desktop:

  • On your Raspberry Pi (using Terminal or via SSH), run vncserver. Make note of the IP address/display number that VNC Server will print to your Terminal (e.g. 192.167.5.149:1).

  • On the device you’ll use to take control, enter this information into VNC Viewer.

To destroy a virtual desktop, run the following command:

vncserver -kill :<display-number>

This will also stop any existing connections to this virtual desktop.

Setting up an Apache Web Server

Apache is a popular web server application you can install on the Raspberry Pi to allow it to serve web pages.

On its own, Apache can serve HTML files over HTTP, and with additional modules can serve dynamic web pages using scripting languages such as PHP.

Installing Apache

First, update the available packages by typing the following command into the Terminal:

sudo apt update

Then, install the apache2 package with this command:

sudo apt install apache2 -y

Test the Web Server

By default, Apache puts a test HTML file in the web folder. This default web page is served when you browse to http://localhost/ on the Raspberry Pi itself, or http://192.168.1.10 (whatever the Raspberry Pi’s IP address is) from another computer on the network. To find the Raspberry Pi’s IP address, type hostname -I at the command line (or read more about finding your IP address).

Browse to the default web page either on the Raspberry Pi or from another computer on the network and you should see the following:

Apache success message

This means you have Apache working!

Changing the Default Web Page

This default web page is just an HTML file on the filesystem. It is located at /var/www/html/index.html.

Navigate to this directory in a terminal window and have a look at what’s inside:

cd /var/www/html
ls -al

This will show you:

total 12
drwxr-xr-x  2 root root 4096 Jan  8 01:29 .
drwxr-xr-x 12 root root 4096 Jan  8 01:28 ..
-rw-r--r--  1 root root  177 Jan  8 01:29 index.html

This shows that by default there is one file in /var/www/html/ called index.html and it is owned by the root user (as is the enclosing folder). In order to edit the file, you need to change its ownership to your own username. Change the owner of the file (the default pi user is assumed here) using sudo chown pi: index.html.

You can now try editing this file and then refreshing the browser to see the web page change. If you know HTML you can put your own HTML files and other assets in this directory and serve them as a website on your local network.

Installing PHP for Apache

To allow your Apache server to process PHP files, you’ll need to install the latest version of PHP and the PHP module for Apache. Type the following command to install these:

sudo apt install php libapache2-mod-php -y

Now remove the index.html file:

sudo rm index.html

and create the file index.php:

sudo nano index.php

Put some PHP content in it:

<?php echo "hello world"; ?>

Now save and refresh your browser. You should see "hello world". This is not dynamic but still served by PHP. Try something dynamic:

<?php echo date('Y-m-d H:i:s'); ?>

or show your PHP info:

<?php phpinfo(); ?>

Network boot your Raspberry Pi

You can set up a DHCP/TFTP server which will allow you to boot a Raspberry Pi 3 or 4 from the network.

The instructions assume that you have an existing home network, and that you want to use a Raspberry Pi for the server. You will also need an additional Raspberry Pi 3 or 4 as a client to be booted. Only one SD Card is needed because the client will be booted from the server after the initial client configuration.

Note
Due to the huge range of networking devices and routers available, we can’t guarantee that network booting will work with any device. We have had reports that, if you cannot get network booting to work, disabling STP frames on your network may help.

Client Configuration

Raspberry Pi 3 Model B

Note
This section only applies to the Raspberry Pi 3 Model B, as network boot is enabled on the Raspberry Pi 3 Model B+ at the factory.

Before the Raspberry Pi 3 Model B will network boot it needs to be booted from an SD Card with a config option to enable USB boot mode. This will set a bit in the OTP (One Time Programmable) memory in the Raspberry Pi SoC that enables network booting. Once this is done, the Raspberry Pi 3B will attempt to boot from USB, and from the network, if it cannot boot from the SD card.

Install Raspberry Pi OS Lite, or Raspberry Pi OS with desktop, on the SD card in the usual fashion. Next, enable USB boot mode with the following command:

echo program_usb_boot_mode=1 | sudo tee -a /boot/config.txt

This adds program_usb_boot_mode=1 to the end of /boot/config.txt. Reboot the Raspberry Pi with sudo reboot. Once the client Raspberry Pi has rebooted, check that the OTP has been programmed with:

vcgencmd otp_dump | grep 17:
17:3020000a

Ensure the output 0x3020000a is correct.

The client configuration is almost done. The final thing to do is to remove the program_usb_boot_mode line from config.txt. You can do this with sudo nano /boot/config.txt, for example. Finally, shut the client Raspberry Pi down with sudo poweroff.

Raspberry Pi 4 Model B

Network boot can be enabled on the Raspberry Pi 4 using the raspi-config tool. First, run raspi-config as follows:

sudo raspi-config

Within raspi-config, choose Advanced Options, then Boot Order, then Network Boot. You must then reboot the device for the change to the boot order to be programmed into the bootloader EEPROM. Once the Raspberry Pi has rebooted, check that the boot order is now 0xf21:

vcgencmd bootloader_config

For further details of configuring the Raspberry Pi 4 bootloader, see Raspberry Pi 4 Bootloader Configuration.

Ethernet MAC address

Before configuring network boot, make a note of the serial number and mac address so that the board can be identified by the TFTP/DHCP server.

On Raspberry Pi 4 the MAC address is programmed at manufacture and there is no link between the MAC address and serial number. Both the MAC address and serial numbers are displayed on the bootloader HDMI diagnostics screen.

To find the Ethernet MAC address:

ethtool -P eth0

To find the serial number:

grep Serial /proc/cpuinfo | cut -d ' ' -f 2 | cut -c 8-16

Server Configuration

Plug the SD card into the server Raspberry Pi, and then boot the server. The client Raspberry Pi will need a root file system to boot from: we will use a copy of the server’s root filesystem and place it in /nfs/client1:

sudo mkdir -p /nfs/client1
sudo apt install rsync
sudo rsync -xa --progress --exclude /nfs / /nfs/client1

Regenerate SSH host keys on the client filesystem by chrooting into it:

cd /nfs/client1
sudo mount --bind /dev dev
sudo mount --bind /sys sys
sudo mount --bind /proc proc
sudo chroot .
rm /etc/ssh/ssh_host_*
dpkg-reconfigure openssh-server
exit
sudo umount dev sys proc

Find the settings of your local network. You need to find the address of your router (or gateway), which can be done with:

ip route | awk '/default/ {print $3}'

Then run:

ip -4 addr show dev eth0 | grep inet

which should give an output like:

inet 10.42.0.211/24 brd 10.42.0.255 scope global eth0

The first address is the IP address of your server Raspberry Pi on the network, and the part after the slash is the network size. It is highly likely that yours will be a /24. Also note the brd (broadcast) address of the network. Note down the output of the previous command, which will contain the IP address of the Raspberry Pi and the broadcast address of the network.

Finally, note down the address of your DNS server, which is the same address as your gateway. You can find this with:

cat /etc/resolv.conf

Configure a static network address on your server Raspberry Pi via the systemd networking, which works as the network handler and DHCP server.

To do that, you’ll need to create a 10-eth0.netdev and a 11-eth0.network like so:

sudo nano /etc/systemd/network/10-eth0.netdev

Add the following lines:

[Match]
Name=eth0

[Network]
DHCP=no

Then create a network file:

sudo nano /etc/systemd/network/11-eth0.network

Add the following contents:

[Match]
Name=eth0

[Network]
Address=10.42.0.211/24
DNS=10.42.0.1

[Route]
Gateway=10.42.0.1

At this point, you will not have working DNS, so you will need to add the server you noted down before to systemd/resolved.conf. In this example, the gateway address is 10.42.0.1.

sudo nano /etc/systemd/resolved.conf

Uncomment the DNS line and add the DNS IP address there. Additionally, if you have a fallback DNS server, add it there as well.

[Resolve]
DNS=10.42.0.1
#FallbackDNS=

Enable systemd-networkd and then reboot for the changes to take effect:

sudo systemctl enable systemd-networkd
sudo reboot

Now start tcpdump so you can search for DHCP packets from the client Raspberry Pi:

sudo apt install tcpdump dnsmasq
sudo systemctl enable dnsmasq
sudo tcpdump -i eth0 port bootpc

Connect the client Raspberry Pi to your network and power it on. Check that the LEDs illuminate on the client after around 10 seconds, then you should get a packet from the client "DHCP/BOOTP, Request from …​"

IP 0.0.0.0.bootpc > 255.255.255.255.bootps: BOOTP/DHCP, Request from b8:27:eb...

Now you need to modify the dnsmasq configuration to enable DHCP to reply to the device. Press CTRL + C to exit the tcpdump program, then type the following:

echo | sudo tee /etc/dnsmasq.conf
sudo nano /etc/dnsmasq.conf

Then replace the contents of dnsmasq.conf with:

# Note: comment out port if you want DNS services for systems on the network.
port=0
dhcp-range=10.42.0.255,proxy
log-dhcp
enable-tftp
tftp-root=/tftpboot
pxe-service=0,"Raspberry Pi Boot"

Where the first address of the dhcp-range line is, use the broadcast address you noted down earlier.

Now create a /tftpboot directory:

sudo mkdir /tftpboot
sudo chmod 777 /tftpboot
sudo systemctl enable dnsmasq.service
sudo systemctl restart dnsmasq.service

Now monitor the dnsmasq log:

tail -F /var/log/daemon.log

You should see something like this:

raspberrypi dnsmasq-tftp[1903]: file /tftpboot/bootcode.bin not found

Next, you will need to copy the contents of the boot folder into the /tftpboot directory.

First, press CTRL + C to exit the monitoring state. Then type the following:

cp -r /boot/* /tftpboot

Since the tftp location has changed, restart dnsmasq:

sudo systemctl restart dnsmasq

Set up NFS root

This should now allow your Raspberry Pi client to attempt to boot through until it tries to load a root file system (which it doesn’t have).

At this point, export the /nfs/client1 file system created earlier, and the TFTP boot folder.

sudo apt install nfs-kernel-server
echo "/nfs/client1 *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports
echo "/tftpboot *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee -a /etc/exports

Restart RPC-Bind and the NFS server in order to have them detect the new files.

sudo systemctl enable rpcbind
sudo systemctl restart rpcbind
sudo systemctl enable nfs-kernel-server
sudo systemctl restart nfs-kernel-server

Edit /tftpboot/cmdline.txt and from root= onwards, and replace it with:

root=/dev/nfs nfsroot=10.42.0.211:/nfs/client1,vers=4.1,proto=tcp rw ip=dhcp rootwait

You should substitute the IP address here with the IP address you have noted down. Also remove any part of the command line starting with init=.

Finally, edit /nfs/client1/etc/fstab and remove the /dev/mmcblk0p1 and p2 lines (only proc should be left). Then, add the boot partition back in:

echo "10.42.0.211:/tftpboot /boot nfs defaults,vers=4.1,proto=tcp 0 0" | sudo tee -a /nfs/client1/etc/fstab

Good luck! If it doesn’t boot on the first attempt, keep trying. It can take a minute or so for the Raspberry Pi to boot, so be patient.

Using pxetools

We have created a Python script that is used internally to quickly set up Raspberry Pis that will network boot.

The script takes a serial number, which you can find in cat /proc/cpuinfo, an owner name and the name of the Raspberry Pi. It then creates a root filesystem for that Raspberry Pi from a Raspberry Pi OS image. There is also a --list option which will print out the IP address of the Raspberry Pi, and a --remove option.

Note
The following instructions describe how to set up the environment required by the script starting from a fresh Raspberry Pi OS lite image. It might be a good idea to mount a hard disk or flash drive on /nfs so that your SD card isn’t providing filesystems to multiple Raspberry Pis. This is left as an exercise for the reader.
sudo apt update
sudo apt full-upgrade -y
sudo reboot

wget https://datasheets.raspberrypi.com/soft/prepare_pxetools.sh
bash prepare_pxetools

When prompted about saving iptables rules, say no.

The prepare_pxetools script should prepare everything you need to use pxetools.

We found that we needed to restart the nfs server after using pxetools for the first time. Do this with:

sudo systemctl restart nfs-kernel-server

Then plug in your Raspberry Pi and it should boot!

Network booting using IPv6

There are 4 stages to booting a Raspberry Pi computer over the network:

  1. The bootloader negotiates to get an IP address and the details of a TFTP server using DHCP.

  2. The bootloader loads the firmware via TFTP and hands over the boot process to the firmware, passing it the details of the network.

  3. The firmware loads the kernel and command line via TFTP.

  4. The kernel boots the rest of the system, loading the root filesystem (rootfs) via NFS or some other mechanism.

The bootloader and firmware (stages 1 to 3) have been enhanced to support booting over IPv6.

Important
IPv6 netboot is an experimental alpha feature and depending on feedback, we may need to change how it works in future. This only works on Raspberry Pi 4 and Compute Module 4.

How it works

To boot via IPv6 you need an updated version of the firmware (e.g. start4.elf) and the bootloader. Using the Bullseye release of Raspberry Pi OS and the latest stable bootloader should be sufficient.

Note
The commonly used dnsmasq DHCP server doesn’t currently support the network boot parameters required for IPv6 network boot, so for the time being you will have to use a different DHCP server such as ISC DHCP.

To mount rootfs over the network the IPv4 netboot tutorial suggests using nfsroot. This doesn’t support IPv6, so another method is needed to mount rootfs over the network.

If your ISP and router don’t support IPv6 you will be limited in what you can do.

Network addresses

The first thing the bootloader does is send a router solicitation to get the details of the network. The router responds with an advertisement packet identifying its ethernet address, which the bootloader might need if the TFTP server is on a different network.

The router advertisement includes a flag which tells it whether to use stateful (managed) or stateless (unmanaged) configuration for its IP address. Stateless configuration means that the device configures its own IP address. Currently the bootloader generates an address derived from its ethernet MAC address and a network prefix supplied by the router.

If the router indicates that stateful configuration is enabled DHCP is used to obtain the IP address of the device. This involves the device sending a solicitation request to a DHCP server which responds with an advertisement. The client then requests the address before getting a reply acknowledgement from the server.

DHCP Servers and clients identify themselves with variable length DUID (Device Unique ID). On the Raspberry Pi this is derived from the MAC address (DUID_LL).

TFTP address

Whether using stateless or stateful configuration, the DHCP server is used to obtain the TFTP server address. This is encoded in the BOOTFILE-URL parameter. We send the client architecture type value 0x29 to identify a device.

Boot process

The device should now have an IP address and TFTP details. It downloads the firmware binary start4.elf from the TFTP server and continues running with this. The firmware is passed the IP address and TFTP server details so it can download the kernel and boot the rest of the system.

Kernel Boot

With IPv4 netboot, nfsroot is used to mount rootfs over the network. This doesn’t support IPv6 so another solution is required. It might involve a small RAM file system that can mount the appropriate network location before switching to the proper rootfs contents.

Note
A mechanism to boot the Linux kernel with NFS via IPv6 is still to be demonstrated.

Test Setup

If you want to try this out you will need another Raspberry Pi to act as the TFTP and DHCP server.

The TFTP server can in theory be on any routable network but the DHCP server has to be on the same network as the devices it will serve.

TFTP Server

If you have a working IPv4 network boot setup you can reuse the TFTP server in dnsmasq to supply the files (it can talk to both IPv4 and IPv6).

Alternatively you can use a standalone TFTP server like tftpd-hpa.

$ sudo apt-get install tftpd-hpa
$ sudo systemctl start tftpd-hpa

DHCP Server

DHCP in IPv6 has changed a lot. We need DHCP to at least tell us the address of the TFTP server, which in this case is the same machine.

$ sudo apt-get install isc-dhcp-server

Modify the configuration in /etc/default/isc-dhcp-server

DHCPDv6_CONF=/etc/dhcp/dhcpd6.conf
INTERFACESv6="eth0"

In /etc/dhcp/dhcpd6.conf you need to specify the TFTP server address and setup a subnet. Here the DHCP server is configured to supply some made up unique local addresses (ULA). The host test-rpi4 line tells DHCP to give a test device a fixed address.

not authoritative;

# Check if the client looks like a Raspberry Pi
if option dhcp6.client-arch-type = 00:29 {
        option dhcp6.bootfile-url "tftp://[fd49:869:6f93::1]/";
}

subnet6 fd49:869:6f93::/64 {
        host test-rpi4 {
                host-identifier option dhcp6.client-id 00:03:00:01:e4:5f:01:20:24:0b;
                fixed-address6 fd49:869:6f93::1000;
        }
}

Your server has to be assigned the IPv6 address in /etc/dhcpcd.conf

interface eth0
static ip6_address=fd49:869:6f93::1/64

Now start the DHCP server.

$ sudo systemctl restart isc-dhcp-server.service

Bootloader

Modify the configuration to tell it to attempt network boot via IPv6 rather than IPv4.

BOOT_ORDER=0xf21 # 2=Network boot
USE_IPV6=1 # Enable IPv6 network boot
BOOT_UART=1 # Debug

To revert to IPv4 network boot just remove the USE_IPV6 line from boot.conf.

Router

To use IPv6 you really need a router and ISP that supports IPv6. There are sites on the internet that can check this for you or alternatively run the following command.

sudo apt-get install ndisc6
rdisc6 -1 eth0

This sends a router solicitation to your router asking for your network details such as the network prefix, router ethernet address and whether to use DHCP for addressing. If there’s no response to this command it’s likely your network and ISP only supports IPv4. If IPv6 is supported it’s most likely that it will be configured to use stateless configuration where clients generate their own addresses.

Soliciting ff02::2 (ff02::2) on eth0...
Hop limit                 :           64 (      0x40)
Stateful address conf.    :           No
Stateful other conf.      :          Yes
Mobile home agent         :           No
Router preference         :       medium
Neighbor discovery proxy  :           No
Router lifetime           :          180 (0x000000b4) seconds
Reachable time            :  unspecified (0x00000000)
Retransmit time           :  unspecified (0x00000000)

You might be able to configure your router for stateful configuration, which means it will use DHCP to obtain an IP address.

Hop limit                 :           64 (      0x40)
Stateful address conf.    :          Yes
Stateful other conf.      :          Yes
Mobile home agent         :           No
Router preference         :       medium
Neighbor discovery proxy  :           No
Router lifetime           :          180 (0x000000b4) seconds
Reachable time            :  unspecified (0x00000000)
Retransmit time           :  unspecified (0x00000000)

Debugging

Logs and Traces

If the boot uart is enabled you should see something like this from the serial port. The lines starting RX6 indicate that IPv6 is in use.

Here dc:a6:32:6f:73:f4 is the MAC address of the TFTP server and it has an IPv6 address of fd49:869:6f93::1. The device itself has a MAC address e4:5f:01:20:24:0b and an IPv6 address of fd49:869:6f93::1000

Boot mode: NETWORK (02) order f
GENET: RESET_PHY
PHY ID 600d 84a2
NET_BOOT: e4:5f:01:20:24:0b wait for link TFTP6: (null)
LINK STATUS: speed: 100 full duplex
Link ready
GENET START: 64 16 32
GENET: UMAC_START 0xe45f0120 0x240b0000
RX6: 12 IP: 1 MAC: 1 ICMP: 1/1 UDP: 0/0 ICMP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
NET fd49:869:6f93::1000 tftp fd49:869:6f93::1
RX6: 17 IP: 4 MAC: 4 ICMP: 2/2 UDP: 2/2 ICMP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
TFTP_GET: dc:a6:32:6f:73:f4 fd49:869:6f93::1 ab5a4158/start4.elf

RX6: 17 IP: 4 MAC: 4 ICMP: 2/2 UDP: 2/2 ICMP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
RX6: 18 IP: 5 MAC: 5 ICMP: 2/2 UDP: 3/3 ICMP_CSUM_ERR: 0 UDP_CSUM_ERR: 0
TFTP_GET: dc:a6:32:6f:73:f4 fd49:869:6f93::1 ab5a4158/config.txt

Finally the bootloader hands over to firmware which should load the kernel.

Stateful configuration

You can examine network activity with tcpdump.

$ sudo tcpdump -i eth0 -e ip6 -XX -l -v -vv

Below is an extract of a TCP dump where the router is configured to use stateful (DHCP) network configuration.

Device sends a router solicitation.

12:23:35.387046 e4:5f:01:20:24:0b (oui Unknown) > 33:33:00:00:00:02 (oui Unknown), ethertype IPv6 (0x86dd), length 70: (hlim 255, next-header ICMPv6 (58) payload length: 16) fe80::e65f:1ff:fe20:240b > ip6-allrouters: [icmp6 sum ok] ICMP6, router solicitation, length 16
          source link-address option (1), length 8 (1): e4:5f:01:20:24:0b
            0x0000:  e45f 0120 240b

Router sends a response telling the device to use stateful configuration.

12:23:35.498902 60:8d:26:a7:c1:88 (oui Unknown) > 33:33:00:00:00:01 (oui Unknown), ethertype IPv6 (0x86dd), length 110: (hlim 255, next-header ICMPv6 (58) payload length: 56) bthub.home > ip6-allnodes: [icmp6 sum ok] ICMP6, router advertisement, length 56
        hop limit 64, Flags [managed, other stateful], pref medium, router lifetime 180s, reachable time 0ms, retrans timer 0ms
          rdnss option (25), length 24 (3):  lifetime 60s, addr: bthub.home
            0x0000:  0000 0000 003c fe80 0000 0000 0000 628d
            0x0010:  26ff fea7 c188
          mtu option (5), length 8 (1):  1492
            0x0000:  0000 0000 05d4
          source link-address option (1), length 8 (1): 60:8d:26:a7:c1:88
            0x0000:  608d 26a7 c188

Device sends a DHCP solicitation.

12:23:35.502517 e4:5f:01:20:24:0b (oui Unknown) > 33:33:00:01:00:02 (oui Unknown), ethertype IPv6 (0x86dd), length 114: (hlim 255, next-header UDP (17) payload length: 60) fe80::e65f:1ff:fe20:240b.dhcpv6-client > ff02::1:2.dhcpv6-server: [udp sum ok] dhcp6 solicit (xid=8cdd56 (client-ID hwaddr type 1 e45f0120240b) (IA_NA IAID:0 T1:0 T2:0) (option-request opt_59) (opt_61) (elapsed-time 0))

The DHCP server replies with an advertisement.

12:23:35.510478 dc:a6:32:6f:73:f4 (oui Unknown) > e4:5f:01:20:24:0b (oui Unknown), ethertype IPv6 (0x86dd), length 172: (flowlabel 0xad54d, hlim 64, next-header UDP (17) payload length: 118) fe80::537a:52c:c647:b184.dhcpv6-server > fe80::e65f:1ff:fe20:240b.dhcpv6-client: [bad udp cksum 0xd886 -> 0x6d26!] dhcp6 advertise (xid=8cdd56 (IA_NA IAID:0 T1:3600 T2:7200 (IA_ADDR fd49:869:6f93::1000 pltime:604800 vltime:2592000)) (client-ID hwaddr type 1 e45f0120240b) (server-ID hwaddr/time type 1 time 671211709 dca6326f73f4) (opt_59))

The device sends a request for an address and TFTP details to the DHCP server.

12:23:35.510763 e4:5f:01:20:24:0b (oui Unknown) > 33:33:00:01:00:02 (oui Unknown), ethertype IPv6 (0x86dd), length 132: (hlim 255, next-header UDP (17) payload length: 78) fe80::e65f:1ff:fe20:240b.dhcpv6-client > ff02::1:2.dhcpv6-server: [udp sum ok] dhcp6 request (xid=8cdd56 (client-ID hwaddr type 1 e45f0120240b) (server-ID hwaddr/time type 1 time 671211709 dca6326f73f4) (IA_NA IAID:0 T1:0 T2:0) (option-request opt_59) (opt_61) (elapsed-time 1))

The DHCP server replies, opt_59 is used to pass the address of the TFTP server.

12:23:35.512122 dc:a6:32:6f:73:f4 (oui Unknown) > e4:5f:01:20:24:0b (oui Unknown), ethertype IPv6 (0x86dd), length 172: (flowlabel 0xad54d, hlim 64, next-header UDP (17) payload length: 118) fe80::537a:52c:c647:b184.dhcpv6-server > fe80::e65f:1ff:fe20:240b.dhcpv6-client: [bad udp cksum 0xd886 -> 0x6826!] dhcp6 reply (xid=8cdd56 (IA_NA IAID:0 T1:3600 T2:7200 (IA_ADDR fd49:869:6f93::1000 pltime:604800 vltime:2592000)) (client-ID hwaddr type 1 e45f0120240b) (server-ID hwaddr/time type 1 time 671211709 dca6326f73f4) (opt_59))

The device sends a neighbour solicitation to the FTP server because it needs its MAC address.

12:23:36.510768 e4:5f:01:20:24:0b (oui Unknown) > 33:33:ff:00:00:01 (oui Unknown), ethertype IPv6 (0x86dd), length 86: (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::e65f:1ff:fe20:240b > ff02::1:ff00:1: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has fd49:869:6f93::1
          source link-address option (1), length 8 (1): e4:5f:01:20:24:0b
            0x0000:  e45f 0120 240b

The FTP server replies with its MAC address.

12:23:36.510854 dc:a6:32:6f:73:f4 (oui Unknown) > e4:5f:01:20:24:0b (oui Unknown), ethertype IPv6 (0x86dd), length 86: (hlim 255, next-header ICMPv6 (58) payload length: 32) fd49:869:6f93::1 > fe80::e65f:1ff:fe20:240b: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is fd49:869:6f93::1, Flags [solicited, override]
          destination link-address option (2), length 8 (1): dc:a6:32:6f:73:f4
            0x0000:  dca6 326f 73f4

TFTP requests are made by the device which should now boot over the network.

12:23:36.530820 e4:5f:01:20:24:0b (oui Unknown) > dc:a6:32:6f:73:f4 (oui Unknown), ethertype IPv6 (0x86dd), length 111: (hlim 255, next-header UDP (17) payload length: 57) fd49:869:6f93::1000.61785 > fd49:869:6f93::1.tftp: [udp sum ok]  49 RRQ "ab5a4158/start4.elf" octet tsize 0 blksize 1024

Stateless configuration

Below is an extract of a tcp dump for a stateless (non-DHCP) network configuration.

The device sends a router solicitation.

12:55:27.541909 e4:5f:01:20:24:0b (oui Unknown) > 33:33:00:00:00:02 (oui Unknown), ethertype IPv6 (0x86dd), length 70: (hlim 255, next-header ICMPv6 (58) payload length: 16) fe80::e65f:1ff:fe20:240b > ip6-allrouters: [icmp6 sum ok] ICMP6, router solicitation, length 16
          source link-address option (1), length 8 (1): e4:5f:01:20:24:0b
            0x0000:  e45f 0120 240b

The router replies with the network details.

12:55:27.834684 60:8d:26:a7:c1:88 (oui Unknown) > 33:33:00:00:00:01 (oui Unknown), ethertype IPv6 (0x86dd), length 174: (hlim 255, next-header ICMPv6 (58) payload length: 120) bthub.home > ip6-allnodes: [icmp6 sum ok] ICMP6, router advertisement, length 120
        hop limit 64, Flags [other stateful], pref medium, router lifetime 180s, reachable time 0ms, retrans timer 0ms
          prefix info option (3), length 32 (4): 2a00:23c5:ee00:5001::/64, Flags [onlink, auto, router], valid time 300s, pref. time 120s
            0x0000:  40e0 0000 012c 0000 0078 0000 0000 2a00
            0x0010:  23c5 ee00 5001 0000 0000 0000 0000
          prefix info option (3), length 32 (4): fd4d:869:6f93::/64, Flags [onlink, auto, router], valid time 10080s, pref. time 2880s
            0x0000:  40e0 0000 2760 0000 0b40 0000 0000 fd4d
            0x0010:  0869 6f93 0000 0000 0000 0000 0000
          rdnss option (25), length 24 (3):  lifetime 60s, addr: bthub.home
            0x0000:  0000 0000 003c fe80 0000 0000 0000 628d
            0x0010:  26ff fea7 c188
          mtu option (5), length 8 (1):  1492
            0x0000:  0000 0000 05d4
          source link-address option (1), length 8 (1): 60:8d:26:a7:c1:88
            0x0000:  608d 26a7 c188

The device sends an information request to the DHCP multicast address asking for the TFTP details.

12:55:27.838300 e4:5f:01:20:24:0b (oui Unknown) > 33:33:00:01:00:02 (oui Unknown), ethertype IPv6 (0x86dd), length 98: (hlim 255, next-header UDP (17) payload length: 44) fe80::e65f:1ff:fe20:240b.dhcpv6-client > ff02::1:2.dhcpv6-server: [udp sum ok] dhcp6 inf-req (xid=e5e0a4 (client-ID hwaddr type 1 e45f0120240b) (option-request opt_59) (opt_61) (elapsed-time 0))

The DHCP server replies with the TFTP server details (opt_59).

12:55:27.838898 dc:a6:32:6f:73:f4 (oui Unknown) > e4:5f:01:20:24:0b (oui Unknown), ethertype IPv6 (0x86dd), length 150: (flowlabel 0xd1248, hlim 64, next-header UDP (17) payload length: 96) fe80::537a:52c:c647:b184.dhcpv6-server > fe80::e65f:1ff:fe20:240b.dhcpv6-client: [bad udp cksum 0xd870 -> 0x78bb!] dhcp6 reply (xid=e5e0a4 (client-ID hwaddr type 1 e45f0120240b) (server-ID hwaddr/time type 1 time 671211709 dca6326f73f4) (opt_59))

The device asks for the TFTP server MAC address since it can tell it’s on the same network.

12:55:28.834796 e4:5f:01:20:24:0b (oui Unknown) > 33:33:ff:1d:fe:2a (oui Unknown), ethertype IPv6 (0x86dd), length 86: (hlim 255, next-header ICMPv6 (58) payload length: 32) fe80::e65f:1ff:fe20:240b > ff02::1:ff1d:fe2a: [icmp6 sum ok] ICMP6, neighbor solicitation, length 32, who has 2a00:23c5:ee00:5001:57f1:7523:2f1d:fe2a
          source link-address option (1), length 8 (1): e4:5f:01:20:24:0b
            0x0000:  e45f 0120 240b

The FTP server replies with its MAC address.

12:55:28.834875 dc:a6:32:6f:73:f4 (oui Unknown) > e4:5f:01:20:24:0b (oui Unknown), ethertype IPv6 (0x86dd), length 86: (hlim 255, next-header ICMPv6 (58) payload length: 32) 2a00:23c5:ee00:5001:57f1:7523:2f1d:fe2a > fe80::e65f:1ff:fe20:240b: [icmp6 sum ok] ICMP6, neighbor advertisement, length 32, tgt is 2a00:23c5:ee00:5001:57f1:7523:2f1d:fe2a, Flags [solicited, override]
          destination link-address option (2), length 8 (1): dc:a6:32:6f:73:f4
            0x0000:  dca6 326f 73f4

The device starts making TFTP requests.

12:55:28.861097 e4:5f:01:20:24:0b (oui Unknown) > dc:a6:32:6f:73:f4 (oui Unknown), ethertype IPv6 (0x86dd), length 111: (hlim 255, next-header UDP (17) payload length: 57) 2a00:23c5:ee00:5001:e65f:1ff:fe20:240b.46930 > 2a00:23c5:ee00:5001:57f1:7523:2f1d:fe2a.tftp: [udp sum ok]  49 RRQ "ab5a4158/start4.elf" octet tsize 0 blksize 1024