1. IP Addressing, DNS & Ports
Every device on a network gets an IP address — a numeric identifier
like 203.0.113.10 (IPv4, four 8-bit octets). Addresses starting with
10., 172.16.–172.31., or 192.168.
are private: they only mean something inside your own network and
get translated to a public address by NAT before reaching the internet. You'll see
this exact split again in Week 8 as an AWS VPC's public and private subnets.
10.0.0.0/8 # private -- 10.0.0.0 to 10.255.255.255
172.16.0.0/12 # private -- 172.16.0.0 to 172.31.255.255
192.168.0.0/16 # private -- 192.168.0.0 to 192.168.255.255
203.0.113.10/32 # a single public address (the /32 means "just this host")
The /N suffix is CIDR notation: it says how many
leading bits of the address are the fixed "network" part, leaving the rest for
individual hosts. A /24 fixes the first 24 bits (the first three
octets), leaving 8 bits — 256 addresses — for hosts on that network. Smaller numbers
mean bigger networks; you'll size VPC subnets with exactly this math in Week 8.
Humans don't remember IP addresses, so DNS (Domain Name System)
maps names to them. Resolving api.example.com walks a chain: your OS
checks its local cache, then asks a configured resolver (often your
ISP's or a public one like 1.1.1.1), which — if it doesn't already know
the answer — asks a root server, then a .com TLD server, then the
authoritative nameserver for example.com, which finally
returns the real IP.
$ dig api.example.com +short
203.0.113.10
$ dig api.example.com
;; ANSWER SECTION:
api.example.com. 300 IN A 203.0.113.10
# 300 = TTL in seconds -- how long resolvers may cache this answer
$ nslookup api.example.com
Once you have an IP, a connection still needs a port — a 16-bit number that tells the receiving machine which service to hand the traffic to. A handful of ports show up constantly in this course:
22 SSH # remote shell access (Week 1)
53 DNS # name resolution
80 HTTP # unencrypted web traffic
443 HTTPS # encrypted web traffic (TLS)
3306 MySQL
5432 PostgreSQL
6379 Redis
Communication over these ports follows the request/response model:
a client opens a connection and sends a request; the server processes it and sends
back a response with a status code (200 OK, 404 Not Found,
500 Internal Server Error) and, usually, a body. Every HTTP API you'll
call from a CI pipeline or a Terraform provider follows this exact shape.
That 300 in the dig output means resolvers may keep serving the old answer for up to five minutes after you change a record. If you're cutting a domain over to a new server, lower the TTL a day in advance so the change propagates fast when you actually flip it — this trips up almost every team the first time they migrate a production hostname.
2. TLS/HTTPS Basics & Firewalls
Plain HTTP sends everything — headers, cookies, request bodies — in cleartext; anyone on the network path can read or modify it. TLS (Transport Layer Security) wraps that traffic in encryption, and HTTPS is just "HTTP over TLS." The handshake happens before a single byte of your actual request is sent: the client and server agree on a cipher, the server proves its identity with a certificate signed by a trusted certificate authority, and both sides derive a shared encryption key for the rest of the connection.
$ openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN=example.com
issuer=C=US, O=Let's Encrypt, CN=R3
notBefore=Jan 2 00:00:00 2026 GMT
notAfter=Apr 2 23:59:59 2026 GMT
A firewall is a set of allow/deny rules applied to incoming and
outgoing traffic, usually by port and source address. On a single Linux host,
ufw (Uncomplicated Firewall) is a friendly front end for the kernel's
packet filter:
sudo ufw default deny incoming # deny everything unless explicitly allowed
sudo ufw default allow outgoing
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
sudo ufw enable
sudo ufw status verbose
Cloud providers add a second layer on top of the host firewall — AWS calls it a security group, and you'll configure one for real in Week 8. The principle is identical: deny by default, allow only the specific ports and sources a service actually needs.
The single most common self-inflicted outage in this space is enabling ufw or tightening a security group without first allowing your own SSH port — you lock yourself out of a box you can now only reach through a cloud console. Always add the allow rule for the port you're connected on before flipping default-deny on.
3. Reading a curl -v Trace End to End
curl is the single most useful debugging tool for anything HTTP-shaped,
and -v (verbose) shows every stage of the request/response model in
order — DNS, connection, TLS, headers, body. Learning to read this output on sight
will save you from guessing when an API call fails in a pipeline.
$ curl -v https://api.example.com/health
* Trying 203.0.113.10:443...
* Connected to api.example.com (203.0.113.10) port 443
* TLS handshake, Client hello (1):
* TLS handshake, Server hello (2):
* TLSv1.3 connection using TLS_AES_256_GCM_SHA384
* Server certificate: CN=api.example.com
> GET /health HTTP/1.1
> Host: api.example.com
> User-Agent: curl/8.5.0
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< content-type: application/json
< content-length: 15
<
{"status":"ok"}
* Connection #0 to host api.example.com left intact
Lines starting with * are curl's own status messages: DNS resolution,
the TCP connection, and the TLS handshake, all happening before your request is
sent. Lines starting with > are the exact bytes curl sent — the
request line and headers. Lines starting with < are what the server
sent back — the status line and response headers — followed by the response body
with no prefix at all. If a trace stops after the * lines, the problem
is networking or TLS, not your application; if it stops after the >
lines with no < response, the server accepted the connection but
never answered — often a slow backend or a security group blocking the return path.
curl -I https://api.example.com # headers only, no body (HEAD request)
curl -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://api.example.com
curl --resolve api.example.com:443:203.0.113.99 https://api.example.com
# force a hostname to resolve to a specific IP,
# useful for testing a new server before the DNS cutover
When you need to test that a new server is actually serving the right content before you flip DNS over to it, curl --resolve is scoped to that single command and leaves no cleanup behind — unlike editing /etc/hosts, which is easy to forget and later debug an unrelated "why is this URL going to the wrong place" mystery.
4. Git Branching Workflows for Ops
Treating infrastructure config the same way you treat application code —
version-controlled, reviewed, and reversible — is a core DevOps habit. The pattern
is a feature branch per change, a pull request for
review, and a protected main branch that only changes through a merge:
git checkout -b add-nginx-rate-limit
# ...edit nginx.conf...
git add nginx.conf
git commit -m "Add rate limiting to /api routes to stop the scraper traffic we saw Tuesday"
git push -u origin add-nginx-rate-limit
# open a pull request in the GitHub UI, request a review, merge once approved
This matters more for infrastructure than for application code: a bad application deploy usually breaks a feature, but a bad infrastructure change can take down everything at once. A required review before merge is a second set of eyes on exactly the kind of change — a firewall rule, a DNS record, a Terraform resource — that's easy to get subtly wrong and expensive to get wrong in production.
Once changes are merged, Git history itself becomes an audit trail: every infrastructure change has an author, a timestamp, a message explaining why, and a full diff of exactly what changed.
git log --oneline --graph -- nginx.conf
# a94f2c1 Add rate limiting to /api routes to stop the scraper traffic we saw Tuesday
# 7b1e0aa Increase client_max_body_size for file uploads
# 3d40912 Initial nginx reverse proxy config
git blame nginx.conf # who last touched each line, and in which commit
git show a94f2c1 # the full diff and commit message for one change
git tag -a v1.4.0 -m "Nginx config as deployed to prod, 2026-08-03"
When something breaks in production and you need to know "what changed and who
approved it," git log and git blame answer that in
seconds — no separate change-management system required. If a merged change turns
out to be wrong, git revert creates a new commit that undoes it, which
is safer in a team setting than rewriting history with git reset.
"Fix bug" tells a future debugger nothing. "Add rate limiting to /api routes to stop the scraper traffic we saw Tuesday" tells them what changed, why, and gives them a starting point if the same symptom reappears. You'll see this same discipline pay off directly when you write GitHub Actions workflows in Week 6 that key off commit messages and PR metadata.
5. Hands-on Exercise
Trace a live request and commit it as a runbook
Combine DNS lookups, a curl trace, and a proper Git ops workflow into one reviewable artifact.
Requirements:
- Pick any public HTTPS site and run
dig <domain> +shortto resolve it, thenopenssl s_client -connect <domain>:443 -servername <domain>piped intoopenssl x509 -noout -subject -issuer -datesto inspect its certificate. - Run
curl -v https://<domain>and save the full output. - In a local git repo, create a branch named
add-request-runbook. - Write
runbook.mddocumenting, in your own words, what each stage of thecurl -voutput means (DNS/connect, TLS handshake, request headers, response headers, body) and the certificate's issuer and expiry date. - Commit with a message explaining what the runbook is for, push the branch, and open a pull request against
main(a repo you own is fine — the goal is practicing the workflow). - Merge the PR, then run
git log --onelineand confirm the commit message alone explains what changed and why, with no need to reread the diff.
If openssl s_client hangs waiting for input, add </dev/null to the end of the command — it stops the tool from waiting on stdin and lets the pipe to openssl x509 complete immediately.
6. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why can't a device with the private address 10.0.0.5 reach the public internet on its own?
Why can't a device with the private address 10.0.0.5 reach the public internet on its own?
Private ranges like 10.0.0.0/8 aren't routable on the public internet — routers outside your network don't know how to send traffic back to them, and duplicate use across countless private networks means a public reply couldn't know which network to return to. A NAT gateway rewrites the source address to a public one on the way out and reverses the mapping on the way back, which is exactly what happens at the edge of an AWS VPC's private subnet in Week 8.
Q2
In a curl -v trace, what does it mean if the output stops right after the last > line with no < response following it?
In a curl -v trace, what does it mean if the output stops right after the last > line with no < response following it?
> lines are what curl sent to the server, and < lines are what the server sent back. If the trace stops after your request was fully sent but before any response arrives, the connection and TLS handshake succeeded, so the server accepted the request — the problem is on the server side: it's hanging, overloaded, or a security group/load balancer is silently dropping the return traffic rather than rejecting the connection outright.
Q3
Why might a DNS record change not take effect immediately, even right after you update it?
Why might a DNS record change not take effect immediately, even right after you update it?
DNS answers are cached by resolvers for as long as the record's TTL (time-to-live) says, so anyone who already resolved the old value keeps using it until that cached entry expires — regardless of when the authoritative record actually changed. That's why a planned cutover should lower the TTL well in advance, so the old cached answers expire quickly once the record is actually flipped.
Q4
Why is Git history described as an "audit trail" for infrastructure changes — what does it give you that a shared doc or chat log doesn't?
Why is Git history described as an "audit trail" for infrastructure changes — what does it give you that a shared doc or chat log doesn't?
Every commit is immutably tied to an author, a timestamp, and an exact diff of what changed, and git blame/git log let you jump straight from "this line is wrong" to "who changed it, when, and why they said they did" without hunting through separate chat history or hoping someone remembered to update a doc. A pull request adds a recorded approval on top of that, so the trail covers both what changed and who signed off on it.