| Tested on | RHEL 10.2 (Coughlan) — vm1.lab.example (kickstart server), vm2.lab.example (PXE install target) |
|---|---|
| Package | pykickstart 3.52.12-1.el10 |
| Applies to | RHEL, Rocky Linux, AlmaLinux, Oracle Linux, CentOS Stream, Fedora |
| Privilege | root on the kickstart server; installer boot access on the target host |
| Scope | Kickstart structure, validation with ksvalidator, anaconda-ks.cfg and template generation, command examples by topic, inst.ks boot parameters, lab harness kickstart file, rhsm registration, %pre/%post timing overview, verification, and kickstart-side troubleshooting. PXE server build and netboot logs are in the PXE boot server guide. |
| Related guides | Configure Kickstart PXE boot server Kickstart %pre script examples Kickstart %post script examples Kickstart clearpart not working Register RHEL with subscription-manager |
Kickstart is how you automate Red Hat’s Anaconda installer: a plain-text answer file lists language, network, disk layout, users, packages, and optional % scripts, and the installer applies those answers without manual clicks. This guide walks through that flow on RHEL 10—file structure, command examples, validation with ksvalidator, generating templates from anaconda-ks.cfg, and a lab harness kickstart exercised through PXE on vm2.
You need a valid Red Hat subscription for CDN installs: an organization ID and activation key from the Activation Keys page. The article uses placeholders (ORG_ID, ACTIVATION_KEY_NAME)—set real values only on vm1, never in git or public URLs. To netboot vm2, wire the PXE server first in Configure Kickstart PXE boot server. Deep %pre and %post catalogs live in dedicated articles linked below.
Lab overview
| Host | Role | Lab address | What it does |
|---|---|---|---|
| vm1 | Kickstart server | 192.168.56.116 |
Publishes kickstart files under /var/www/html/ks/, PXE stack, local inst.stage2 |
| vm2 | Install target | 192.168.56.156 |
Netboots, loads kickstart from vm1, becomes a fresh RHEL 10 system |
Two kickstart files share the same PXE server:
| File | URL | Use |
|---|---|---|
rhel10-pxe.cfg |
http://192.168.56.116/ks/rhel10-pxe.cfg |
PXE boot server guide — minimal @^minimal-environment install and netboot screenshots |
rhel10-lab.cfg |
http://192.168.56.116/ks/rhel10-lab.cfg |
This guide — full harness (GUI, bond, ignoredisk, %pre/%post) for validation and satellite articles |
Flow for the full harness: vm2 PXE netboots (see PXE guide) with inst.ks=…/rhel10-lab.cfg → Anaconda applies the harness → rhsm registers and pulls packages from the CDN → vm2 reboots with hostname vm2.lab.example and user admin.
clearpart on the install disk wipes vm2. Replace ORG_ID and ACTIVATION_KEY_NAME on vm1 only. For rhel10-lab.cfg (@^graphical-server-environment, bond, extra scripts), assign 6 GiB+ RAM to vm2. The minimal rhel10-pxe.cfg in the PXE guide needs 4 GiB+.
What is a Kickstart installation?
| Piece | Role |
|---|---|
| Kickstart file | Automated answers for locale, network, disk, users, packages, and scripts |
inst.ks= |
Boot parameter that points Anaconda at the kickstart URL or path |
repo / url / rhsm |
Where RPMs install from (mirrors or CDN) |
inst.stage2= on kernel line |
Where installer runtime (install.img) loads during network boot |
| Fully automated | No prompts when every required item is valid and reachable |
| Semi-automated | Kickstart leaves gaps; Anaconda stops at the text menu |
Understand kickstart file structure
A kickstart file combines top-level commands (one per line) and script sections introduced with % headers. Each section ends with %end.
| Section or block | Runs when | Typical use |
|---|---|---|
#version=RHEL10 |
Parse time | Selects RHEL 10 grammar for ksvalidator and Anaconda |
| Top-level commands | Throughout install planning | Locale, repos, disk layout, users, firewall, reboot |
%packages |
Package selection | Environment groups (@^…), RPM names, exclusions (-pkg) |
%pre |
After kickstart load, before partitioning | Disk detection, wipe scripts, logging |
%pre-install |
After /mnt/sysimage is mounted, before packages |
Users/groups with fixed IDs, layout tweaks |
%post |
After packages install (chrooted by default) | Configuration, markers, services |
%post --nochroot |
After packages, in installer environment | Copy installer logs to target |
%addon |
Addon modules | com_redhat_kdump, OpenSCAP profiles |
Deep %pre / %post libraries: Kickstart %pre script examples and Kickstart %post script examples.
Kickstart command examples by topic
Reference shapes from Red Hat documentation and lab testing—not every variant is netbooted in the lab. Validate with ksvalidator before production.
Locale, keyboard, and legal
#version=RHEL10
lang en_US.UTF-8
keyboard us
timezone Asia/Kolkata --utc
eula --agreedInstallation source
For RHEL 10 CDN installs, register during install with rhsm. Do not add conflicting url, repo, or inst.repo= when you rely on CDN-only delivery.
rhsm --organization=ORG_ID --activation-key=ACTIVATION_KEY_NAMECreate the activation key on the Activation Keys page. Restrict kickstart file permissions on the server (chmod 640) because it contains registration credentials.
On the PXE kernel line, set inst.stage2= to a local HTTP tree with install.img (see PXE boot server guide). Omit inst.repo= so rhsm controls package sources.
rhsm and point repo lines at public mirrors. Use @^minimal-environment or the group your tree publishes. Stage2 and boot media must match that distribution, not RHEL DVD paths.
Network
Static install NIC by MAC (lab harness—avoids wrong DHCP lease):
network --bootproto=static --device=08:00:27:7b:9e:bd --ip=192.168.56.156 --netmask=255.255.255.0 --gateway=192.168.56.116 --nameserver=192.168.56.116,8.8.8.8 --activate
network --bootproto=dhcp --device=eth1 --no-activate
network --hostname=vm2.lab.exampleBond and VLAN with --nodefroute (lab harness—extra NICs do not steal default route):
network --device=bond0 --bootproto=static --ip=192.168.56.220 --netmask=255.255.255.0 --bondslaves=eth2,eth3 --bondopts=mode=active-backup,miimon=100 --nodefroute --activate --vlanid=56 --interfacename=bond0.56
network --device=bond0.56 --bootproto=static --ip=192.168.56.221 --netmask=255.255.255.0 --nodefroute --activateStorage
Scope disks on multi-disk hosts with ignoredisk and clearpart --drives=. See Kickstart clearpart not working when old LVM blocks autopart.
Lab harness (sda install, sdb ignored):
ignoredisk --drives=sdb
zerombr
clearpart --all --initlabel --drives=sda
autopart --type=lvm --nohomeUsers and authentication
authselect select sssd with-fingerprint --force
rootpw --lock
user --name=admin --groups=wheel --password=CHANGEME --plaintext
sshkey --username=admin "ssh-ed25519 AAAA...comment"Packages
%packages --ignoremissing
@^graphical-server-environment
openssh-server
chrony
-vim-enhanced
%end--ignoremissing avoids interactive prompts when an optional package (for example htop) is absent from entitled repos. Confirm groups on a reference host:
dnf group list --hidden | grep -iE 'minimal|graphical|server'Bootloader, firewall, and services
bootloader --location=mbr --append="crashkernel=auto"
firewall --enabled --ssh
selinux --enforcing
services --enabled=chronyd,sshd
firstboot --disable
rebootScripts and addons
%pre --log=/tmp/kickstart_pre.log
#!/bin/bash
lsblk >> /tmp/kickstart_pre.log
%end
%post --erroronfail --log=/var/log/kickstart_post.log
echo "kickstart-post-ok" > /root/ks-lab-marker.txt
%end
%addon com_redhat_kdump --enable --reserve-mb=auto
%endGenerate kickstart templates
You do not have to write every line by hand. Start from a template Anaconda or a GUI tool produced, then edit hardware-specific lines and run ksvalidator.
anaconda-ks.cfg after a successful install
After any successful interactive or kickstart install, Anaconda writes a template on the new system:
/root/anaconda-ks.cfgThat file reflects the machine it was generated on—disk names, interface names, package set. Edit away hardware-specific lines, validate, and reuse on similar hosts.
On vm2 after the lab run:
sudo ls -l /root/anaconda-ks.cfgCompare that file to your hand-written /var/www/html/ks/rhel10-lab.cfg on vm1 to see how Anaconda recorded the install.
Scratch files like /root/anaconda-tb-* on a build host come from Image Builder, Cockpit, or incomplete installs—they are not the standard post-install template name.
Legacy kickstart example generator (RHEL/CentOS 8)
The old rhel-centos-8-kickstart-example-generator article is consolidated here. Prefer these sources instead of copying RHEL 7/8 syntax:
| Source | When to use |
|---|---|
#version=RHEL10 + official command reference |
New files for RHEL 10 |
/root/anaconda-ks.cfg from a reference host |
Cloning similar hardware |
| Cockpit or Image Builder exports | Image-mode or custom builds—review for retired commands |
system-config-kickstart |
Legacy GUI on older releases—not available on minimal RHEL 10 server images |
Workflow after generating a template:
- Set
#version=RHEL10at the top if missing. - Remove or update retired commands (
auth, oldtimezoneflags, etc.). - Replace disk and
networklines with values for the target hardware. - Run
ksvalidatoron the edited file. - Test on a disposable VM before production.
Validate with ksvalidator
Syntax checking catches misspelled commands before you netboot:
ksvalidator /var/www/html/ks/rhel10-lab.cfgChecking kickstart file /var/www/html/ks/rhel10-lab.cfgNo stderr means the parser accepted the file. ksvalidator cannot prove disks exist, URLs respond, or package groups are published.
Lab harness kickstart file
vm1 publishes the full harness at http://192.168.56.116/ks/rhel10-lab.cfg. Point inst.ks= at that URL when you test GUI environment, multi-disk ignoredisk, bond/VLAN networking, %pre/%post logging, and CDN install via rhsm. Replace placeholders before netboot.
The minimal kickstart used for PXE walkthrough screenshots (@^minimal-environment, no script sections) is rhel10-pxe.cfg in Configure Kickstart PXE boot server.
#version=RHEL10
# GoLinuxCloud lab harness — full PXE test (RHEL 10.2 vm2)
# PXE: bootif (IPAPPEND 2) + net.ifnames=0 -> eth0=lab, eth1=NAT, eth2+eth3=bond slaves
# Disk: sda 20GB LVM autopart; sdb 10GB ignored
# GUI: @^graphical-server-environment — vm2 RAM 6 GiB+ recommended
lang en_US.UTF-8
eula --agreed
keyboard us
timezone Asia/Kolkata --utc
timesource --ntp-server 0.rhel.pool.ntp.org
network --bootproto=static --device=08:00:27:7b:9e:bd --ip=192.168.56.156 --netmask=255.255.255.0 --gateway=192.168.56.116 --nameserver=192.168.56.116,8.8.8.8 --activate
network --bootproto=dhcp --device=eth1 --no-activate
network --device=bond0 --bootproto=static --ip=192.168.56.220 --netmask=255.255.255.0 --bondslaves=eth2,eth3 --bondopts=mode=active-backup,miimon=100 --nodefroute --activate --vlanid=56 --interfacename=bond0.56
network --device=bond0.56 --bootproto=static --ip=192.168.56.221 --netmask=255.255.255.0 --nodefroute --activate
network --hostname=vm2.lab.example
authselect select sssd with-fingerprint --force
rootpw CHANGEME_ROOT --plaintext
user --name=admin --groups=wheel --password=CHANGEME --plaintext
sshkey --username=admin "ssh-ed25519 AAAA...admin-key-comment"
sshkey --username=admin "ssh-ed25519 AAAA...second-key-comment"
sshkey --username=root "ssh-ed25519 AAAA...admin-key-comment"
sshkey --username=root "ssh-ed25519 AAAA...second-key-comment"
ignoredisk --drives=sdb
zerombr
clearpart --all --initlabel --drives=sda
autopart --type=lvm --nohome
bootloader --location=mbr --append="crashkernel=auto"
firewall --enabled --ssh
selinux --enforcing
services --enabled=chronyd,sshd
firstboot --disable
rhsm --organization=ORG_ID --activation-key=ACTIVATION_KEY_NAME
reboot
%packages --ignoremissing
@^graphical-server-environment
htop
vim-enhanced
tree
bind-utils
tmux
bash-completion
%end
%addon com_redhat_kdump --enable --reserve-mb=auto
%end
%pre --log=/tmp/kickstart_pre.log
#!/bin/bash
set -x
RUN_TAG="ks-lab-$(date +%Y%m%d-%H%M%S)"
echo "RUN_TAG=${RUN_TAG}"
echo "=== %pre $(date -Is) ==="
echo "--- lsblk ---"
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
for n in /sys/class/net/*; do
name=$(basename "$n")
[[ "$name" == "lo" ]] && continue
mac=$(cat "$n/address")
drv=$(basename "$(readlink -f "$n/device/driver" 2>/dev/null)" 2>/dev/null || echo "?")
echo "NIC ${name} mac=${mac} driver=${drv}"
done
ip -br link
ip -br addr
echo "BOOTIF=${BOOTIF:-unset}"
df -Th
free -m
touch /tmp/pre-no-log-marker
echo "${RUN_TAG}" > /tmp/ks-lab-run-tag
INSTALL_DISK=sda
udevadm settle
if [[ -b /dev/${INSTALL_DISK} ]]; then
vgchange -an 2>/dev/null || true
dmsetup remove_all 2>/dev/null || true
wipefs -af "/dev/${INSTALL_DISK}" || true
for part in /dev/${INSTALL_DISK}*; do
[[ -b "$part" ]] && wipefs -af "$part" || true
done
lsblk "/dev/${INSTALL_DISK}"
fi
%end
%pre-install --log=/tmp/kickstart_pre_install.log
#!/bin/bash
echo "=== %pre-install $(date -Is) ==="
df -Th
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
mount | grep sysimage || true
%end
%post --erroronfail --log=/var/log/kickstart_post.log
#!/bin/bash
echo "=== %post chroot $(date -Is) ==="
df -Th
hostname -f
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
ip -br addr
nmcli -t -f DEVICE,TYPE,STATE,CONNECTION device status 2>/dev/null || true
echo "kickstart-post-ok" > /root/ks-lab-marker.txt
echo "graphical-server-environment" > /root/ks-lab-environment.txt
echo "bond+bond0.56 vlan" > /root/ks-lab-network.txt
systemctl enable chronyd
systemctl enable gdm
systemctl set-default graphical.target
cat > /etc/ssh/sshd_config.d/01-lab-ssh.conf << 'SSHEOF'
# Lab harness: rootpw plus sshkey for password and key login (not for production)
PermitRootLogin yes
PasswordAuthentication yes
PubkeyAuthentication yes
SSHEOF
cat > /root/ks-lab-verify.sh << 'VERIFY'
#!/bin/bash
echo "=== ks-lab verification $(date -Is) ==="
ls -la /var/log/kickstart* /var/log/pre-no-log-marker 2>/dev/null
lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT
ip -br addr
nmcli con show 2>/dev/null | grep -E "bond|vlan|NAME" || true
ip -d link show bond0 2>/dev/null || true
cat /root/ks-lab-marker.txt /root/ks-lab-environment.txt /root/ks-lab-network.txt 2>/dev/null
cat /etc/redhat-release
VERIFY
chmod +x /root/ks-lab-verify.sh
%end
%post --nochroot --log=/mnt/sysimage/var/log/kickstart_post_nochroot.log
#!/bin/bash
echo "=== %post --nochroot $(date -Is) ==="
INSTALLER_LOGS="/tmp/anaconda.log /tmp/storage.log /tmp/packaging.log /tmp/program.log /tmp/syslog /tmp/ks-lab-run-tag"
for f in $INSTALLER_LOGS /tmp/kickstart_pre.log /tmp/kickstart_pre_install.log /tmp/pre-no-log-marker /tmp/pre-without-log-marker; do
[[ -f "$f" ]] && cp -av "$f" /mnt/sysimage/var/log/
done
[[ -d /tmp/anaconda ]] && cp -av /tmp/anaconda /mnt/sysimage/var/log/anaconda-installer-tmp 2>/dev/null || true
df -Th
ip -br link
%end
%post --interpreter=/usr/bin/bash --log=/var/log/kickstart_bash_post.log
echo "bash interpreter %post on $(hostname -f) at $(date -Is)"
%end
%post --interpreter=/usr/libexec/platform-python --log=/var/log/kickstart_python_post.log
print("python interpreter %post at", __import__("datetime").datetime.now().isoformat())
%end
%pre
#!/bin/bash
touch /tmp/pre-without-log-marker
%end
%post --log=/var/log/kickstart_post_no_erroronfail.log
#!/bin/bash
echo "post section without --erroronfail $(date -Is)"
if [[ -f /var/log/pre-no-log-marker ]]; then echo "pre-no-log-marker copied"; fi
if [[ -f /var/log/pre-without-log-marker ]]; then echo "pre-without-log-marker copied"; fi
%endFull interpreter walkthroughs and extra log-copy patterns: Kickstart %pre script examples and Kickstart %post script examples.
PXE kernel parameters for the full harness (set inst.ks= to rhel10-lab.cfg in PXE boot files; the PXE article default is rhel10-pxe.cfg):
inst.ks=http://192.168.56.116/ks/rhel10-lab.cfg inst.text ip=dhcp net.ifnames=0 inst.stage2=http://192.168.56.116/rhel10/BaseOS/x86_64/os/| Parameter | Role |
|---|---|
inst.ks= |
Kickstart answer file URL |
inst.stage2= |
Installer runtime (install.img) tree |
inst.text |
Text-mode Anaconda during PXE install only. Does not set the post-install boot target. Use @^graphical-server-environment plus systemctl set-default graphical.target in %post for a GUI login after reboot. |
ip=dhcp |
Early boot DHCP on PXE NIC before kickstart network lines apply |
net.ifnames=0 |
Predictable eth0 naming for bond slaves in the harness |
Verify the installed system
After a PXE kickstart run, confirm the install on the target. Netboot and install progress screenshots are in Configure Kickstart PXE boot server.
SSH as the kickstart user:
ssh admin@vm2.lab.exampleOn the new system, confirm the release:
cat /etc/redhat-releaseRed Hat Enterprise Linux release 10.2 (Coughlan)Confirm %post left the lab marker:
test -f /root/ks-lab-marker.txt && cat /root/ks-lab-marker.txtkickstart-post-okRun the harness verification script when present:
sudo /root/ks-lab-verify.sh=== ks-lab verification 2026-08-09T21:16:35+05:30 ===
├─rhel_vm200-root 16G lvm xfs /
└─rhel_vm200-swap 2G lvm swap [SWAP]
sdb 10G disk
└─sdb1 10G part LVM2_member
eth0 UP 192.168.56.156/24
bond0.56@bond0 UP 192.168.56.220/24
--- markers ---
kickstart-post-ok
graphical-server-environment
bond+bond0.56 vlan
--- release ---
Red Hat Enterprise Linux release 10.2 (Coughlan)Confirm the GUI default target after first boot:
systemctl get-defaultgraphical.targetConfirm RHSM registration and the GUI environment group:
sudo subscription-manager status
sudo dnf grouplist --installedOverall Status: Registered
Installed Environment Groups:
Server with GUIConfirm the system boots to the graphical target (not only multi-user):
systemctl get-default
rpm -q gdmgraphical.target
gdm-47.0-22.el10_2.x86_64rootpw in kickstart sets the root password, but RHEL still blocks root SSH by default. The lab harness adds PermitRootLogin yes in %post. Test with ssh root@vm2.lab.example after install, or log in as admin and use su -.
Compare /root/anaconda-ks.cfg on vm2 with your hand-written file on vm1.
Installer-side logs for failed runs:
/tmp/anaconda.log
/tmp/storage.log
/tmp/packaging.log
/tmp/pre.log
/tmp/post.logOn the installed system, copies land under /var/log/ when %post --nochroot copied them.
Review important RHEL 10 kickstart notes
- Confirm removed commands against the RHEL 10 kickstart documentation—do not copy
author oldtimezonesyntax from retired guides. - Use
rhsmfor registration instead of hand-written%postwhen activation keys fit your process. inst.textkeeps automated installs predictable on serial or VGA consoles while%packagesstill installs a GUI environment group.- Image-mode (
bootc) kickstart options apply only when you intentionally build image-mode systems.
Troubleshoot kickstart installs
Kickstart file and Anaconda
| Symptom | Likely cause | Fix |
|---|---|---|
inst.ks not found |
HTTP URL wrong or firewall blocks target | curl -sI the URL from the lab LAN; fix Apache path and firewalld |
| ksvalidator passes but install prompts | Missing command or unavailable package | Fill gaps; use %packages --ignoremissing or remove missing packages |
| Wrong disk wiped | ignoredisk missing or clearpart too broad |
Scope clearpart --drives=; see clearpart troubleshooting |
| Not enough free space | Old LVM on install disk | Aggressive clearpart plus %pre wipefs on the target disk |
| Repository / CDN error | Bad rhsm credentials or inst.repo= overrides CDN |
Fix org ID and activation key; remove conflicting url/repo/inst.repo= |
rhsm Errno -2 (name not known) |
DNS blocked on PXE server | Add dns to firewalld; configure dnsmasq forwarders—see PXE guide |
rhsm Errno 101 (network unreachable) |
Bond or static NIC stole default route | --nodefroute on extra interfaces; explicit gateway on install NIC |
%pre failed |
Script exit non-zero | Inspect /tmp/pre.log or /tmp/kickstart_pre.log |
%post never ran |
Script error before reboot | Check /tmp/post.log; use --erroronfail while testing |
PXE and network boot
PXE-specific symptoms (wrong NIC, TFTP failures, Exec format error, stage2 URLs) are covered in Configure Kickstart PXE boot server.
References
- Red Hat Enterprise Linux 10 — Automatically installing RHEL
- Red Hat Enterprise Linux 10 — Kickstart commands and options reference
- Red Hat Enterprise Linux 10 — Creating Kickstart files
- ksvalidator(1) — Linux manual page
Summary
Kickstart automates Anaconda: publish an answer file, point the installer at it with inst.ks=, and on RHEL use rhsm with a valid activation key so packages install from the CDN. Start templates from anaconda-ks.cfg, validate with ksvalidator, and wire PXE delivery through Configure Kickstart PXE boot server.
The lab harness on vm1/vm2 validates GUI environment install, multi-disk ignoredisk, bond networking, and %pre/%post logging. Archive /root/anaconda-ks.cfg from vm2 after a successful run as the baseline for the next iteration.

