Build a DNS and DHCP Server on Debian

Deploy Bind9 DNS and Kea DHCP on a headless Debian 13 VM for a private lab network.

Introduction

30 Second Summary

Every time you connect to Wi-Fi, your device silently asks for an IP address and looks up website names behind the scenes. These invisible services are the backbone of every network, from your home router to enterprise data centers.

In this project, you will build a two-VM network lab where a Debian server provides DNS name resolution and automatic IP address assignment via DHCP to a Linux Mint client.

What You'll Build

A self-contained virtual network lab with a headless Debian 13 server VM acting as the authoritative DNS and DHCP server for a private network, tested end-to-end from a separate Linux Mint client VM.

By the end of this project, you'll have:

  • A Debian 13 server VM configured with Bind9 DNS serving forward and reverse zones for the lab.local domain.
  • A Kea DHCP4 server automatically assigning IP addresses and pushing DNS and gateway settings to clients.
  • A hardened server with nftables firewall rules and SSH key-based authentication blocking all unnecessary traffic.
  • Secret Mission: Deploy an Nginx web server with a custom DNS record so your Mint client can browse to a custom domain and see a page served by your full stack.

Are there any prerequisites?

You should be comfortable with basic Linux command-line tasks (cd, ls, sudo, editing files with nano).

You also need VMware Workstation Pro installed on your Windows host, and an existing Linux Mint VM that you can repurpose as the network client.

Set Up the Lab Environment

Every corporate network depends on a few core services running behind the scenes. DNS translates friendly hostnames into IP addresses. DHCP automatically hands out network settings to every device that connects.

Before you can build those services, you need a lab. In this step, you will create an isolated virtual network inside VMware Workstation Pro, install a headless Debian 13 server, and connect your existing Linux Mint VM to the same network.

In this step, get ready to:
  • Configure a host-only network in VMware with VMware's built-in DHCP disabled.
  • Install a Debian 13 server VM with no desktop environment.
  • Connect your Linux Mint VM to the same isolated network.
Configure a host-only network in VMware

A host-only network creates a private virtual switch that connects your VMs to each other without touching the internet. By disabling VMware's built-in DHCP on this network, you guarantee that your Debian server will be the only device handing out IP addresses later.

  • In VMware Workstation Pro, click Edit in the top menu bar.
  • Click Virtual Network Editor.
  • If prompted for administrator access, click Yes to allow changes.
  • Select VMnet1 in the list (this is the default host-only network).
  • In the Subnet IP text box, enter 192.168.100.0.
  • Set the Subnet mask to 255.255.255.0.
  • Uncheck Use local DHCP service to distribute IP addresses to VMs.
  • Click OK to save.

Why disable VMware's DHCP?

You are about to build your own DHCP server in a later step. If VMware's built-in DHCP is also running on the same network, clients would receive conflicting leases from two sources. Disabling it now ensures a clean, unambiguous test environment.

Don't see VMnet1 in the list?

Click Add Network at the bottom of the Virtual Network Editor, select VMnet1, and set its type to Host-only. Then configure the subnet as described above.

Help me set up a host-only network in VMware Virtual Network Editor

Install Debian 13 server VM

The netinst (network install) ISO is a small image that downloads packages from the internet during installation. You will temporarily use a NAT adapter to complete the install, then switch to the host-only network and assign a static IP.

Download the Debian 13 netinst ISO
  • Open your browser on the Windows host and navigate to https://www.debian.org/download.
  • Download the file named debian-13.5.0-amd64-netinst.iso.
  • Save it somewhere you can find it easily, such as your Downloads folder.
Create the VM in VMware
  • In VMware Workstation Pro, click File then New Virtual Machine.
  • Choose Typical and click Next.
  • Select Installer disc image file (iso) and browse to the debian-13.5.0-amd64-netinst.iso you downloaded.
  • Click Next through the prompts. Set the VM name to something recognizable like Debian-Server.
  • Set the disk size to 10 GB and choose Store virtual disk as a single file.
  • Click Customize Hardware.
  • Set Memory to 1024 MB (1 GB).
  • Set Processors to 1.
  • For the Network Adapter, select NAT for now. You will switch this to the host-only network after installation completes.
  • Click Close, then Finish.

Why NAT during installation?

The netinst ISO needs internet access to download packages like the SSH server. NAT gives the VM temporary internet through your host. After the install finishes, you will switch the adapter to the isolated host-only network.

Walk through the Debian installer
  • Power on the VM. It boots from the ISO automatically.
  • Select Install (not Graphical Install) to use the text-based installer.
  • Choose your language, location, and keyboard layout.
  • The installer will detect the network and attempt DHCP. With NAT, this should succeed automatically.
  • When prompted for a hostname, enter debian-server.
  • For domain name, enter lab.local.
  • Set a root password and create a regular user account (remember these credentials).
  • Accept the default partitioning (guided, use entire disk).
  • When the installer reaches the Software selection screen (tasksel), uncheck everything except:
  • SSH server (checked).
  • standard system utilities (checked).
  • Make sure all desktop environment options are unchecked.
  • Install the GRUB bootloader to the primary disk when prompted.
  • Complete the installation and let the VM reboot.

No desktop means less RAM

By skipping all desktop environments, the server only needs about 512 MB of RAM at runtime. This leaves more memory for your Linux Mint VM and your Windows host.

Switch to host-only and assign a static IP

Now that the install is complete, you will disconnect the VM from NAT and connect it to the isolated lab network.

  • Shut down the Debian VM by logging in and running:
sudo shutdown -h now
  • In VMware, right-click the Debian-Server VM and click Settings.
  • Select Network Adapter.
  • Change the connection from NAT to Custom: Specific virtual network and choose VMnet1 (Host-only).
  • Click OK.
  • Power the VM back on and log in with your user credentials.

Now configure the static IP. The network interface in VMware is typically named ens33. Confirm your interface name first.

  • Identify the network interface name by running:
ip a

Look for the interface that is not lo (loopback). It is most likely ens33.

  • Open the network interfaces configuration file by running:
sudo nano /etc/network/interfaces
  • Find the lines that configure your network interface. They will look something like:
allow-hotplug ens33
iface ens33 inet dhcp
  • Replace those lines with the following static configuration:
# The primary network interface
auto ens33
iface ens33 inet static
    address 192.168.100.10
    netmask 255.255.255.0

What does this configuration do?

The auto ens33 line tells the system to bring this interface up at boot. The iface ens33 inet static line declares a static IPv4 configuration instead of DHCP.

The address 192.168.100.10 with netmask 255.255.255.0 places this server at a fixed position on the 192.168.100.0/24 subnet. No gateway is needed because this is an isolated lab network.

  • Save the file by pressing Ctrl+O, then Enter, then exit with Ctrl+X.
  • Restart the networking service to apply the changes by running:
sudo systemctl restart networking
  • Verify the static IP is active by running:
ip addr show ens33

You should see 192.168.100.10/24 listed next to inet in the output.

Not seeing the IP address?

Double-check that you replaced dhcp with static in /etc/network/interfaces. Make sure the interface name matches exactly what ip a showed you (it might be ens33, ens160, or enp0s3 depending on your VMware version).

Help me configure a static IP on my Debian server VM

Connect the Linux Mint VM to the same network

Your Linux Mint VM is currently connected to a different network adapter. You need to move it onto the same host-only network so it can communicate with the Debian server.

  • If your Linux Mint VM is running, shut it down gracefully.
  • In VMware, right-click the Linux Mint VM and click Settings.
  • Select Network Adapter.
  • Change the connection to Custom: Specific virtual network and choose VMnet1 (Host-only).
  • Click OK.
  • Power on the Linux Mint VM.
  • Open a terminal in Linux Mint and check its IP address by running:
ip addr show

The Mint client should show no IPv4 address on its network interface. This is expected because there is no DHCP server on the network yet. Your Debian server will provide one in a later step.

Why does Mint have no IP?

You disabled VMware's DHCP service earlier, and your Kea DHCP server on Debian is not running yet. Linux Mint is trying to get an IP via DHCP but nobody is answering. Once you configure Kea in a later step, Mint will automatically receive an address.

Mint still shows an old IP address?

If Mint kept an IP from a previous session, release it by running sudo dhclient -r in the terminal. Then run ip addr show again to confirm the interface has no IPv4 address.

Help me make sure my Mint VM has no IP on the host-only network

Your lab environment is ready. Both VMs are on the same isolated network, and your Debian server has a fixed address. Next up, you will turn that server into a DNS authority for the lab.local domain.

Configure the DNS Server (Bind9)

Your lab environment is ready with both VMs connected to the same isolated network. Now you need to give that network a brain.

Every time you type a domain name, a DNS server translates it to an IP address behind the scenes. In this step, you will install and configure Bind9 on your Debian server to handle name resolution for your lab.local domain.

In this step, get ready to:
  • Install Bind9 and configure its global options and zone declarations.
  • Create forward and reverse zone files for the lab.local domain.
  • Start the DNS service and verify it resolves hostnames correctly.
Install and configure Bind9

Bind9 is the most widely used DNS server software on the internet. You will install it along with its diagnostic utilities, then configure where it forwards unknown queries and who is allowed to ask it questions.

  • In your Debian server terminal, install Bind9 and its DNS utilities by running this command:
sudo apt update && sudo apt install bind9 bind9-dnsutils -y
  • Wait for the installation to complete. You should see the packages download and install with no errors in the terminal output.

Seeing package errors?

Make sure your Debian server has an internet connection. If you configured a host-only adapter only, the VM cannot reach external package repositories. You may need to temporarily add a NAT adapter in VMware to download packages, then switch back to host-only.

Help me fix my package installation errors

  • Open the Bind9 options file by running:
sudo nano /etc/bind/named.conf.options
  • Select all existing content and delete it. Replace it with the following configuration:
options {
    directory "/var/cache/bind";

    // Forward unresolved queries to Google DNS
    forwarders {
        8.8.8.8;
    };

    // Only allow recursion from our lab network
    allow-recursion {
        192.168.100.0/24;
        localhost;
    };

    listen-on { any; };

    dnssec-validation auto;
};
  • Save the file by pressing Ctrl+O, then Enter, then Ctrl+X to exit nano.

What does this configuration do?

  • forwarders tells Bind9 to send any query it cannot answer locally to Google's public DNS (8.8.8.8).
  • allow-recursion restricts which clients can ask this server to resolve names on their behalf. Only machines on the 192.168.100.0/24 subnet and localhost are allowed.
  • listen-on { any; } makes the server accept DNS queries on all its network interfaces.
  • dnssec-validation auto enables cryptographic validation of DNS responses using built-in trust anchors.
  • Now open the local zones configuration file by running:
sudo nano /etc/bind/named.conf.local
  • Delete any existing content and replace it with the following zone declarations:
// Forward zone for lab.local domain
zone "lab.local" {
    type master;
    file "/etc/bind/zones/db.lab.local";
};

// Reverse zone for 192.168.100.x lookups
zone "100.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.100";
};
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).

What is a forward zone vs a reverse zone?

A forward zone translates hostnames to IP addresses. When a client asks "what is the IP of server.lab.local?", the forward zone answers with 192.168.100.10.

A reverse zone does the opposite. When a client asks "what hostname belongs to 192.168.100.10?", the reverse zone answers with server.lab.local. The reverse zone name (100.168.192.in-addr.arpa) is the network octets written backwards, which is how DNS reverse lookups work.

  • Verify your configuration syntax by running:
sudo named-checkconf
  • If the command returns with no output, your configuration syntax is correct. Any errors will appear as text in the terminal.

Seeing a syntax error?

Check that every line inside the options block ends with a semicolon. Missing semicolons are the most common Bind9 configuration mistake. Also ensure all curly braces are properly matched.

Help me fix my Bind9 syntax error

Create the zone files

Zone files are the actual databases that map hostnames to IP addresses. You will create one for forward lookups and one for reverse lookups.

  • Create the zones directory by running:
sudo mkdir -p /etc/bind/zones
  • Create the forward zone file by running:
sudo nano /etc/bind/zones/db.lab.local
  • Paste the following zone file content:
$TTL    604800
@       IN      SOA     server.lab.local. admin.lab.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name server record
@       IN      NS      server.lab.local.

; A records - map hostnames to IPs
server  IN      A       192.168.100.10
client  IN      A       192.168.100.50
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).

What do these records mean?

  • SOA (Start of Authority) identifies this server as the authoritative source for the lab.local zone. The serial number (2) is incremented each time you update the zone.
  • NS (Name Server) tells clients which server is responsible for this zone.
  • A records map a hostname to an IPv4 address. Here, server.lab.local resolves to 192.168.100.10 and client.lab.local resolves to 192.168.100.50.
  • Create the reverse zone file by running:
sudo nano /etc/bind/zones/db.192.168.100
  • Paste the following reverse zone content:
$TTL    604800
@       IN      SOA     server.lab.local. admin.lab.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name server record
@       IN      NS      server.lab.local.

; PTR records - map IPs to hostnames
10      IN      PTR     server.lab.local.
50      IN      PTR     client.lab.local.
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).

What are PTR records?

PTR (Pointer) records are the reverse of A records. They map an IP address back to a hostname. The numbers 10 and 50 represent the last octet of the IP address within the 192.168.100.0/24 network. So 10 maps to server.lab.local and 50 maps to client.lab.local.

  • Validate your forward zone file by running:
sudo named-checkzone lab.local /etc/bind/zones/db.lab.local
  • You should see output ending with OK and a loaded serial number.
  • Validate your reverse zone file by running:
sudo named-checkzone 100.168.192.in-addr.arpa /etc/bind/zones/db.192.168.100
  • You should again see output ending with OK.

Zone validation failed?

The most common issues are missing trailing dots on fully qualified domain names (e.g. server.lab.local. needs the final dot) and missing semicolons in the SOA record. Double-check that your SOA record's closing parenthesis is followed by a line containing the Negative Cache TTL value.

Help me fix my zone file error

Start and test DNS
  • Enable Bind9 to start on boot and start it now by running:
sudo systemctl enable --now bind9
  • Check that Bind9 is running by running:
sudo systemctl status bind9
  • You should see active (running) in green text in the output. Press q to exit the status view.

Bind9 failed to start?

Run sudo journalctl -u bind9 --no-pager -n 20 to see recent log entries. Common causes include typos in zone files or incorrect file paths in named.conf.local. Verify your zone file paths match exactly what you declared in the configuration.

Help me diagnose why Bind9 failed to start

  • Test forward DNS resolution by running:
dig @127.0.0.1 server.lab.local
  • In the ANSWER SECTION of the output, you should see server.lab.local resolving to 192.168.100.10.
  • Test reverse DNS resolution by running:
dig @127.0.0.1 -x 192.168.100.10
  • In the ANSWER SECTION, you should see a PTR record pointing 10.100.168.192.in-addr.arpa to server.lab.local.

Why test with @127.0.0.1?

The @127.0.0.1 flag tells dig to query the DNS server running on localhost. This confirms your Bind9 instance is responding correctly before you connect any clients to it.

✔️ Awesome, I've got everything!

Great. Make sure both the forward and reverse queries returned correct answers in the ANSWER SECTION.

ⓧ I'd like to double check the full code

Here are all four configuration files for comparison.

/etc/bind/named.conf.options:

options {
    directory "/var/cache/bind";

    // Forward unresolved queries to Google DNS
    forwarders {
        8.8.8.8;
    };

    // Only allow recursion from our lab network
    allow-recursion {
        192.168.100.0/24;
        localhost;
    };

    listen-on { any; };

    dnssec-validation auto;
};

/etc/bind/named.conf.local:

// Forward zone for lab.local domain
zone "lab.local" {
    type master;
    file "/etc/bind/zones/db.lab.local";
};

// Reverse zone for 192.168.100.x lookups
zone "100.168.192.in-addr.arpa" {
    type master;
    file "/etc/bind/zones/db.192.168.100";
};

/etc/bind/zones/db.lab.local:

$TTL    604800
@       IN      SOA     server.lab.local. admin.lab.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name server record
@       IN      NS      server.lab.local.

; A records - map hostnames to IPs
server  IN      A       192.168.100.10
client  IN      A       192.168.100.50

/etc/bind/zones/db.192.168.100:

$TTL    604800
@       IN      SOA     server.lab.local. admin.lab.local. (
                              2         ; Serial
                         604800         ; Refresh
                          86400         ; Retry
                        2419200         ; Expire
                         604800 )       ; Negative Cache TTL

; Name server record
@       IN      NS      server.lab.local.

; PTR records - map IPs to hostnames
10      IN      PTR     server.lab.local.
50      IN      PTR     client.lab.local.

Your DNS server is live and resolving names. Next up, you will configure a DHCP server so your Mint client automatically gets an IP address and knows to use your DNS server.

Configure the DHCP Server (Kea)

Your DNS server is live and answering queries. The next piece of the puzzle is getting your Mint client an IP address automatically.

Right now, your Mint VM sits on the network with no IP address because you disabled VMware's built-in DHCP in Step 1. You will deploy your own DHCP server using Kea, the modern replacement for the legacy ISC DHCP server. Kea uses a clean JSON configuration format and is actively maintained by ISC.

In this step, get ready to:
  • Install and configure Kea DHCP4 to hand out IP addresses on your lab network.
  • Validate the configuration and start the service.
  • Test that your Linux Mint client receives an IP address, DNS settings, and can resolve hostnames.
Install and configure Kea DHCP4

Before Kea can start handing out addresses, you need to know which network interface your Debian server uses. Interface names on modern Debian follow the predictable naming scheme, so it will likely be something like ens33 rather than the old eth0.

  • Identify your server's network interface name by running this command:
ip link show

What am I looking for?

You will see a list of interfaces. Look for the one with a MAC address (link/ether line) that is NOT lo (the loopback). On VMware Workstation, it is typically named ens33. Note this name down. You will need it for the configuration file.

  • Install the Kea DHCP4 server by running:
sudo apt install kea-dhcp4-server -y

Why Kea instead of isc-dhcp-server?

ISC ceased maintaining the legacy isc-dhcp-server in 2022. Kea is the actively maintained successor from the same organization (ISC). It uses a modular JSON-based configuration and is what the Debian Wiki recommends for new deployments.

Now you will write the Kea configuration file. This file tells Kea which interface to listen on, what pool of addresses to distribute, and what network options (gateway, DNS server, domain name) to push to clients.

  • Open the Kea DHCP4 configuration file by running:
sudo nano /etc/kea/kea-dhcp4.conf
  • Delete all existing content in the file, then paste the following configuration:
{
  "Dhcp4": {
    // Listen on the server's network interface
    "interfaces-config": {
      "interfaces": [ "[[INTERFACE_NAME=\"your interface name, e.g. ens33\"]]" ]
    },

    // Lease lifetime: 1 hour
    "valid-lifetime": 3600,
    "renew-timer": 1800,
    "rebind-timer": 2700,

    // Store leases in a local file
    "lease-database": {
      "type": "memfile",
      "persist": true,
      "name": "/var/lib/kea/kea-leases4.csv"
    },

    // Define our lab subnet
    "subnet4": [
      {
        "id": 1,
        "subnet": "192.168.100.0/24",
        "pools": [
          {
            "pool": "192.168.100.50 - 192.168.100.100"
          }
        ],
        // Push gateway, DNS, and domain to clients
        "option-data": [
          {
            "name": "routers",
            "data": "192.168.100.10"
          },
          {
            "name": "domain-name-servers",
            "data": "192.168.100.10"
          },
          {
            "name": "domain-name",
            "data": "lab.local"
          }
        ]
      }
    ]
  }
}

What does this configuration do?

The interfaces-config section tells Kea which network interface to listen on for DHCP requests.

The subnet4 section defines the 192.168.100.0/24 network. The pools array specifies addresses 192.168.100.50 through 192.168.100.100 for dynamic allocation.

The option-data array pushes three settings to every client: the default gateway (routers), the DNS server (domain-name-servers), and the search domain (domain-name). All three point back to your Debian server.

  • Save the file with Ctrl+O, press Enter, then exit with Ctrl+X.

Not sure which interface name to use?

Run ip addr show and look for the interface that has 192.168.100.10 assigned to it. That is the interface name you need in the config file.

Help me find my interface name

Validate and start Kea

Kea provides a built-in configuration checker. Running it before starting the service catches JSON typos and missing fields before they cause a startup failure.

  • Validate the configuration file by running:
sudo kea-dhcp4 -t /etc/kea/kea-dhcp4.conf

What should I see?

If the configuration is valid, the command prints several INFO lines and exits without errors. If you see a syntax error, check for missing commas or mismatched braces in the JSON file.

  • Enable and start the Kea DHCP4 service so it runs now and on every boot:
sudo systemctl enable --now kea-dhcp4-server
  • Verify the service is running by checking its status:
sudo systemctl status kea-dhcp4-server

You should see active (running) in green text. This confirms Kea is listening for DHCP requests.

Service failed to start?

Run journalctl -u kea-dhcp4-server --no-pager -n 20 to see the last 20 log lines. Common causes include a JSON syntax error in the config file, or the interface name not matching an actual interface on the system.

Help me troubleshoot Kea startup errors

Test from the Mint client

The real proof is whether your Linux Mint VM can get an address from your new DHCP server. You will release any stale configuration, request a fresh lease, and verify that all the network parameters came through.

  • Switch to your Linux Mint VM.
  • Open a terminal in Mint.
  • Release any existing DHCP lease and request a new one by running:
sudo dhclient -r && sudo dhclient
  • Verify the client received an IP in the correct range by running:
ip addr show

Look for an IP address in the 192.168.100.50-100 range assigned to your network interface.

  • Verify the DNS server was pushed correctly by running:
cat /etc/resolv.conf

You should see nameserver 192.168.100.10 in the output. This means your DHCP server successfully told the client to use your DNS server.

  • Test name resolution by querying your DNS server:
dig server.lab.local

The response should show 192.168.100.10 in the ANSWER section.

  • Confirm end-to-end connectivity by pinging the server by hostname:
ping -c 3 server.lab.local

You should see three successful replies from 192.168.100.10. This proves the full chain is working: DHCP assigned an IP, pushed DNS settings, and DNS resolved the hostname correctly.

Mint did not get an IP address?

Check that the Mint VM's network adapter is set to the same host-only network (VMnet1) as your Debian server. Also verify that VMware's built-in DHCP is still disabled in the Virtual Network Editor.

If dhclient hangs, it means no DHCP server responded. Confirm kea-dhcp4-server is running on your Debian server with sudo systemctl status kea-dhcp4-server.

Help me troubleshoot DHCP on my Mint client

Your DHCP server is live and distributing addresses. Next up, you will lock down the server with a firewall and harden SSH access to make this lab production-ready.

Harden the Server with Firewall and SSH

Your DNS and DHCP services are up and running, serving clients on the network. But right now, your server accepts traffic on every port and allows password-based SSH logins.

In a production environment, an unprotected server is a liability. In this step, you will lock down the Debian server with a firewall that only permits essential traffic, then switch SSH to key-based authentication so no one can brute-force a password.

In this step, get ready to:
  • Configure nftables firewall rules to allow only SSH, DNS, DHCP, and ICMP traffic.
  • Harden SSH with key-based authentication and disable password logins.
  • Verify the hardened setup from the Linux Mint client.
Configure nftables firewall

A firewall controls which network traffic can reach your server. nftables is the default firewall framework on Debian 13, replacing the older iptables.

  • Install nftables on your Debian server by running:
sudo apt install nftables -y

Why nftables instead of iptables?

nftables is the modern replacement for iptables and the default on Debian 13. It uses a cleaner syntax and a single tool for all protocol families.

iptables still works through a compatibility layer, but learning nftables directly gives you the skill that Debian and RHEL-family distros use going forward.

Now you need to write firewall rules that allow only the services your server provides (SSH, DNS, DHCP) and drop everything else.

  • Open the nftables configuration file by running:
sudo nano /etc/nftables.conf
  • Replace the entire contents of the file with the following ruleset:
#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Allow traffic from established and related connections
        ct state established,related accept

        # Allow loopback traffic
        iifname lo accept

        # Allow ICMP (ping)
        ip protocol icmp accept

        # Allow SSH (TCP port 22)
        tcp dport 22 accept

        # Allow DNS (TCP and UDP port 53)
        tcp dport 53 accept
        udp dport 53 accept

        # Allow DHCP (UDP ports 67 and 68)
        udp dport { 67, 68 } accept
    }

    chain forward {
        type filter hook forward priority 0; policy drop;
    }

    chain output {
        type filter hook output priority 0; policy accept;
    }
}

What does this configuration do?

  • flush ruleset clears any existing rules before applying the new ones.
  • policy drop; on the input chain means any traffic not explicitly allowed is silently discarded.
  • ct state established,related accept allows return traffic for connections your server initiated (like DNS forwarding queries to 8.8.8.8).
  • Each tcp dport or udp dport rule opens a specific port for the named service.
  • The output chain uses policy accept; so the server can freely send outbound traffic.
  • Save the file by pressing Ctrl+O then Enter, and exit nano with Ctrl+X.
  • Enable and start the nftables service by running:
sudo systemctl enable --now nftables
  • Verify your rules are active by running:
sudo nft list ruleset

You should see your full ruleset printed, including the input chain with your SSH, DNS, DHCP, and ICMP rules, and the default drop policy.

Lost SSH connection after applying rules?

If you get disconnected, your rules may have a typo that blocks port 22. Use the VMware console (not SSH) to log into the Debian VM directly.

Check for syntax errors by running sudo nft -f /etc/nftables.conf and reading the error message. Fix the file in nano, then reload.

help me fix my nftables rules so I can SSH back into my server

Harden SSH with key-based authentication

Password authentication is vulnerable to brute-force attacks. SSH keys use cryptographic key pairs instead, making unauthorized access virtually impossible.

You will generate a key pair on your Linux Mint client, copy the public key to the server, then disable password logins entirely.

  • Switch to your Linux Mint VM.
  • Generate an SSH key pair by running:
ssh-keygen -t ed25519

What happens when you run this?

The command generates an Ed25519 key pair (a modern, fast, and secure algorithm). It will ask where to save the key and whether to set a passphrase.

Press Enter to accept the default location (~/.ssh/id_ed25519). You can press Enter again twice to skip the passphrase for this lab environment.

  • Copy your public key to the Debian server by running:
ssh-copy-id [[DEBIANUSER="your Debian username"]]@192.168.100.10
  • Enter your Debian user's password when prompted. This is the last time you will need it.
  • Test that key-based login works by running:
ssh [[DEBIANUSER="your Debian username"]]@192.168.100.10

You should be logged into the Debian server without being asked for a password.

Still being asked for a password?

Make sure ssh-copy-id reported that one key was added. If it failed, check that the Debian server's SSH service is running with sudo systemctl status sshd.

Also verify the ~/.ssh/authorized_keys file exists on the server and contains your public key.

help me troubleshoot why SSH key authentication is not working

Now that key-based login works, disable password authentication to lock the door behind you.

  • On the Debian server (you should already be SSH'd in), open the SSH configuration file:
sudo nano /etc/ssh/sshd_config
  • Find the line that says #PasswordAuthentication yes (it may be commented out with a #).
  • Change it to:
PasswordAuthentication no
  • Find the line that says #PermitRootLogin (it may say prohibit-password or yes).
  • Change it to:
PermitRootLogin no
  • Save and exit nano (Ctrl+O, Enter, Ctrl+X).
  • Restart the SSH service to apply the changes:
sudo systemctl restart sshd

What do these settings do?

PasswordAuthentication no disables all password-based SSH logins. Only clients with an authorized key can connect.

PermitRootLogin no blocks the root user from logging in via SSH entirely. Admins must log in as a regular user and use sudo for elevated commands.

Verify the hardened setup

Time to confirm everything works together. Your firewall should allow SSH, DNS, and DHCP while blocking everything else.

  • Open a new terminal on your Linux Mint VM (or exit the current SSH session with exit).
  • Confirm SSH key-based login works by running:
ssh [[DEBIANUSER="your Debian username"]]@server.lab.local

You should be logged in without a password prompt. The hostname server.lab.local resolves through your DNS server.

  • Exit the SSH session and confirm DNS queries still work:
exit
dig @server.lab.local client.lab.local

You should see client.lab.local resolve to 192.168.100.50 in the ANSWER section.

  • Test that a blocked port is actually dropped by attempting a connection to port 80:
curl --connect-timeout 5 http://192.168.100.10

The command should hang and then time out after 5 seconds, confirming that port 80 is being dropped by the firewall.

  • Back on the Debian server, verify the full ruleset is loaded by running:
sudo nft list ruleset

You should see your input, forward, and output chains with all the rules you configured.

DNS queries not working from Mint?

If dig times out, SSH into the Debian server and confirm Bind9 is still running with sudo systemctl status bind9.

Also double-check that your nftables rules include both tcp dport 53 accept and udp dport 53 accept. DNS primarily uses UDP, so a missing UDP rule will break queries.

help me figure out why DNS queries are failing after I set up my firewall

Your server is now hardened. Only SSH, DNS, DHCP, and ping traffic can reach it. Next up, you will take on the Secret Mission and add a web server to prove the entire stack works end-to-end.

Secret mission

Deploy a Web Server with DNS

Your DNS and DHCP stack is solid. But can you prove the whole system works end-to-end? In this secret mission, you will install Nginx on your Debian server, create a custom web page, add a DNS record for www.lab.local, open port 80 in your firewall, and browse to http://www.lab.local from your Mint client.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on local virtual machines, so there are no ongoing cloud costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Debian 13 server VM (created in this project, running Bind9, Kea, nftables, Nginx, SSH).
  • Linux Mint VM network adapter (changed from its original setting to VMnet1 host-only).
  • VMnet1 host-only network configuration (subnet changed to 192.168.100.0/24, VMware DHCP disabled).
  • Debian 13 netinst ISO file (downloaded to your host machine).

Keep everything running

No action needed. Choose this if you are still actively experimenting with your lab or plan to add more services.

  • Both VMs remain powered on with their current configurations intact.
  • VMnet1 stays configured for your lab network.
  • You can SSH back into the Debian server from Mint at any time.

Pause - I'll come back to this later

Shut down both VMs to free up RAM on your host machine. Your configurations and files are preserved on disk.

  • In your Debian server VM, run:
  • In your Linux Mint VM, shut down through the desktop menu or run the same command.
  • When you are ready to resume, power on the Debian server first (so DHCP and DNS are available), then start Mint.
sudo shutdown -h now

All services (Bind9, Kea, nftables, Nginx, SSH) start automatically on boot thanks to systemd.

Delete - I don't want to use this again

Remove the Debian server VM and restore your environment to its pre-project state.

  • Power off the Debian server VM if it is still running.
  • In VMware Workstation Pro, select the Debian VM in the library.
  • Click VM in the menu bar, then Manage, then Delete from Disk.
  • Click Yes to confirm. This permanently removes the VM and all its files from your host.
  • Restore your Linux Mint VM network adapter. Open the Mint VM settings in VMware, change the network adapter back to NAT (or whichever mode it used before this project), then boot Mint.
  • Restore VMnet1 settings. Open Edit > Virtual Network Editor. Select VMnet1, check Use local DHCP service to distribute IP addresses to VMs, and click OK.
  • Delete the Debian 13 netinst ISO from your Downloads folder if you no longer need it.

Nice Work!

Nice Work!

You just built a fully functional network services lab from scratch. Your Debian server is now providing DNS name resolution and automatic DHCP address assignment to clients on an isolated virtual network.

You've learned how to:

  • Configure Bind9 with forward and reverse zone files to provide authoritative DNS for a custom domain.
  • Deploy Kea DHCP4 to automatically distribute IP addresses, gateway, and DNS settings to clients on your network.
  • Harden a server with nftables firewall rules and SSH key-based authentication to restrict access to only necessary traffic.
  • Secret Mission: Deployed an Nginx web server with a custom DNS record, proving end-to-end name resolution and service delivery from your Mint client.

Ready to quiz yourself?