Run a Web Server and Explore Networking

Launch a Python web server and investigate how the internet works.

Introduction

30 Second Summary

Every time you open a website, your computer is quietly doing dozens of things behind the scenes. It looks up addresses, picks routes, passes through security checkpoints, and exchanges messages with servers across the world.

In this project, you will launch a web server on your Mac and then use real networking tools to investigate every layer of how that communication works. You will trace packets, write firewall rules, and capture live network traffic.

What You'll Build

You will run a Python HTTP server on your Mac and use built-in Terminal tools to explore IP addresses, DNS resolution, routing tables, firewalls, and packet capture.

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

  • A running Python HTTP server serving a custom webpage from your Mac.
  • A network identity report documenting your Mac's IP addresses, interfaces, and routes.
  • Hands-on experience with firewall rules and a captured packet trace showing real HTTP traffic on your machine.
  • Secret Mission: build a custom Python server that responds to different URL paths, and write firewall rules that selectively allow only specific types of traffic.

Are there any prerequisites?

You just need a Mac with Terminal access. All of the tools in this project are built into macOS. Basic familiarity with opening Terminal and running commands is helpful.

Discover Your Mac's Network Identity

Every device on a network has an identity. Your Mac is no different. It has addresses, interfaces, and connections that define how it communicates with other devices and the wider internet.

In this step, you'll use built-in macOS tools to uncover your Mac's network identity. You'll find your private and public IP addresses, then save everything to a reference file you can use throughout the project.

In this step, get ready to:
  • Find your Mac's network interfaces and IP addresses.
  • Discover the difference between your private and public IP address.
  • Save your complete network identity to a reference file.
Find your network interfaces and IP addresses

Your Mac has multiple network interfaces. Each one is a connection point to a different type of network.

  • See all of your Mac's network interfaces by running this command:
ifconfig

You'll see a long list of interfaces with names like lo0, en0, and utun0. Each entry shows details about that interface, including any IP addresses assigned to it.

What are network interfaces?

A network interface is a connection point between your Mac and a network. lo0 is the loopback interface, which your Mac uses to send traffic to itself. en0 is typically your Wi-Fi adapter.

Each interface can show an inet line with its assigned IP address. Look for the one under en0 to find your Wi-Fi IP.

  • Get just your Wi-Fi IP address by running this command:
ipconfig getifaddr en0

You should see a single IP address like 192.168.x.x or 10.0.x.x printed to the screen. This is your private IP address on your local network.

Don't see an IP address?

Make sure your Mac is connected to Wi-Fi. The en0 interface only has an IP address when it's active and connected.

If you're using Ethernet instead of Wi-Fi, try running ipconfig getifaddr en1 instead.

Why isn't ipconfig getifaddr en0 returning my IP address on macOS?

Discover your public IP address

The IP address you just found only works inside your local network. The rest of the internet sees a completely different address for your Mac.

  • Find out what address the rest of the internet sees by running:
curl ifconfig.me

You should see a different IP address from the one you found with ipconfig getifaddr en0. This is your public IP address.

What is the difference between private and public IP addresses?

Your private IP address is what your router assigns to your Mac on your home network. Every device on the same Wi-Fi gets its own private IP.

Your public IP address is what the rest of the internet sees. All devices on your network share the same public IP. Your router translates between the two using a process called NAT (Network Address Translation).

Command hanging or no output?

Make sure you have an active internet connection. curl ifconfig.me reaches an external server to look up your public IP, so it needs internet access to work.

Why is curl ifconfig.me not returning my public IP address?

Save your network identity to a reference file

You've been discovering your network identity one command at a time. Now you'll create a script that gathers everything into a single reference file you can use throughout the project.

  • Navigate to your project folder by running this command:
cd ~/Desktop/network-lab
  • Create and open a new script file in nano by running:
nano network_identity.sh

First time using nano?

nano is a simple text editor that runs inside Terminal. You can paste text with Cmd+V. When you're done editing, press Ctrl+O then Enter to save, and Ctrl+X to exit.

  • Copy the following script and paste it into the nano editor:
#!/bin/bash
# Script to discover and save your Mac's network identity

echo "=== Network Identity Report ===" > ~/Desktop/network_identity.txt
echo "Generated: $(date)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Network Interfaces ---" >> ~/Desktop/network_identity.txt
ifconfig | grep -E "^[a-z]|inet " >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Wi-Fi IP Address ---" >> ~/Desktop/network_identity.txt
echo "Private IP: $(ipconfig getifaddr en0)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Public IP Address ---" >> ~/Desktop/network_identity.txt
echo "Public IP: $(curl -s ifconfig.me)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Default Gateway ---" >> ~/Desktop/network_identity.txt
netstat -rn | grep "^default" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Routing Table (IPv4) ---" >> ~/Desktop/network_identity.txt
netstat -rn -f inet >> ~/Desktop/network_identity.txt

echo ""
echo "Network identity saved to ~/Desktop/network_identity.txt"
cat ~/Desktop/network_identity.txt

What does this script do?

  • ifconfig | grep -E "^[a-z]|inet " filters your network interfaces to show only interface names and their IP addresses.
  • ipconfig getifaddr en0 grabs your Wi-Fi private IP address.
  • curl -s ifconfig.me fetches your public IP. The -s flag hides the download progress bar.
  • netstat -rn captures your default gateway and full routing table. You'll explore these in a later step.
  • Everything gets written to ~/Desktop/network_identity.txt so you can reference it anytime.
  • Press Ctrl+O, then Enter to save the file.
  • Press Ctrl+X to exit nano.
  • Make the script executable and run it by running these commands:
chmod +x network_identity.sh
./network_identity.sh

The script prints your full network identity report to the screen. You should also see the message Network identity saved to ~/Desktop/network_identity.txt near the top of the output.

  • Confirm the output file was created by running:
ls ~/Desktop/network_identity.txt

You should see /Users/your-username/Desktop/network_identity.txt in the output, confirming the file exists.

Script not running or no output file?

Check that you saved the file in nano before exiting. Press Ctrl+O, Enter, then Ctrl+X.

Make sure you're in the ~/Desktop/network-lab folder. Run pwd to check your current location.

If you see Permission denied, make sure you ran chmod +x network_identity.sh before executing the script.

Why is my network_identity.sh script failing to run on macOS?

✔️ Awesome, I've got everything!

Great. Double check you've saved your file and confirmed network_identity.txt exists on your Desktop.

ⓧ I'd like to double check the full code

Here is the complete network_identity.sh file. Compare it with what you have in your editor.

#!/bin/bash
# Script to discover and save your Mac's network identity

echo "=== Network Identity Report ===" > ~/Desktop/network_identity.txt
echo "Generated: $(date)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Network Interfaces ---" >> ~/Desktop/network_identity.txt
ifconfig | grep -E "^[a-z]|inet " >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Wi-Fi IP Address ---" >> ~/Desktop/network_identity.txt
echo "Private IP: $(ipconfig getifaddr en0)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Public IP Address ---" >> ~/Desktop/network_identity.txt
echo "Public IP: $(curl -s ifconfig.me)" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Default Gateway ---" >> ~/Desktop/network_identity.txt
netstat -rn | grep "^default" >> ~/Desktop/network_identity.txt
echo "" >> ~/Desktop/network_identity.txt

echo "--- Routing Table (IPv4) ---" >> ~/Desktop/network_identity.txt
netstat -rn -f inet >> ~/Desktop/network_identity.txt

echo ""
echo "Network identity saved to ~/Desktop/network_identity.txt"
cat ~/Desktop/network_identity.txt

Your Mac's network identity is documented and saved. Next up, you'll launch a web server on your Mac and make your first HTTP requests.

Launch a Web Server and Make HTTP Requests

In Step 1, you mapped out your Mac's network identity. You know your private IP, your public IP, and the interfaces that connect you to the network.

Now it's time to put that network to work. In this step, you'll launch a real web server on your Mac and make HTTP requests to it. This is the same protocol that powers every website you visit.

In this step, get ready to:
  • Create an HTML page and launch a Python web server.
  • Make HTTP requests with curl and examine response headers.
  • Open your server in a browser and observe the server logs.
Create your HTML page and start the server

Every website you visit works through the same pattern. A client (your browser) sends an HTTP request, and a server sends back a response.

Python has a built-in web server that lets you see this cycle in action on your own machine. First, you'll create an HTML page for it to serve.

  • Make sure you're in your project folder by running this command:
cd ~/Desktop/network-lab
  • Create your index.html file by pasting this entire command into your Terminal:
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Network Lab</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
            max-width: 600px;
            margin: 80px auto;
            padding: 20px;
            background: #f5f5f7;
            color: #1d1d1f;
        }
        h1 { color: #0071e3; }
        .info { background: white; padding: 20px; border-radius: 12px; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>My Network Lab</h1>
    <p>This page is being served by Python's built-in HTTP server.</p>
    <div class="info">
        <h2>What just happened?</h2>
        <ul>
            <li>Your browser sent an HTTP GET request to this server</li>
            <li>The server received it on port 8000</li>
            <li>The server sent back this HTML file as the response</li>
        </ul>
    </div>
</body>
</html>
EOF

What does this command do?

The cat > index.html << 'EOF' pattern is called a heredoc. It writes everything between the two EOF markers into a new file called index.html.

The HTML itself is a simple styled page with a heading, a description, and an info card that explains what happens during an HTTP request. You'll see all of this rendered in your browser shortly.

  • Confirm the file was created by running:
ls

You should see index.html alongside network_identity.sh in the output.

Don't see index.html?

Make sure you ran the entire command, including the final EOF line. If your Terminal is waiting for more input (showing > instead of $), type EOF and press Enter.

Also check that you're in the network-lab folder by running pwd. You should see /Users/yourname/Desktop/network-lab.

Help me troubleshoot why my index.html file wasn't created

✔️ Awesome, I've got everything!

Great. Double check you see index.html in your network-lab folder, then continue below.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My Network Lab</title>
    <style>
        body {
            font-family: -apple-system, BlinkMacSystemFont, sans-serif;
            max-width: 600px;
            margin: 80px auto;
            padding: 20px;
            background: #f5f5f7;
            color: #1d1d1f;
        }
        h1 { color: #0071e3; }
        .info { background: white; padding: 20px; border-radius: 12px; margin-top: 20px; }
    </style>
</head>
<body>
    <h1>My Network Lab</h1>
    <p>This page is being served by Python's built-in HTTP server.</p>
    <div class="info">
        <h2>What just happened?</h2>
        <ul>
            <li>Your browser sent an HTTP GET request to this server</li>
            <li>The server received it on port 8000</li>
            <li>The server sent back this HTML file as the response</li>
        </ul>
    </div>
</body>
</html>

Your HTML page exists as a file, but nobody can access it over the network yet. You need to start a web server that listens on a specific port for incoming requests.

  • Start a Python web server on port 8000 by running:
python3 -m http.server 8000

What does this command do?

python3 -m http.server 8000 starts Python's built-in HTTP server. It serves every file in your current directory to anyone who connects on port 8000.

A port is a numbered doorway on your computer where a specific program listens for traffic. Your Mac has 65,535 available ports. Port 8000 is a common choice for local development servers.

You should see output similar to Serving HTTP on :: port 8000 (http://[::]:8000/) ... in your Terminal.

Your Terminal is now busy running the server

The server runs in the foreground, which means this Terminal window is occupied. Do not close this window or press Ctrl+C yet. You'll open a second Terminal window in the next substep to run more commands.

Seeing 'Address already in use'?

Another program is already using port 8000. Try stopping it first by finding the process with lsof -i :8000, then try running the server again.

If that doesn't help, use a different port by running python3 -m http.server 8080 instead. Remember to use 8080 in place of 8000 for the rest of this project.

Help me troubleshoot why my Python HTTP server won't start

Make HTTP requests with curl

Your server is running and waiting for connections. curl is a command-line tool that sends HTTP requests, just like a browser does, but shows you the raw response directly in your Terminal.

  • Open a new Terminal window by pressing Cmd+N in Terminal.
  • Navigate to your project folder by running:
cd ~/Desktop/network-lab
  • Send an HTTP GET request to your server by running:
curl http://localhost:8000

You should see the full HTML source code of your index.html printed in the Terminal. This is exactly what the server sends back to any client that requests it.

What just happened?

curl acted as the client. It connected to localhost (your own machine) on port 8000, sent an HTTP GET request, and your Python server responded with the contents of index.html.

HTTP responses include more than just the page content. They also include headers with metadata about the response.

  • Request just the response headers by running:
curl -I http://localhost:8000

What are HTTP response headers?

Headers are metadata that the server sends before the actual content. They describe the response without being part of the page itself.

You should see headers like Content-type: text/html (the file type being sent), Content-Length (the file size in bytes), and Server: SimpleHTTP/0.6 Python/3.x (the software that served the response).

Getting 'Connection refused'?

Switch back to your first Terminal window and check that the server is still running. You should see the Serving HTTP on :: message.

If you accidentally closed it or pressed Ctrl+C, navigate back to the network-lab folder and run python3 -m http.server 8000 again.

Help me troubleshoot why curl can't connect to my local server

Open your server in a browser

curl showed you the raw HTTP response. A browser does the same thing, but renders the HTML into a visual page you can interact with.

  • Open any web browser (Safari, Chrome, or Firefox).
  • Type http://localhost:8000 in the address bar and press Enter.

You should see your styled My Network Lab page with a blue heading, a description, and a white info card listing three things that happened during the HTTP exchange.

  • Switch back to the Terminal window where your Python server is running.

You should see new log lines showing the requests your browser just made. Each line looks similar to 127.0.0.1 - - [date] "GET / HTTP/1.1" 200 -.

What do the server logs mean?

  • 127.0.0.1 is the IP address of the client that made the request. Since your browser is running on the same machine as the server, it shows the loopback address.
  • GET / is the HTTP method and the path the browser requested. / means the root of the site, which maps to index.html.
  • 200 is the HTTP status code meaning the request was successful.

You might see extra requests

Browsers often send additional requests for things like /favicon.ico (the small icon in the browser tab). If you see a 404 status code for that request, it just means the server couldn't find a favicon file. This is completely normal.

Your web server is up and running, handling real HTTP requests from both curl and your browser. Next up, you'll explore how domain names like google.com get translated into IP addresses using DNS.

Investigate DNS

In the last step, you launched a web server and watched HTTP requests flow between your browser and localhost. You saw exactly what those requests look like in Terminal and in your server logs.

Every time you type a URL like google.com into your browser, your computer needs to translate that name into an IP address before it can connect. This translation is called DNS resolution. In this step, you will query DNS servers directly to see this process in action.

In this step, get ready to:
  • Look up DNS records for a domain using dig.
  • Query different DNS servers with nslookup and compare their results.
  • Perform a reverse DNS lookup to map an IP address back to a domain name.
Look up DNS records with dig

DNS stands for Domain Name System. It works like a phone book for the internet, converting domain names like google.com into the numeric IP addresses that computers actually use to find each other.

The dig command lets you query DNS servers directly and see the full response. It is built into macOS, so nothing extra to install.

  • Query the DNS records for Google by running this command:
dig google.com

You should see a detailed response with multiple sections, including an ANSWER SECTION that contains one or more IP addresses.

What does this output mean?

The dig output has several sections. Here are the key ones:

  • QUESTION SECTION shows the domain you queried and the record type (A for IPv4 address).
  • ANSWER SECTION shows the actual IP addresses that google.com resolves to. This is what your browser uses to connect.
  • The bottom lines show which DNS server answered your query and how long it took (in milliseconds).
  • Get just the IP addresses without all the extra detail by running:
dig +short google.com

You should see one or more IP addresses printed on their own lines, like 142.250.xx.xx. Take note of one of these IPs. You will use it later in this step for a reverse lookup.

Not seeing any output?

  • Make sure you are connected to Wi-Fi. DNS queries require an internet connection.
  • If you see connection timed out, your DNS server might be unreachable. Try flushing your DNS cache by running:
sudo dscacheutil -flushcache; sudo killall -HUP mDNSResponder

Then try the dig command again.

Still stuck? Help me troubleshoot why dig is not returning results on my Mac.

Query different DNS servers with nslookup

By default, your Mac uses the DNS server assigned by your Wi-Fi router. But you can choose to ask any public DNS server instead.

Two of the most popular public DNS servers are Google's (8.8.8.8) and Cloudflare's (1.1.1.1). The nslookup command lets you query a specific DNS server by name.

  • Query Google's DNS server for google.com by running:
nslookup google.com 8.8.8.8

What does nslookup show?

  • Server and Address at the top show which DNS server answered your query.
  • Non-authoritative answer means the answer came from a cache, not directly from the domain owner's DNS server. This is normal for popular domains like Google.
  • The Name and Address lines below that show the domain and its resolved IP addresses.
  • Now query Cloudflare's DNS server for the same domain by running:
nslookup google.com 1.1.1.1

Compare the two results. You will likely see the same domain name resolve to the same or similar IP addresses, but the Server line at the top changes to show which DNS server answered.

Why would different DNS servers return different results?

Large companies like Google have servers all around the world. A DNS server in one region might point you to a different Google data center than a DNS server in another region.

This is why you might see slightly different IP addresses from Google's DNS vs Cloudflare's DNS. Both answers are correct. They just route you to different copies of the same service.

Perform a reverse DNS lookup

So far you have gone from a domain name to an IP address. That is a forward DNS lookup. Reverse DNS goes the other direction: starting with an IP address and finding what domain name it belongs to.

  • Run a reverse DNS lookup on Google's public DNS IP by running:
dig -x 8.8.8.8

In the ANSWER SECTION, you should see a PTR record that shows dns.google. This confirms that the IP 8.8.8.8 belongs to Google's DNS service.

What is a PTR record?

A PTR (Pointer) record is the reverse DNS equivalent of an A record. While an A record maps a domain name to an IP address, a PTR record maps an IP address back to a domain name.

Not every IP address has a PTR record. The owner of the IP range has to set one up. This is why some reverse lookups return results and others do not.

  • Now try a reverse lookup on one of the IP addresses you got from your earlier dig +short google.com results by running:
dig -x [[DISCOVERED_IP="one of the IP addresses from your dig +short google.com output"]]

You should see a PTR record in the ANSWER SECTION with a hostname that likely ends in .1e100.net. This is Google's internal domain for its data center servers.

No PTR record in the answer?

  • If the ANSWER SECTION is empty or missing, that IP address does not have a reverse DNS record configured. This is normal for some IPs.
  • Try running dig -x 8.8.8.8 instead. This IP always has a PTR record pointing to dns.google.

Still need help? Help me understand the output of my dig -x reverse DNS lookup.

You have just explored how the internet's naming system works at the command-line level. Next up, you will dig even deeper into your Mac's networking stack by reading your routing table and tracing the path your packets take across the internet.

Explore Routing

In the last step, you used dig and nslookup to explore how DNS translates domain names into IP addresses. Now you know how your Mac finds the address of a remote server.

But knowing the address is only half the story. Your Mac also needs to figure out which direction to send each packet to reach that address.

This is the job of your routing table. In this step, you will read it, query it, and then trace the actual path your packets take across the internet.

In this step, get ready to:
  • Read your Mac's routing table and identify the default gateway.
  • Look up how your Mac routes traffic to specific destinations.
  • Trace the full path your packets travel across the internet.
Read your Mac's routing table

Your Mac maintains a routing table that works like a set of directions. For every packet your Mac sends, the routing table tells it which network interface to use and which next-hop address to forward the packet to.

  • Display your Mac's routing table by running this command:
netstat -rn

You will see a large table with both IPv4 and IPv6 entries. The IPv4 section (labeled Internet:) is the one to focus on.

What am I looking at?

The routing table has several columns. Here are the key ones:

  • Destination is the address or network the rule applies to. The row labeled default is the most important one. It tells your Mac where to send any packet that does not match a more specific route.
  • Gateway is the next-hop address. For the default row, this is your router's IP address (your default gateway).
  • Flags describe the route. U means the route is up. G means traffic goes through a gateway. H means the destination is a single host, not a network.
  • Netif is the network interface used for the route. You will see en0 (your Wi-Fi adapter) and lo0 (the loopback interface for talking to yourself).
  • Find the default row in the IPv4 section. The IP address in the Gateway column is your router.
  • Look for rows with lo0 in the Netif column. These are local routes that stay on your Mac and never leave the machine.

You should see the default row with flags UGScg (or similar) pointing to your router's IP, along with several other routes for local networks and the loopback interface.

Seeing an empty table or an error?

  • Make sure you typed netstat -rn (lowercase r and n, no spaces between the dash and letters).
  • If you do not see a default row, check that your Mac is connected to Wi-Fi.

Still stuck? Ask for help.

Look up how your Mac routes specific destinations

The full routing table shows every rule your Mac knows about. But when you want to know how your Mac handles one specific destination, you can ask it directly.

  • Look up the route your Mac uses to reach Google's DNS server by running this command:
route -n get 8.8.8.8

What does this output tell me?

This command asks your Mac: "If I needed to send a packet to 8.8.8.8, what would I do?"

  • route to shows the destination your Mac matched.
  • gateway shows the next hop. This should be your router's IP (the same one from the default row in the routing table).
  • interface shows which network adapter sends the packet. This is typically en0 for Wi-Fi.
  • Now look up the route to a website hostname by running this command:
route -n get google.com

You should see the same gateway and interface as before. Both the IP address and the hostname resolve to the same route because they both leave your local network to reach the internet.

Why are both routes identical?

Your Mac does not have specific routes for every server on the internet. Instead, anything that is not on your local network gets sent to the default gateway. Your router then figures out the rest.

This is the same concept behind cloud VPC route tables. A default route sends all non-local traffic to an internet gateway.

Trace the full path packets take across the internet

So far you know that your Mac sends non-local packets to your router. But what happens after that? Your packet passes through many routers (called hops) on its way to the destination.

The traceroute command reveals every hop along the way, showing you the full path your packets travel across the internet.

  • Trace the route from your Mac to Google's servers by running this command:
traceroute google.com

What does the traceroute output mean?

Each numbered line is one hop. The first hop is your router (the default gateway you saw earlier). Subsequent hops are routers operated by your ISP and other networks.

The three time values on each line are round-trip measurements in milliseconds. They show how long it took a probe packet to reach that hop and come back.

If you see * * * on a line, that router chose not to respond. This is normal and does not mean the route is broken.

Be patient with traceroute

This command can take 30 seconds to a minute to finish, especially if some hops are unresponsive. Let it complete on its own.

You should see somewhere between 5 and 20 hops, starting with your local router and ending at a Google-owned server.

Traceroute shows nothing but * * * lines?

  • Some networks block traceroute traffic. Try a different destination like traceroute 8.8.8.8 (an IP address instead of a hostname).
  • If you are on a corporate or university network, the network administrator may have blocked outgoing traceroute packets entirely.

Still getting all asterisks? Ask for help.

Now let's measure the round-trip latency to the same destination using ping.

  • Send 5 ping packets to Google and measure latency by running this command:
ping -c 5 google.com

What does ping show?

Each line shows a round-trip time in milliseconds. The -c 5 flag limits ping to 5 packets. Without it, ping on macOS runs forever.

At the end, you get a summary with minimum, average, and maximum latency. Compare the average to the last hop in your traceroute. They should be similar.

You should see 5 replies with round-trip times, followed by a statistics summary showing 0.0% packet loss.

Seeing Request timeout or 100% packet loss?

  • Check that your Mac is connected to the internet. Open a browser and try loading any website.
  • If ping hangs and you forgot the -c 5 flag, press Ctrl+C to stop it.

Need help with ping issues? Ask here.

You have now explored every layer of how your Mac routes traffic. You can read the routing table, query specific routes, trace the full path across the internet, and measure latency. Next up, you will use firewall rules to control which traffic is allowed to reach your web server.

Control Traffic with Firewall Rules

You have traced the path your packets take across the internet and explored your Mac's routing table. Now you know exactly how traffic gets from point A to point B.

Knowing how traffic flows is only half the picture. The other half is controlling which traffic is allowed through. In this step, you will write firewall rules that block and allow specific network traffic to your web server.

In this step, get ready to:
  • Create a firewall rules file that blocks all incoming traffic to your server's port.
  • Load the rules and verify your server becomes unreachable, then add a rule to allow localhost traffic only.
  • Clean up by disabling the firewall and restoring your Mac to its default state.
Create a firewall rules file

A firewall controls which network traffic is allowed in or out of a machine. Your Mac has a built-in packet filter called pf that lets you write rules based on ports, IP addresses, and protocols.

Why pf instead of the macOS System Settings firewall?

The macOS System Settings firewall works at the application level. It can allow or block specific apps, but it cannot write rules for specific ports or IP addresses.

pf lets you write port-level and IP-level rules. This is the same concept behind cloud security groups in AWS or Azure. Learning pf now means you already understand what security groups do when you encounter them in the cloud.

  • Create the firewall rules file by running this command:
cat > ~/Desktop/network-lab/firewall_rules.conf << 'EOF'
# Custom pf rules for Network Lab
# This file blocks all incoming TCP traffic to port 8000,
# then allows connections from localhost only.

# Block all incoming TCP traffic to port 8000
block return in proto tcp from any to any port 8000
EOF

What does this rule do?

  • block return drops the packet and sends back a TCP reset. The client gets an immediate "connection refused" instead of waiting for a timeout.
  • in means the rule applies to incoming traffic only.
  • proto tcp specifies the TCP protocol. pf requires you to name the protocol when filtering by port.
  • from any to any port 8000 matches traffic from any source heading to port 8000.
  • Confirm the file was created by running:
cat ~/Desktop/network-lab/firewall_rules.conf

You should see the comments and the block return rule printed in your Terminal.

Seeing "No such file or directory"?

Make sure the path includes your network-lab folder. The full path should be ~/Desktop/network-lab/firewall_rules.conf.

Help me troubleshoot my firewall rules file creation.

Load your rules and test the firewall

Your web server needs to be running so you can test whether the firewall actually blocks traffic to it. You will use two Terminal tabs: one running the server and one for running firewall commands.

  • Start your Python web server by running these commands:
cd ~/Desktop/network-lab
python3 -m http.server 8000

You should see Serving HTTP on :: port 8000 in the output. This Terminal tab is now occupied by the running server.

  • Open a new Terminal tab by pressing **Cmd+T**.

You will be asked for your password

The next commands use sudo, which requires administrator privileges. When prompted, type your Mac login password and press **Enter**. The password characters will not appear on screen as you type.

  • In your new Terminal tab, load the firewall rules and enable the packet filter by running these commands:
sudo pfctl -f ~/Desktop/network-lab/firewall_rules.conf
sudo pfctl -e

What do these commands do?

  • pfctl -f loads your rules file into the packet filter.
  • pfctl -e enables the packet filter so it starts enforcing those rules.

You may see a message like No ALTQ support in kernel. This is normal on macOS and does not affect your firewall rules.

  • Verify the rules are loaded by running:
sudo pfctl -s rules

You should see your block return rule listed in the output.

  • Now test whether the firewall is working by running:
curl http://localhost:8000

The request should fail with a Connection refused error. Your firewall is blocking all incoming traffic to port 8000, including traffic from your own machine.

Curl still returning HTML?

Make sure you ran both pfctl -f (load rules) and pfctl -e (enable pf). Loading rules alone does not start enforcing them.

Run sudo pfctl -s rules to confirm your block rule is loaded. If no rules appear, re-run the load command and double check the file path.

Help me troubleshoot why my pf firewall is not blocking curl requests to localhost:8000.

Blocking everything is a good start, but a real firewall needs to be selective. You want to block outside traffic while still allowing your own machine to access the server.

  • Add a pass rule to your firewall file by running this command:
cat >> ~/Desktop/network-lab/firewall_rules.conf << 'EOF'

# Allow incoming TCP traffic to port 8000 from localhost
pass in inet proto tcp from 127.0.0.1 to any port 8000 no state
EOF

How does pf decide which rule wins?

pf processes rules from top to bottom, and the last matching rule wins. Your file now has two rules that both match traffic from localhost to port 8000.

The block rule matches first, but the pass rule matches second. Since pass comes last, localhost traffic is allowed through. Traffic from any other source only matches the block rule and gets rejected.

  • Reload the updated rules by running:
sudo pfctl -f ~/Desktop/network-lab/firewall_rules.conf
  • Test that localhost access now works by running:
curl http://localhost:8000

You should see the full HTML content of your index.html page printed in the Terminal. Your firewall now blocks all traffic to port 8000 except from localhost.

This is exactly how cloud security groups work

You just wrote a rule that says "block everything on this port, except from this specific source." Cloud security groups use the same logic. You define which ports to open and which IP addresses are allowed to connect.

Still seeing "Connection refused" after adding the pass rule?

Make sure you reloaded the rules with sudo pfctl -f ~/Desktop/network-lab/firewall_rules.conf after appending the pass rule. Editing the file alone does not update the active firewall.

Run sudo pfctl -s rules to verify both rules are loaded. You should see a block rule and a pass rule in the output.

Help me figure out why my pass rule is not allowing localhost traffic through pf.

✔️ Awesome, I've got everything!

Great. Double check you have saved your file before moving on.

ⓧ I'd like to double check the full code

Here is the complete firewall_rules.conf file. Make sure yours matches this exactly.

# Custom pf rules for Network Lab
# This file blocks all incoming TCP traffic to port 8000,
# then allows connections from localhost only.

# Block all incoming TCP traffic to port 8000
block return in proto tcp from any to any port 8000

# Allow incoming TCP traffic to port 8000 from localhost
pass in inet proto tcp from 127.0.0.1 to any port 8000 no state
Clean up by disabling the firewall

You have seen your firewall rules in action. It is important to disable the packet filter before moving on so it does not interfere with your normal network traffic.

  • Disable the packet filter by running:
sudo pfctl -d

You should see pf disabled in the output. Your Mac is now back to its default state with no custom firewall rules active.

Your rules file is still saved

Disabling pf only turns off the active firewall. Your firewall_rules.conf file is still saved in your project folder. You used a separate custom file instead of editing your Mac's default configuration at /etc/pf.conf, so nothing persists after you disable pf.

You have written and tested real firewall rules that block and allow traffic at the port level. Next up, you will capture live network packets and see exactly what your HTTP traffic looks like on the wire.

Capture Live Network Traffic

In the last step, you wrote firewall rules that blocked and allowed traffic to your server. You saw the effect of those rules, but you couldn't actually see the individual packets moving through the network.

In this step, you'll use tcpdump to perform a live packet capture on your server's port. You'll watch the TCP handshake happen in real time and save a capture to a file you can analyze later.

In this step, get ready to:
  • Start a live packet capture on your server's port.
  • Observe the TCP handshake and HTTP exchange in real time.
  • Save a capture to a file and count the packets in a request-response cycle.
Start a live packet capture

So far, you've sent requests and seen the results. But what does the traffic actually look like on the wire? tcpdump lets you watch every packet that flows through a network interface in real time.

Your Python server is still running in one Terminal window. You'll need a separate window for tcpdump so both can run at the same time.

  • Open a new Terminal window by pressing Cmd+N in your existing Terminal app.
  • Start capturing packets on the loopback interface for port 8000 by running this command:
sudo tcpdump -i lo0 -n port 8000

What does this command do?

  • sudo runs the command with administrator privileges, which tcpdump needs to access raw network traffic.
  • -i lo0 tells tcpdump to listen on the loopback interface. This is the virtual interface your Mac uses when it talks to itself (like when curl connects to localhost).
  • -n disables DNS lookups so you see raw IP addresses instead of hostnames. This keeps the output fast and readable.
  • port 8000 filters the capture to only show traffic on your server's port.
  • Terminal will prompt you for your Mac password. Type it and press Enter. The characters won't appear as you type, but they are being entered.

You should see a line like listening on lo0 and then nothing else. That's expected. tcpdump is silently waiting for traffic to appear on port 8000.

Seeing Permission denied or No suitable device?

  • If you see Permission denied, make sure you included sudo at the start of the command.
  • If you see No suitable device found, double check that you typed lo0 (lowercase L, lowercase O, zero) for the loopback interface.
  • Help me debug tcpdump errors on macOS.
Make an HTTP request and observe the TCP handshake

Every HTTP request starts with a TCP handshake. Before any data is sent, the client and server exchange three packets to establish a connection. This is called the three-way handshake: SYN, SYN-ACK, ACK.

You need a separate Terminal window to make a request while tcpdump keeps running.

  • Open another new Terminal window by pressing Cmd+N.
  • Send a request to your server by running this command:
curl http://localhost:8000

You should see the HTML from your index.html file printed in this Terminal window.

  • Now switch back to the Terminal window running tcpdump. You should see a burst of new lines that appeared.

What am I looking at?

Each line in the tcpdump output is one packet. Look for these patterns in the Flags field (the letters inside square brackets):

  • [S] is a SYN packet. The client is saying "I want to connect."
  • [S.] is a SYN-ACK. The server replies "OK, I'm ready too."
  • [.] is an ACK. The client confirms "Great, let's go." This completes the three-way handshake.
  • [P.] is a PUSH with ACK. This is where the actual HTTP GET request and the HTML response travel.
  • [F.] is a FIN. The connection is closing down.

This is the same sequence that happens every time you load a website. Normally it's invisible, but tcpdump makes it visible.

Not seeing any output in tcpdump?

  • Make sure your Python server is still running in its original Terminal window. If you accidentally closed it, navigate back to your project folder and restart it with python3 -m http.server 8000.
  • Check that you typed localhost in the curl command, not your Mac's private IP. Only localhost traffic goes through the lo0 interface.
  • Help me figure out why tcpdump isn't capturing any packets.
Save a capture to a file and count the packets

Watching live output is useful for quick debugging. For deeper analysis, you can save a capture to a file. This is how professionals collect evidence of network issues or security incidents.

  • Go back to the Terminal window running tcpdump.
  • Stop the running capture by pressing Ctrl+C.
  • Start a new capture that writes to a file by running this command:
sudo tcpdump -i lo0 -n port 8000 -w ~/Desktop/capture.pcap

What does the -w flag do?

The -w flag tells tcpdump to write raw packets to a file instead of printing them to your screen. The .pcap format is the standard for packet captures. You can open these files later with tools like Wireshark for visual analysis.

You should see listening on lo0 again. The capture is running, but this time the packets go to the file instead of your screen.

  • Switch to your other Terminal window (the one you used for curl earlier).
  • Send another request to your server by running this command:
curl http://localhost:8000
  • Switch back to the Terminal running tcpdump.
  • Stop the capture by pressing Ctrl+C.

You should see a summary line like X packets captured. Now let's read the file and get an exact count.

  • Count the packets in your capture file by running this command:
tcpdump -r ~/Desktop/capture.pcap | wc -l

What does this command do?

  • tcpdump -r reads packets from a saved file instead of capturing live traffic.
  • The | pipe sends that output to wc -l, which counts the number of lines. Each line is one packet, so you get the total packet count for one HTTP request-response cycle.

You should see a number, typically between 8 and 15. That is how many individual TCP packets it takes for a single curl request to your server: the three-way handshake, the HTTP request, the HTML response, and the connection teardown.

  • Confirm the capture file exists on your Desktop by running:
ls ~/Desktop/capture.pcap

You should see /Users/<your-username>/Desktop/capture.pcap printed in the output.

Seeing 0 packets or an empty file?

  • Make sure you ran the curl command AFTER starting the tcpdump -w capture, not before. Packets that happened before the capture started are not recorded.
  • Check that your Python server is still running. If it's stopped, navigate to ~/Desktop/network-lab and restart it with python3 -m http.server 8000, then repeat the capture steps.
  • Help me figure out why my pcap capture file is empty.

Why does this matter for cloud networking?

Packet captures are one of the most powerful debugging tools in networking. When a cloud server can't reach a database, or a load balancer is dropping requests, professionals use the same technique you just practiced to find out exactly where packets are getting lost.

Cloud platforms like AWS offer VPC Flow Logs and Traffic Mirroring that work on the same principle. You're building the foundational skill here.

Secret mission

Build a Custom Server with Selective Firewall Rules

Your web server from Step 2 serves the same page to every visitor, no matter what URL they request. What if you could build a server that responds with different content depending on the path, and then lock it down so only localhost traffic gets through?

Before you go

Clean Up Your Resources

This project runs entirely on your Mac using built-in tools, so there are no ongoing cloud costs. Decide whether to keep your project files, pause by stopping any running processes, or delete everything entirely.

Resources you used:

  • Python HTTP server (running process on port 8000).
  • index.html file in your project folder.
  • network_identity.sh script in your project folder.
  • network_identity.txt file on your Desktop.
  • firewall_rules.conf file in your project folder.
  • capture.pcap packet capture file on your Desktop.
  • pf firewall changes (temporary, already disabled in Step 5).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your server and networking tools.

  • Your Python HTTP server keeps running in its Terminal window until you stop it with Ctrl+C.
  • All project files stay on your Desktop for future reference.
  • The pf firewall was already disabled in Step 5. No firewall rules are active.

Pause - I'll come back to this later

Stop any running processes to free up resources, but keep your files so you can pick up where you left off.

  • Switch to the Terminal window running your Python HTTP server and press Ctrl+C to stop it.
  • Close any extra Terminal windows you opened during the project.
  • Your project files (index.html, network_identity.sh, firewall_rules.conf) stay on your Desktop ready for next time.
  • To restart later, open Terminal, navigate to your project folder, and run python3 -m http.server 8000 again.

Delete - I don't want to use this again

Remove all project files and start fresh. Nothing persists after these steps.

  • Stop the Python HTTP server by pressing Ctrl+C in its Terminal window.
  • Delete your project folder from the Desktop. This removes index.html, network_identity.sh, and firewall_rules.conf.
  • Delete network_identity.txt from your Desktop.
  • Delete capture.pcap from your Desktop.
  • The pf firewall was already disabled and restored to defaults in Step 5. No system-level changes remain.

Mission accomplished

That's a Wrap!

Nice work! You just explored your Mac's entire networking stack from the ground up.

You've learned how to:

  • Launched a Python web server, discovered your Mac's network identity (private IP, public IP, and interfaces), and made HTTP requests with curl.
  • Investigated DNS resolution with dig and nslookup, read your routing table, and traced packet paths across the internet with traceroute.
  • Wrote and applied firewall rules using macOS's built-in packet filter (the same concept behind cloud security groups) and captured live network traffic with tcpdump.
  • Secret Mission: Built a custom multi-path Python web server with selective firewall rules.

Ready to quiz yourself?