Using PXE to deploy a DNS server

Frederique Retsema

Last week, I published a blog about implementing a PXE server [1]. Today I’ll show how kickstart/anaconda files can be used to deploy a server. I will use the example of a DNS-server to explain what can be changed where in kickstart files and what you can do when the kickstart file isn’t enough for your purposes. The full kickstart file and an example menu for the deployment can be found both at the end of this blog and on my github repository [2].

1. Kickstart files

The best place to change settings is in the kickstart file itself: many checks will be done before the installation starts. The VM that we deployed in the previous blog was configured based on DHCP, it had the following line in it:

network –bootproto=dhcp –device=eth0 –ipv6=auto –activate

For a DNS, we should use a static DNS address, so we will change this into:

network –bootproto=static –ip= –gateway= –nameserver= –netmask= –ipv6=auto –device=eth0 –activate

The network directive is also the place to configure the name of the server:

network –hostname=dns

There is also room for specifying additional packages to the (in this case: minimal) base install. kexec-tools was already in the kickstart file, it is needed for the install itself. I added bind (for the DNS-server) and bind-utils (for utilities like dig, nslookup etc) myself:


2. The post-install part of kickstart

There is a part in the kickstart file that by default isn’t there. I’m talking about the post install part, you can use this part to add or change files after the installation, but before the reboot. This part starts with %post and ends with %end.

For the DNS-server, there should be changes in the configuration file /etc/named.conf. After the installation of the DNS-server, the DNS-server will by default respond only to connections from its own server (localhost). To be useful for other servers in the network this should be changed to the network the DNS server is running on. Two lines must be changed: the line where we listen on port 53 must be changed from to the network address of the DNS-server, in my case (“listen-on port 53 {;};”), and the line where we allow queries from our network must be changed from localhost to (every node in our network: “allow-query {;};”).

The configuration file should also contain our own (local) domain, where we can add some DNS-entries. After the line

zone "." IN {
     type "hints";

the following text should be added:

zone "" IN {
     type master;
     file "";
    allow-update { none; } ;

The “allow-update { none;};” will take care that the domain cannot be changed by other nodes, the only way to change the content of this zone is to change the configuration file

After that, the file will be created in the /var/named directory, with the following content:

$TTL 86400

@ IN SOA (
dns      IN A
server1 IN A
server2 IN A

Now, the only thing left to do is to change the firewall. When we would configure a DNS server by hand, we would simply use the following commands on the command line:

firewall-cmd –add-port=53/tcp
firewall-cmd –add-port=53/tcp –permanent
firewall-cmd –add-port=53/udp
firewall-cmd –add-port=53/udp –permanent

The problem is, however, that when we would use this in the kickstart file, the changes would be made on the installation environment, not on the destination host. So we have to come up with a trick to do these changes directly after the reboot of the server.

3. Start up script

We can do that by creating a start up script that will only run once, after the first start of the server. The script should run after the firewall is started. We will create a “oneshot service” in systemd, which removes itself after execution. Anaconda should create a file /etc/systemd/system/dnsconf.service with the following content:

Description=Configure selinux for named

ExecStart=/bin/firewall-cmd --add-port=53/tcp 
ExecStart=/bin/firewall-cmd --add-port=53/tcp --permanent
ExecStart=/bin/firewall-cmd --add-port=53/udp 
ExecStart=/bin/firewall-cmd --add-port=53/udp --permanent
ExecStart=rm -f /etc/systemd/system/dnsconf.service
ExecStart=rm -f /etc/systemd/system/


To let this work, there should be a softlink in /etc/systemd/system/ Normally, you wouldn’t create this softlink yourself, it is placed there by the command systemctl enable dnsconf. But because systemd is not running on the target system when you create these files, Anaconda will have to put the softlink there for you. The creation of the file /etc/systemd/system/dnsconf.service and the creation of the softlink is done in the %post part of the Anaconda file. The same is true for the DNS service itself:

ln -s /etc/systemd/system/dnsconf.service /etc/systemd/system/
ln -s /etc/systemd/system/named.service /etc/systemd/system/

4. Conclusion

The nice thing about this solution is that you can deploy an OS and configure it over the network. Even without logging on to the server, this solution will work as soon as you see the login prompt: you can use dig @ from another server and you will see the address as a result.  

For a small organization with few changes in their DNS configuration, it might be considered not to allow users on this new machine at all (not even an administrator) for security reasons: in that case, a userdel command can be added in the %post installation part of Anaconda to delete the (in this example) frederique user. When there should be changes in the DNS environment, the changes are made on the kickstart file on the PXE server and the DNS server is redeployed with PXE after that.



[2], in the dns directory

Content of the file /etc/lib/tftpboot/pxelinux.cfg/C0A802:

DEFAULT menu.c32
LABEL CentOS 8 dns
MENU centos8dns
KERNEL /networkboot/vmlinuz
APPEND initrd=/networkboot/ inst.repo= ks=

Content of /var/ftp/centos8dns.cfg:

ignoredisk --only-use=sda
autopart --type=lvm
# Partition clearing information
clearpart --all
# Use graphical install
repo --name=centos-updates --mirrorlist=$releasever&arch=$basearch&repo=BaseOS --cost=1000
repo --name=appstream-updates --mirrorlist=$releasever&arch=$basearch&repo=AppStream --cost=1000
repo --name=extras-updates --mirrorlist=$releasever&arch=$basearch&repo=Extras --cost=1000
# Use FTP installation media
url --url=
# Keyboard layouts
keyboard --vckeymap=us --xlayouts='us'
# System language
lang en_US.UTF-8

# Network information
network  --bootproto=static --ip= --gateway= --nameserver= --netmask= --ipv6=auto --device=eth0 --activate
#Root password
rootpw --lock
# Run the Setup Agent on first boot
firstboot --enable
# Do not configure the X Window System
# System services
services --enabled="chronyd"
# System timezone
timezone Europe/Amsterdam --isUtc
user --groups=wheel --name=frederique --password=$6$ISxpNV3kE7gfvlpi$RRWVusy/EiatEuCEIdYhR.R1PdT6KBt2xuaPGaKdZRx93RB5bua4QEEh4aQNmTIpVLcz.4YXaVvk8brKlwslf/ --iscrypted --gecos="Frederique"



%addon com_redhat_kdump --enable --reserve-mb='auto'


    sed -i 's/' /etc/named.conf
    sed -i '/allow-query/c \ \ \ \ \ \ \ \ allow-query {;};' /etc/named.conf
    ZONELINENO=`grep -rne "zone \".\" IN {" /etc/named.conf | awk -F":" '{print $1}'`

    echo "" > /tmp/
    echo "zone \"\" IN {" >> /tmp/
    echo "        type master;" >> /tmp/
    echo "        file \"\";" >> /tmp/
    echo "        allow-update{ none; };" >> /tmp/
    echo "};" >> /tmp/

    sed -i "${ZONELINENO} r/tmp/" /etc/named.conf

    echo "\$TTL 86400" > /var/named/
    echo "\$ORIGIN" >> /var/named/
    echo "" >> /var/named/
    echo "@ IN SOA (" >> /var/named/
    echo "    2020021200" >> /var/named/
    echo "    3600" >> /var/named/
    echo "    1800" >> /var/named/
    echo "    604800" >> /var/named/
    echo "    86400)" >> /var/named/
    echo "@ IN NS" >> /var/named/
    echo "dns     IN A" >> /var/named/
    echo "server1 IN A" >> /var/named/
    echo "server2 IN A" >> /var/named/

    echo "[Unit]" > /etc/systemd/system/dnsconf.service
    echo "Description=Configure selinux for named" >> /etc/systemd/system/dnsconf.service
    echo "After=firewalld.service" >> /etc/systemd/system/dnsconf.service
    echo "" >> /etc/systemd/system/dnsconf.service
    echo "[Service]" >> /etc/systemd/system/dnsconf.service
    echo "Type=oneshot" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=/bin/firewall-cmd --add-port=53/tcp" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=/bin/firewall-cmd --add-port=53/tcp --permanent" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=/bin/firewall-cmd --add-port=53/udp" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=/bin/firewall-cmd --add-port=53/udp --permanent" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=rm -f /etc/systemd/system/dnsconf.service" >> /etc/systemd/system/dnsconf.service
    echo "ExecStart=rm -f /etc/systemd/system/" >> /etc/systemd/system/dnsconf.service
    echo "" >> /etc/systemd/system/dnsconf.service
    echo "[Install]" >> /etc/systemd/system/dnsconf.service
    echo "" >> /etc/systemd/system/dnsconf.service

    ln -s /etc/systemd/system/dnsconf.service /etc/systemd/system/
    ln -s /etc/systemd/system/named.service /etc/systemd/system/

pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges --notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges --emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Next Post

Secure browsing using a local SOCKS proxy server (on desktop or mobile) and an always free OCI compute instance as SSH server

Facebook 0 Twitter Linkedin Oracle provides several services as ‘always free’. In contrast to Azure and Amazon, these include compute instances which remain ‘forever’ free to use. Although there are some limitations on CPU, disk, network resources, these instances are ideal to use as a remote SSH server and with […]