{"id":4941,"date":"2025-06-16T22:43:58","date_gmt":"2025-06-17T02:43:58","guid":{"rendered":"https:\/\/dft.wiki\/?p=4941"},"modified":"2026-06-08T09:37:20","modified_gmt":"2026-06-08T13:37:20","slug":"network-os-installation-with-pxe-linux-and-ipxe-boot","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=4941","title":{"rendered":"Network Installation with PXE Boot and Local Packages"},"content":{"rendered":"<p>Technologies like <strong>PXE<\/strong> (Preboot Execution Environment) and its open-source implementation <strong>iPXE<\/strong> are essential for managing multiple machines or deploying operating systems at scale (e.g., in a data centre).<\/p>\n<p><strong>PXE Boot Chain<\/strong><\/p>\n<ul>\n<li>NIC with PXE Firmware<\/li>\n<li>BIOS capable of booting from NIC<\/li>\n<li>DHCP with options 66 and 67<\/li>\n<li>TFTP server with NBP<\/li>\n<li>and so on.<\/li>\n<\/ul>\n<p><strong>DHCP<\/strong> option <strong>66<\/strong> (&#8220;next server&#8221;) specifies the address of the <strong>TFTP<\/strong> server, and option <strong>67<\/strong> (&#8220;bootfile name&#8221;) specifies the <strong>NBP<\/strong> (<strong>N<\/strong>etwork <strong>B<\/strong>ootstrap <strong>P<\/strong>rogram). For example, <strong>66<\/strong> set to <code>10.1.1.1<\/code> and <strong>67<\/strong> set to <code>pxelinux.0<\/code>.<\/p>\n<p>The chain is necessary because the NBP must be very small, as it is a pre-boot program designed to fetch and load the target system.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4981\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-1.drawio.png\" alt=\"\" width=\"380\" height=\"264\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-1.drawio.png 380w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-1.drawio-300x208.png 300w\" sizes=\"auto, (max-width: 380px) 100vw, 380px\" \/><\/p>\n<p>The OS installer can be served from the same TFTP server or shared via HTTP, NFS, or SMB.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4982\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-2.drawio.png\" alt=\"\" width=\"440\" height=\"263\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-2.drawio.png 440w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/pxe-2.drawio-300x179.png 300w\" sizes=\"auto, (max-width: 440px) 100vw, 440px\" \/><\/p>\n<p><strong>Note:<\/strong> The DHCP, TFTP, and HTTP servers can run on the same host or on different ones. For example, DHCP could be handled by a network appliance such as a Cisco router, TFTP by a NAS, and HTTP by any machine on the LAN or the Internet.<\/p>\n<hr \/>\n<p><strong>INDEX<\/strong><\/p>\n<ul>\n<li><strong>Installing<\/strong>\n<ul>\n<li>DHCP Server<\/li>\n<li>TFTP Server<\/li>\n<li>HTTP Server<\/li>\n<\/ul>\n<\/li>\n<li><strong>Configuring<\/strong>\n<ul>\n<li>NAT \/ Routing<\/li>\n<li>PXE Boot<\/li>\n<li>iPXE Boot<\/li>\n<\/ul>\n<\/li>\n<li><span style=\"text-decoration: underline;\"><strong>PXE the Easy Way<\/strong><\/span>\n<ul>\n<li>NetBoot.xyz [<a href=\"https:\/\/netboot.xyz\/\">Link<\/a>]<\/li>\n<li>LinuxServer.io [<a href=\"https:\/\/www.linuxserver.io\/\">Link<\/a>]<\/li>\n<li>iVentoy [<a href=\"https:\/\/www.iventoy.com\/\">Link<\/a>]<\/li>\n<\/ul>\n<\/li>\n<li><strong>Auto Install<\/strong>\n<ul>\n<li>Ubuntu Server<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<hr \/>\n<p><strong>INSTALLING PACKAGES<\/strong><\/p>\n<pre>apt update &amp;&amp; apt upgrade -y\r\napt install isc-dhcp-server tftpd-hpa pxelinux syslinux-common -y<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4943\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_21-52-08.png\" alt=\"\" width=\"951\" height=\"441\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_21-52-08.png 951w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_21-52-08-300x139.png 300w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_21-52-08-768x356.png 768w\" sizes=\"auto, (max-width: 951px) 100vw, 951px\" \/><\/p>\n<p>If you see an error message immediately after installation, that is expected. The DHCP service failed to start because another DHCP server is already active on the same network segment.<\/p>\n<hr \/>\n<p><strong>DHCP SERVER<\/strong><\/p>\n<p>Specify the network interface the DHCP service should bind to.<\/p>\n<pre>nano \/etc\/default\/isc-dhcp-server<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4942\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_22-07-44.png\" alt=\"\" width=\"581\" height=\"63\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_22-07-44.png 581w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-15_22-07-44-300x33.png 300w\" sizes=\"auto, (max-width: 581px) 100vw, 581px\" \/><\/p>\n<p>Define the parameters to be assigned to the client&#8217;s network adapter.<\/p>\n<pre>nano \/etc\/dhcp\/dhcpd.conf<\/pre>\n<pre>subnet 10.1.1.0 netmask 255.255.255.0 {\r\n  range 10.1.1.100 10.1.1.200;\r\n  option routers 10.1.1.1;\r\n  option broadcast-address 10.1.1.255;\r\n  option domain-name-servers 1.1.1.1;\r\n  next-server <strong>10.1.1.1<\/strong>; \r\n  filename \"<strong>pxelinux.0<\/strong>\";\r\n}<\/pre>\n<p><strong>Note:<\/strong> In this example, the DHCP and TFTP servers are the same host. If running TFTP on a different server, update the <code>next-server<\/code> IP accordingly.<\/p>\n<p>Apply the changes.<\/p>\n<pre>systemctl restart isc-dhcp-server\r\nsystemctl enable isc-dhcp-server<\/pre>\n<p>The equivalent commands to set options 66 and 67 on a <strong>Ubiquiti EdgeRouter<\/strong> are:<\/p>\n<pre><strong>$<\/strong> configure\r\n<strong>#<\/strong> show service dhcp-server\r\n<strong>#<\/strong> set service dhcp-server shared-network-name LAN subnet <strong>10.1.1.0<\/strong><strong>\/24<\/strong> bootfile-name <strong>pxelinux.0<\/strong>\r\n<strong>#<\/strong> set service dhcp-server shared-network-name LAN subnet <strong>10.1.1.0\/24<\/strong> tftp-server-name <strong>10.1.1.1<\/strong>\r\n<strong>#<\/strong> commit\r\n<strong>#<\/strong> save<\/pre>\n<p>This is what the DHCP offer looks like in a <strong>packet capture<\/strong>.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4969\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-20_20-39-56.png\" alt=\"\" width=\"286\" height=\"102\" \/><\/p>\n<p>It can also be observed using the <strong>Nmap<\/strong> script <code>broadcast-dhcp-discover<\/code>.<\/p>\n<pre>nmap --script broadcast-dhcp-discover<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4983\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-21_11-11-18.png\" alt=\"\" width=\"510\" height=\"303\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-21_11-11-18.png 510w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-21_11-11-18-300x178.png 300w\" sizes=\"auto, (max-width: 510px) 100vw, 510px\" \/><\/p>\n<hr \/>\n<p><strong>TFTP SERVER<\/strong><\/p>\n<p>By default, the TFTP server root directory is <code>\/srv\/tftp\/<\/code>. Netboot images need to be placed there.<\/p>\n<p><strong>Debian 12 Netboot<\/strong><\/p>\n<pre>mkdir -p \/srv\/tftp\/debian\r\ncd \/srv\/tftp\/debian\r\nwget http:\/\/deb.debian.org\/debian\/dists\/bookworm\/main\/installer-amd64\/current\/images\/netboot\/debian-installer\/amd64\/linux\r\nwget http:\/\/deb.debian.org\/debian\/dists\/bookworm\/main\/installer-amd64\/current\/images\/netboot\/debian-installer\/amd64\/initrd.gz<\/pre>\n<p><strong>PXE Boot with TFTP-HPA<\/strong><\/p>\n<pre>cd \/srv\/tftp\/\r\ncp \/usr\/lib\/PXELINUX\/pxelinux.0\r\ncp \/usr\/lib\/syslinux\/modules\/bios\/menu.c32\r\ncp \/usr\/lib\/syslinux\/modules\/bios\/ldlinux.c32\r\ncp \/usr\/lib\/syslinux\/modules\/bios\/libutil.c32\r\n<\/pre>\n<pre>mkdir -p \/srv\/tftp\/pxelinux.cfg\r\nnano \/srv\/tftp\/pxelinux.cfg\/default<\/pre>\n<pre>DEFAULT menu.c32\r\nPROMPT 0\r\nMENU TITLE PXE Boot Menu\r\nTIMEOUT 50\r\n\r\nLABEL debian\r\n    MENU LABEL Install Debian 12\r\n    KERNEL debian\/linux\r\n    APPEND initrd=debian\/initrd.gz\r\n<\/pre>\n<p>Apply the configuration.<\/p>\n<pre>systemctl restart tftpd-hpa\r\nsystemctl enable tftpd-hpa<\/pre>\n<p>Use the Nmap script <code>tftp-enum.nse<\/code> to test the TFTP service.<\/p>\n<pre>nmap -sU -p 69 --script tftp-enum.nse <strong>10.1.1.1<\/strong><\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-4972\" src=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-20_20-57-53.png\" alt=\"\" width=\"515\" height=\"191\" srcset=\"https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-20_20-57-53.png 515w, https:\/\/dft.wiki\/wp-content\/uploads\/sites\/15\/2025\/06\/Screenshot_2025-06-20_20-57-53-300x111.png 300w\" sizes=\"auto, (max-width: 515px) 100vw, 515px\" \/><\/p>\n<p><strong>PXE Boot with Dnsmasq (alternative to TFTP-HPA)<\/strong><\/p>\n<p><strong>Dnsmasq<\/strong> provides <strong>DNS<\/strong>, <strong>DHCP<\/strong>, and <strong>TFTP<\/strong> in a single daemon.<\/p>\n<p>Installation and configuration.<\/p>\n<pre>apt remove --purge tftpd-hpa -y\r\napt install dnsmasq -y\r\nnano \/etc\/dnsmasq.d\/pxe.conf<\/pre>\n<pre>#port=0                                              # Disable DNS\r\nenable-tftp                                          # Enable TFTP\r\ntftp-root=\/srv\/tftp\r\ndhcp-boot=pxelinux.0\r\npxe-service=x86PC, \"Boot from network\", pxelinux\r\nlog-queries                                          # Logging\r\nlog-facility=\/var\/log\/dnsmasq.log\r\ninterface=eth1                                       # Bind only to vmbr1 interface\r\nexcept-interface=lo\r\nbind-interfaces\r\ndhcp-range=10.1.1.100,10.1.1.200,12h                 # DHCP pool\r\ndhcp-option=3,10.1.1.1                               # Default gateway\r\ndhcp-option=6,10.1.1.1                               # DNS server<\/pre>\n<pre>systemctl restart dnsmasq<\/pre>\n<hr \/>\n<p><strong>INTERNET ACCESS<\/strong><\/p>\n<p>Since a netinstall requires Internet access to fetch packages, the PXE boot server needs to perform NAT.<\/p>\n<pre>echo \"net.ipv4.ip_forward=1\" &gt;&gt; \/etc\/sysctl.d\/99-ipforward.conf\r\nsysctl -p \/etc\/sysctl.d\/99-ipforward.conf\r\nnano \/etc\/nftables.conf<\/pre>\n<pre>#!\/usr\/sbin\/nft -f\r\ntable ip nat {\r\n    chain prerouting {\r\n        type nat hook prerouting priority 0;\r\n    }\r\n    chain postrouting {\r\n        type nat hook postrouting priority 100;\r\n        oifname \"eth0\" masquerade\r\n    }\r\n}\r\n<\/pre>\n<pre>systemctl restart nftables\r\nnft list ruleset<\/pre>\n<hr \/>\n<p><strong>SERVING ISO FILES ON LAN<\/strong><\/p>\n<p>It is also possible to boot from an ISO over the network via HTTP or NFS.<\/p>\n<p><strong>HTTP<\/strong><\/p>\n<p>Install Nginx and get iPXE.<\/p>\n<pre>apt install nginx -y\r\nmkdir -p \/srv\/tftp\/ipxe\r\ncd \/srv\/tftp\/ipxe\r\nwget https:\/\/boot.ipxe.org\/ipxe.lkrn\r\nmkdir -p \/var\/www\/html\/ipxe\r\ncd \/var\/www\/html\/ipxe\r\ncp \/usr\/lib\/syslinux\/memdisk .\r\ncp \/usr\/lib\/syslinux\/modules\/bios\/sanboot.c32 .\r\nnano boot.ipxe<\/pre>\n<pre>#!ipxe\r\n\r\nmenu iPXE Boot Menu\r\nitem ubuntu Ubuntu ISO \r\nitem shell iPXE Shell\r\nchoose distro &amp;&amp; goto ${distro}\r\n\r\n:ubuntu\r\nsanboot http:\/\/releases.ubuntu.com\/noble\/ubuntu-24.04.2-live-server-amd64.iso\r\n#kernel http:\/\/10.1.1.1\/isos\/linux ip=dhcp url=<strong>http:\/\/releases.ubuntu.com\/noble\/ubuntu-24.04.2-live-server-amd64.iso<\/strong>\r\n#initrd http:\/\/10.1.1.1\/isos\/initrd.gz\r\n#boot\r\n\r\n:shell\r\nshell<\/pre>\n<p><strong>Note:<\/strong> The <code>memdisk<\/code> lines are commented out due to its memory limit of around 700 MB. Use <code>sanboot<\/code> instead, which handles larger ISO files up to 2 GB.<\/p>\n<p>Get the ISOs.<\/p>\n<pre>mkdir -p \/var\/www\/html\/isos\r\ncd \/var\/www\/html\/isos\r\nwget https:\/\/releases.ubuntu.com\/24.04\/netboot\/amd64\/initrd -O initrd.gz\r\nwget https:\/\/releases.ubuntu.com\/24.04\/netboot\/amd64\/linux<\/pre>\n<p>Create the chain: PXELINUX + iPXE.<\/p>\n<pre>nano \/srv\/tftp\/pxelinux.cfg\/default<\/pre>\n<p>Append the following block.<\/p>\n<pre>LABEL iPXE Boot\r\n    MENU LABEL Boot using iPXE\r\n    KERNEL ipxe\/ipxe.lkrn\r\n    APPEND dhcp &amp;&amp; chain http:\/\/<strong>10.1.1.1<\/strong>\/ipxe\/boot.ipxe<\/pre>\n<hr \/>\n<p><strong>PXE BOOT THE EASY WAY<\/strong><\/p>\n<p><strong>NetBoot.xyz<\/strong> [<a href=\"https:\/\/netboot.xyz\/\">Link<\/a>]<\/p>\n<p>Set DHCP option 67 to <code>netboot.xyz.kpxe<\/code>.<\/p>\n<p>A single-line Docker deployment makes this even simpler.<\/p>\n<pre>docker run -d --name=netbootxyz -p 3000:3000 -p 8080:80 -p 69:69\/udp -v \/srv\/config:\/config -v \/srv\/assets:\/assets --restart unless-stopped ghcr.io\/netbootxyz\/netbootxyz<\/pre>\n<p>Ports exposed:<\/p>\n<ul>\n<li><code>3000<\/code> Dashboard<\/li>\n<li><code>8080<\/code> Assets (ISOs)<\/li>\n<li><code>69\/udp<\/code> TFTP (NBP and configs)<\/li>\n<\/ul>\n<p>Navigate to the dashboard at <strong>http:\/\/<span style=\"text-decoration: underline;\">10.1.1.1<\/span>:3000\/<\/strong>.<\/p>\n<p><strong>LinuxServer.io<\/strong> [<a href=\"https:\/\/www.linuxserver.io\/\">Link<\/a>]<\/p>\n<p>(pending)<\/p>\n<p><strong>iVentoy<\/strong> [<a href=\"https:\/\/www.iventoy.com\/\">Link<\/a>]<\/p>\n<p>(pending)<\/p>\n<hr \/>\n<p><strong>UBUNTU AUTO INSTALL<\/strong><\/p>\n<p>In the iPXE configuration, set the following.<\/p>\n<pre>kernel <strong>http:\/\/<span style=\"text-decoration: underline;\">10.1.1.1<\/span>\/vmlinuz<\/strong> root=\/dev\/ram0 ramdisk_size=3500000 cloud-config-url=\/dev\/null autoinstall ds=nocloud;s=<strong>http:\/\/<span style=\"text-decoration: underline;\">10.1.1.1<\/span><\/strong> ip=dhcp url=<strong>http:\/\/<span style=\"text-decoration: underline;\">10.1.1.1<\/span>\/ubuntu.iso<\/strong> initrd=initrd.magic\r\ninitrd <strong>http:\/\/<span style=\"text-decoration: underline;\">10.1.1.1<\/span>\/initrd<\/strong><\/pre>\n<p>Then create the necessary files.<\/p>\n<pre>touch meta-data\r\ntouch vendor-data\r\nnano user-data<\/pre>\n<p>Add the following content, adjusting the parameters as needed.<\/p>\n<pre>#cloud-config\r\nautoinstall:\r\n  version: 1\r\n  refresh-installer:\r\n    update: true\r\n    channel: latest\/stable\r\n  apt:\r\n    disable_components: []\r\n    fallback: abort\r\n  mirror-selection:\r\n    geoip: true\r\n  packages:\r\n<strong>    - htop\r\n    - curl\r\n    - nano\r\n    - iputils-ping\r\n    - qemu-guest-agent<\/strong>\r\n  drivers:\r\n    install: true\r\n  refresh-installer:\r\n    channel: stable\r\n    update: yes\r\n  storage:\r\n    layout:\r\n      name: direct\r\n  user-data:\r\n    disable_root: false\r\n  identity:\r\n    hostname: <strong>ubuntu-server<\/strong>\r\n    realname: <strong>ubuntu<\/strong>\r\n    username: <strong>ubuntu<\/strong>\r\n    password: \"<strong>$y$...password...hash<\/strong>\"\r\n  kernel:\r\n    package: linux-generic\r\n  keyboard:\r\n    layout: us\r\n    toggle: null\r\n    variant: ''\r\n  locale: en_US.UTF-8\r\n  network:\r\n    version: 2\r\n    renderer: networkd\r\n    ethernets:\r\n        alleths:\r\n            match:\r\n                name: e*\r\n            dhcp4: true\r\n  oem:\r\n    install: auto\r\n  source:\r\n    id: ubuntu-server-minimal\r\n    search_drivers: true\r\n  ssh:\r\n    install-server: true\r\n    allow-pw: true\r\n    authorized-keys:\r\n      - \"<strong>id_rsa AAAAA<\/strong>\"\r\n  updates: security\r\n  timezone: <strong>America\/Toronto<\/strong>\r\n  late-commands:\r\n    - curtin in-target -- <strong>apt update<\/strong>\r\n    - curtin in-target -- <strong>apt upgrade -y<\/strong><\/pre>\n<hr \/>\n<p><strong>BONUS<\/strong><\/p>\n<p>As an alternative to the well-known <strong>Ventoy<\/strong> [<a href=\"https:\/\/github.com\/ventoy\/Ventoy\">Link<\/a>], check out <strong>Aegis-Boot<\/strong> [<a href=\"https:\/\/github.com\/williamzujkowski\/aegis-boot\/\">Link<\/a>], a signed UEFI Secure Boot rescue environment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Technologies like PXE (Preboot Execution Environment) and its open-source implementation iPXE are essential for managing [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-4941","post","type-post","status-publish","format-standard","hentry","category-ccna"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/4941","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=4941"}],"version-history":[{"count":29,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/4941\/revisions"}],"predecessor-version":[{"id":5601,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/4941\/revisions\/5601"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=4941"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=4941"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=4941"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}