Setting up a Netboot (PXE) Server for Alpine Linux (2023)

I’ve played with network booting before (and I even tried to netboot Windows, what a nightmare that was), but now I want to get serious about it. I want to netboot my VDI Clients. I’ve been working on a thin client series, and the next step is to get rid of the installation entirely. I could use something like Linux Terminal Server Project, but that’s a bit overkill for this, and I wanted to learn Alpine anyway, so I’ve chosen to use Alpine Linux for the client operating system. It’s extremely basic. However, even using Alpine is still a huge setup, so this article will focus on setting up a netboot server (also using Alpine Linux), to prepare for the next video where I create the Proxmox VDI Client using this netboot system.


Here’s a table of contents:

  • Video
  • The Basics
  • Netboot Server Setup
  • Setup HTTP Server
  • Download Alpine Netboot
  • Setup TFTP Server
  • Configure Netboot in OPNsense
  • Testing


Click on the thumbnail to watch the video for this!Setting up a Netboot (PXE) Server for Alpine Linux (1)

The Basics

The boot process looks something like this:

  1. BIOS/UEFI on victim machine decides it should network boot, either because that’s the highest priority boot order or it can’t find any other options.
  2. BIOS/UEFI loads the PXE ROM from the network card and executes it.
  3. PXE code does a DHCP DISCOVER, finds the DHCP server, and receives a next-server. It may also receive one or more boot filenames for UEFI/BIOS.
  4. PXE code downloads the boot filename from the next-server address via TFTP and executes it. If filename is not provided, it first does a DHCP request to the address of next-server (instead of broadcast like normal) to get the filename. This method is called ‘Proxy DHCP’. We will be using that method today.
  5. We’ve compiled a special version of iPXE with an embedded script, and that is the boot code being executed at this point. iPXE is a ‘better’ PXE ROM. Here we have the option of hardcoding the boot commands into an embedded script. The script also has the option of requesting more commands over HTTP, so you can compile a single version of iPXE and use the HTTP server along with CGI/PHP/… to decide what boot commands a specific client needs, since the HTTP request can include things like the MAC address formatted in the URL. We will not be doing this today, but it’s a future enhancement.
  6. The script includes commands to download and load the kernel and initrd over HTTP instead of TFTP, so using iPXE saves us a ton of time over using TFTP and gives us more control over what arguments to pass to the kernel.
  7. For Alpine we can add a modloop argument which specifies the HTTP address to download the modloop file. If you provide it, that file will contain all of the additional kernel modules that may be needed after the early boot process.
  8. For Alpine, we can pass the URL of an Alpine mirror as a kernel command line argument, so the system has a working package manager by default. Normal systems would use the CD/USB IMG as the mirror until you configure networking, so this can get a fully functional working system without an image at all.
  9. For Alpine, we can pass the URL to an APKOVL file which will be applied on top of the initramfs root image. This usually includes /etc but can include more. It also includes the /etc/apk/world file, which is the list of packages that should be installed. Essentially, given an apkovl file, Alpine will rebuild the installation that was saved previously, using a combination of downloading and installing packages from the mirror configured in the /etc/apk/repositories file and the configuration and user data in the APKOVL file.
  10. Due to the modloop and apkovl, we have a completely running system using only 4 files on the server (vmlinuz, initramfs, modloop, apkovl) which will then configure itself exactly as we want. We can export the configuration of a running system using lbu to generate the apkovl file, and the other 3 files are provided by Alpine for netbooting. If we include /home in our apkovl, we can include all of the configuration we need for the thin client without having to mount anything off NFS.
  11. (Optional) If we are doing this in a big organization, we shouldn’t hammer the Alpine package servers every time a thin client boots, so you should setup your own local mirror. That’s beyond the scope of this tutorial. Alpine provides a public rsync server for mirrors to synchronize from, and you can configure your mirror to only mirror specific Alpine versions and architectures to save space and pass requests to the primary mirrors otherwise.

Netboot Server Setup

For the server, we can choose whatever OS we want. But, I’m into Alpine now, so I’m going to use Alpine for the server as well. I’m installing it on Proxmox VE as a VM. I used the full extended ISO, but I don’t think that’s necessary. I ran setup-alpine to install it to disk using sys mode, which is the full system installed on disk and not running from RAM.

Alpine is pretty darn minimal. We need a text editor, it doesn’t come with nano.

#Nano is a text editor that's usefulapk add nano

Now that we have nano we can add the community repository (nano /etc/apk/repositories) and uncomment the line ending in community, but not the ones with edge in them.

And finally, enable root SSH login for now so we can setup the system (disable this once you are done):nano /etc/ssh/sshd_config, find the line starting with #PermitRootLogin and change the line to PermitRootLogin yes. Restart the server using rc-service sshd restart. From now on you can use ssh for the rest of the commands.

Setup HTTP Server

Going to use nginx here, since we need PHP support in the future. You could also use lighttpd.

#Install itapk add nginx#Add www user for nginx to run asadduser -D -g 'www' wwwmkdir /srv/wwwchown -R www:www /var/lib/nginxchown -R www:www /srv/www#Backup old nginx config since we will rewrite itmv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak

Now we need to create an nginx config as simple as possible, since the default one will 404 on everything. So nano /etc/nginx/nginx.conf and paste this in:

user www;worker_processes auto;error_log /var/log/nginx/error.log warn;pid /var/run/nginx/;events { worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; access_log /var/log/nginx/access.log; keepalive_timeout 3000; server { listen 80; root /srv/www; index index.html index.htm; server_name localhost; client_max_body_size 32m; error_page 500 502 503 504 /50x.html; location = /50x.html { root /var/lib/nginx/html; } }}

And finally configure it to start on boot:

#Start on bootrc-update add nginx#Start nowrc-service nginx start

Download Alpine Netboot

Alpine provides netboot images in a .tar.gz format. We need to put these in our /srv/www directory.

Go to the Alpine download page and copy the link to the netboot x86_64. As of the writing of this, the file is called alpine-netboot-3.15.4-x86_64.tar.gz.

Now download the file:

#Move to web directorycd /srv/www#Download alpine linkwget <paste the link here>#Untar ittar -xzf alpine*#Delete the tarrm alpine-netboot*#Files are now in boot folder

There are two versions of the files, virt and lts. Virt is stripped down to only include the drivers needed to run on common hypervisors, while lts should include more driver support.

Setup TFTP Server

We’re gonna need a TFTP server for the ipxe image, since PXE normally requires TFTP to be used. Let’s get that setup now.

#Install itapk add tftp-hpa#Add it to run as a servicerc-update add in.tftpd#Start service nowrc-service in.tftpd start#Default directory is /var/tftpboot/

Since tftp-hpa creates a chroot for itself, it can’t follow symlinks up the directory tree, so we will need to copy our files into /var/tftpboot/ instead of symlinking them. We will need to be aware of this when we install iPXE later.

Download & Configure iPXE

Now that we have a functional server, we need to write our embedded ipxe script to download vmlinuz and friends over HTTP, then compile this into an ipxe binary and put it on our tftp server.

First we need to install git then clone the ipxe repo:

#Install git, make, gcc, perl, all the stuff ipxe will needapk add git make binutils mtools perl xz-dev libc-dev gcc#Going to put ipxe in the srv foldercd /srv#Clone ipxegit clone into itcd ipxe/src

Now we can create an embedded script (nano netboot.ipxe):

#!ipxe#Init networkingdhcp#Networking info we got from the DHCP serverecho next-server is ${next-server}echo filaneme is ${filename}echo MAC address is ${net0/mac}echo IP address is ${ip}#Set flavor to ltsset flavor ltsecho flavor is ${flavor}#Set command line set cmdline modules=loop,squashfs quietecho cmdline is ${cmdline}#Server addressset server http://${next-server}echo server is ${server}#Kernel fileset vmlinuz ${server}/boot/vmlinuz-${flavor}echo vmlinuz is ${vmlinuz}set initramfs ${server}/boot/initramfs-${flavor}echo initramfs is ${initramfs}#Modloop fileset modloop ${server}/boot/modloop-${flavor}echo modloop is ${modloop}#Repository for apk#Update this if you'd like a newer version of Alpine#Alternatively, set branch to edge for the absolutel latestset mirror branch v3.15set repo ${mirror}/${branch}/mainecho repo is ${repo}#apkovl file - set this if you want to apply#an apkovl file to configure the Alpne instanceset apkovl ${server}/thinclient.apkovl.tar.gzecho apkovl is ${apkovl}#Uncomment this if you want to see the information before continuing#prompt Press any key to continue#Kernel, initrd#For EFI, we need to tell the kernel the initrd filename. For BIOS it doens't hurt to leave the initrd argument.#If you want to use Alpine bare, use this line:#kernel ${vmlinuz} ${cmdline} alpine_repo=${repo} modloop=${modloop} initrd=initramfs-${flavor}#If you want to use Alpine with an apkovl, use this line:kernel ${vmlinuz} ${cmdline} modloop=${modloop} apkovl=${apkovl} initrd=initramfs-${flavor}initrd ${initramfs}#Bootboot#Pause if errorsprompt Some error occurred, press any key to continue

Then we build ipxe (make a script nano then chomod +x to automate this):

#Build BIOS version (x86 but should boot into x64 environment)make bin-i386-pcbios/undionly.kpxe EMBED=netboot.ipxe#Build EFI version (x86)make bin-x86_64-efi/ipxe.efi EMBED=netboot.ipxe#Copy files to tftp rootcp bin-i386-pcbios/undionly.kpxe /var/tftpboot/cp bin-x86_64-efi/ipxe.efi /var/tftpboot/ipxe64.efi#The APKOVL we are using is for x64, so not building ipxe32.efi right now#Also not building arm variants for this project

After this, we should have the binaries in our tftp directory and can enable netbooting in our DHCP server

GCC 12 Resolution

Error-checking in GCC 12 results in many compile errors for iPXE, as referenced by this issue. Until the issue is resolved on their end, use this script instead to treat errors as warnings:

#Build BIOS version (x86 but should boot into x64 environment)NO_WERROR=1 make bin-i386-pcbios/undionly.kpxe EMBED=netboot.ipxe#Build EFI version (x86)NO_WERROR=1 make bin-x86_64-efi/ipxe.efi EMBED=netboot.ipxe#Copy files to tftp rootcp bin-i386-pcbios/undionly.kpxe /var/tftpboot/cp bin-x86_64-efi/ipxe.efi /var/tftpboot/ipxe64.efi#The APKOVL we are using is for x64, so not building ipxe32.efi right now#Also not building arm variants for this project

Configure Netboot in OPNsense

I use OPNsense as my firewall, so here’s how I added the next-server and filenames to OPNsense’s DHCP server.


If all goes well, you should be able to boot your client, and as long as PXE is enabled higher than any other bootable devices in the boot order, it should launch into our thin client. You can even configure a VM in Proxmox with no disk and no DVD drive and it should netboot.

Top Articles
Latest Posts
Article information

Author: Tyson Zemlak

Last Updated: 30/09/2023

Views: 5765

Rating: 4.2 / 5 (63 voted)

Reviews: 94% of readers found this page helpful

Author information

Name: Tyson Zemlak

Birthday: 1992-03-17

Address: Apt. 662 96191 Quigley Dam, Kubview, MA 42013

Phone: +441678032891

Job: Community-Services Orchestrator

Hobby: Coffee roasting, Calligraphy, Metalworking, Fashion, Vehicle restoration, Shopping, Photography

Introduction: My name is Tyson Zemlak, I am a excited, light, sparkling, super, open, fair, magnificent person who loves writing and wants to share my knowledge and understanding with you.