Learn the networking basics every DevOps engineer needs, IPv4 and IPv6 addresses, public and private ranges, CIDR subnets, ports, TCP vs UDP, and how to debug connection problems.

Bishal Biswas
Self Learner
Why DevOps engineers need networking
Every deployment is a networking problem in disguise. Your app has to listen on the right port, the server needs the right IP address, the firewall has to let traffic in, and the database must be reachable from the app but not from the internet.
When something breaks, the error is often a networking one: connection refused, connection timed out, no route to host. Knowing what those mean saves hours.
You do not need to be a network engineer. This section gives you the practical basics, starting with three ideas:
- an IP address says which machine;
- a port says which program on that machine;
- a protocol says how they talk.
Think of a large apartment building. The IP address is the street address of the building, the port is the flat number, and the protocol is the language you speak when someone opens the door.
IP addresses
Every device on a network has an IP address (IP stands for Internet Protocol). Data sent across a network is chopped into small packets, and each packet carries the address it came from and the address it is going to, just like an envelope.
IPv4
The classic format is IPv4: four numbers from 0 to 255, separated by dots.
203.0.113.10
Under the hood it is one 32-bit number, which allows about 4.3 billion addresses. That sounded like plenty in the 1980s. It is not enough for today’s billions of phones, laptops, servers and smart fridges, and the free supply of new IPv4 addresses has run out. Public IPv4 addresses are now bought, sold and rented, which is why cloud providers charge for them.
IPv6
The fix is IPv6, which uses 128 bits: enough for an almost unimaginable number of addresses. It is written as eight groups of hexadecimal digits:
2001:0db8:0000:0000:0000:0000:0000:0001
2001:db8::1 <- the same address, shortened
Leading zeros in a group can be dropped, and one run of all-zero groups can be replaced by ::.
Most of the internet now runs both side by side. You will mostly type IPv4 addresses day to day, but expect to see IPv6 in DNS records and cloud consoles.
Public and private addresses
Not every IP address is reachable from the internet. Three ranges are reserved as private, for use inside homes, offices and cloud networks:
| Private range | Written as | Typical use |
|---|---|---|
| 10.0.0.0 to 10.255.255.255 | 10.0.0.0/8 |
Cloud networks, large companies |
| 172.16.0.0 to 172.31.255.255 | 172.16.0.0/12 |
Docker’s default networks, some clouds |
| 192.168.0.0 to 192.168.255.255 | 192.168.0.0/16 |
Home and small office routers |
Your laptop at home probably has an address like 192.168.1.23. Thousands of other homes use exactly the same address, and that is fine, because private addresses never travel on the public internet.
So how does your laptop reach a website? Your home router has one public address and translates between it and your private ones. This trick is called NAT (Network Address Translation). Cloud servers work the same way: a server often has a private IP inside the cloud network, plus a public IP that the provider maps to it.
Tip: This is a key security idea. Put databases and internal services on private addresses only. They can talk to your app, but nobody on the internet can even try to reach them.
Special addresses
| Address | Meaning |
|---|---|
127.0.0.1 (and ::1 in IPv6) |
Loopback, also called localhost: “this same machine”. Traffic never leaves the computer. |
0.0.0.0 |
When a server listens on it: “accept connections on every network interface”. |
203.0.113.0/24, 198.51.100.0/24, 192.0.2.0/24 |
Reserved for documentation. Safe to use in examples, like this course does. |
The difference between 127.0.0.1 and 0.0.0.0 catches out a lot of people. An app that listens on 127.0.0.1:3000 can only be reached from the same machine. The same app listening on 0.0.0.0:3000 can be reached from other machines too (if the firewall allows it). You will meet this again when an app works inside a Docker container but cannot be reached from outside it.
Subnets and CIDR notation
Networks are divided into smaller blocks called subnets. You describe a block with CIDR notation: an address, a slash, and a number.
10.0.1.0/24
The number after the slash says how many bits, counting from the left, are fixed for the network. The rest are free for individual machines. An IPv4 address has 32 bits, so:
/24fixes the first 24 bits (the first three numbers), leaving 8 bits: 256 addresses,10.0.1.0to10.0.1.255./16fixes the first two numbers, leaving 16 bits: 65,536 addresses,10.0.0.0to10.0.255.255.
A smaller number after the slash means a bigger network.
| CIDR | Addresses | Example range | Typical use |
|---|---|---|---|
/32 |
1 | 203.0.113.10 only |
One exact machine, e.g. a firewall rule |
/28 |
16 | 10.0.1.0 - 10.0.1.15 |
A tiny subnet |
/24 |
256 | 10.0.1.0 - 10.0.1.255 |
A typical subnet |
/16 |
65,536 | 10.0.0.0 - 10.0.255.255 |
A whole cloud network (VPC) |
/0 |
All of them | Everything | “Anywhere on the internet” in firewall rules |
In each subnet, the first address names the network itself and the last is the broadcast address, so neither is given to a machine. Cloud providers reserve a few more (AWS keeps five per subnet).
You will use CIDR all the time in DevOps:
- Cloud networks: a VPC of
10.0.0.0/16, split into a public subnet10.0.1.0/24for load balancers and a private subnet10.0.2.0/24for app servers and databases. More in Cloud Computing Basics. - Firewall rules: “allow SSH only from
198.51.100.7/32” (your office) or “allow HTTPS from0.0.0.0/0” (everyone).
The code panel for this lesson has a small CIDR calculator so you can see exactly what any block contains.
Ports
One server runs many programs at once: a web server, SSH, maybe a database. When a packet arrives, how does the machine know which program it is for? By the port: a number from 0 to 65535.
A program listens on a port. A connection is always to an IP address plus a port, written like this:
203.0.113.10:443
Some ports are standard, so everyone knows where to knock:
| Port | Used by |
|---|---|
| 22 | SSH |
| 25 | SMTP (sending email between servers) |
| 53 | DNS |
| 80 | HTTP |
| 443 | HTTPS |
| 3306 | MySQL |
| 5432 | PostgreSQL |
| 6379 | Redis |
| 27017 | MongoDB |
| 3000, 5173, 8080 | Common choices for apps in development |
When you type https://example.com into a browser, it quietly uses port 443. http:// uses port 80. You only see a port in the URL when it is not the default, such as http://localhost:3000.
A few things worth knowing:
- Only one program can listen on a given IP and port at a time. Start a second one and you get
address already in use(Node calls itEADDRINUSE). - On Linux, ports below 1024 need root privileges to listen on. That is one reason apps usually run on a high port such as 3000 behind a reverse proxy that listens on 80 and 443.
- The client side of a connection uses a random, temporary high port, called an ephemeral port. You rarely have to think about it.
Protocols: TCP and UDP
A protocol is an agreed set of rules for talking. Networking uses layers of protocols, each building on the one below:
| Layer | Job | Examples |
|---|---|---|
| Application | What the programs are saying | HTTP, DNS, SSH, SMTP |
| Transport | Getting data between two programs | TCP, UDP |
| Internet | Getting packets between two machines | IP |
| Link | Getting bits across one physical link | Ethernet, Wi-Fi |
At the transport layer there are two main choices:
| TCP | UDP | |
|---|---|---|
| Connection | Sets one up first, with a “handshake” | None: just sends |
| Delivery | Guaranteed, in order; lost packets are re-sent | Best effort: packets may be lost or arrive out of order |
| Speed | A little slower | Faster, less overhead |
| Like | A phone call | Shouting across a room, or sending postcards |
| Used by | HTTP/1.1 and HTTP/2, SSH, databases, email | DNS lookups, video calls, online games, HTTP/3 |
Most of what you deploy uses TCP. Before any data is sent, the two sides do a quick three-way handshake:
Client --- SYN -------> Server "Can we talk?"
Client <-- SYN-ACK ---- Server "Yes, can you hear me?"
Client --- ACK -------> Server "Yes. Here comes the data."
That handshake is what makes the most useful debugging trick in this lesson possible.
Debugging connections: refused vs timed out
When a connection fails, how it fails tells you a lot:
| Error | What happened | Usual causes |
|---|---|---|
Connection refused |
The machine was reached and replied: “nothing is listening on that port” | The app is not running, crashed, or listens on a different port or on 127.0.0.1 only |
Connection timed out |
No reply at all; the packets vanished | A firewall or security group is silently dropping them, the IP is wrong, or the machine is off |
No route to host |
The network cannot find a path to that address | Wrong IP or subnet, network misconfiguration |
Name or service not known |
The name could not be turned into an IP address | A DNS problem: see DNS: How Domain Names Become Addresses |
So: refused means “check the app”; timed out means “check the firewall”.
Commands you will use
ip addr # this machine's network interfaces and IP addresses (short: ip a)
hostname -I # just the IP addresses
curl -s https://ifconfig.me # your PUBLIC IP, as the internet sees it
ping -c 3 203.0.113.10 # is the machine reachable at all? (some block ping)
ss -tlnp # which programs are listening on which TCP ports
nc -zv 203.0.113.10 5432 # can I open a TCP connection to this port?
traceroute example.com # the path packets take, hop by hop
ss -tlnp (show TCP sockets that are listening, with numeric ports and the process) is the first thing to run when you suspect an app is not listening where you think:
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=655,fd=6))
LISTEN 0 4096 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=541,fd=3))
LISTEN 0 511 127.0.0.1:3000 0.0.0.0:* users:(("node",pid=812,fd=21))
Here nginx and SSH accept connections from anywhere, while the Node app on port 3000 only accepts connections from the same machine, which is exactly right if nginx forwards traffic to it.
The CIDR calculator
The code panel’s script works out everything about a CIDR block: its mask, first and last usable address, broadcast address and size. It also checks whether an address is inside a block, and whether it is private or public. Run it with no arguments for a tour:
node subnet.mjs
10.0.1.0/24
mask 255.255.255.0
network 10.0.1.0
hosts 10.0.1.1 - 10.0.1.254
broadcast 10.0.1.255
addresses 256 (254 usable)
...
Which network is it in?
10.0.1.57 in 10.0.1.0/24? true
10.0.2.5 in 10.0.1.0/24? false
Private or public?
192.168.1.20 private (only reachable inside a network)
8.8.8.8 public
Or check your own block and address:
node subnet.mjs 192.168.1.0/26 192.168.1.70
192.168.1.0/26
mask 255.255.255.192
network 192.168.1.0
hosts 192.168.1.1 - 192.168.1.62
broadcast 192.168.1.63
addresses 64 (62 usable)
192.168.1.70 is outside this network
Common misunderstandings
“It works on localhost, so the server is fine”
localhost only proves the app works on the same machine. Other machines also need the app to listen on 0.0.0.0 (or the server’s IP), the firewall to allow the port, and the right public address.
“A private IP is secure, so I do not need a firewall”
Private addresses keep the internet out, but anything inside the same network can still reach them. If one server is broken into, an open database next door is an easy next step. Use firewall rules inside the network too.
“Ping failed, so the server is down”
Many servers and clouds block ping (it uses a protocol called ICMP) on purpose. Try connecting to the actual port with nc -zv or curl before deciding.
Quick recap
- An IP address identifies a machine; IPv4 looks like
203.0.113.10, IPv6 like2001:db8::1. - Private ranges (
10/8,172.16/12,192.168/16) are not reachable from the internet; NAT shares a public address. 127.0.0.1is this machine only; listening on0.0.0.0accepts connections on every interface.- CIDR such as
10.0.1.0/24describes a block of addresses: a smaller number means a bigger block. - A port identifies a program; HTTPS is 443, HTTP 80, SSH 22.
- TCP is reliable and connection-based; UDP is fast and fire-and-forget.
- Refused means nothing is listening; timed out usually means a firewall.
Next, you will see how names like github.com turn into IP addresses in DNS: How Domain Names Become Addresses.
// subnet.mjs: a tiny CIDR calculator. See what "10.0.1.0/24" really means.
// Run it with: node subnet.mjs (uses the examples below)
// or: node subnet.mjs 192.168.1.0/26 192.168.1.70
// An IPv4 address is really one 32-bit number, written as four 8-bit parts.
const toInt = (ip) => ip.split('.').reduce((n, part) => n * 256 + Number(part), 0);
const toIp = (n) => [24, 16, 8, 0].map((shift) => (n >>> shift) & 255).join('.');
function parse(cidr) {
const [ip, bits] = cidr.split('/');
const prefix = Number(bits);
// The mask has `prefix` ones on the left: /24 = 255.255.255.0
const mask = prefix === 0 ? 0 : (0xffffffff << (32 - prefix)) >>> 0;
const network = (toInt(ip) & mask) >>> 0;
const broadcast = (network | ~mask) >>> 0;
return { prefix, mask, network, broadcast, size: 2 ** (32 - prefix) };
}
function describe(cidr) {
const { prefix, mask, network, broadcast, size } = parse(cidr);
// The first address names the network and the last is broadcast, so neither is
// given to a machine. (Clouds such as AWS also reserve a few more.)
const usable = prefix >= 31 ? size : size - 2;
console.log(`${cidr}`);
console.log(` mask ${toIp(mask)}`);
console.log(` network ${toIp(network)}`);
console.log(` hosts ${toIp(network + 1)} - ${toIp(broadcast - 1)}`);
console.log(` broadcast ${toIp(broadcast)}`);
console.log(` addresses ${size.toLocaleString('en-US')} (${usable.toLocaleString('en-US')} usable)`);
}
const contains = (cidr, ip) => {
const { mask, network } = parse(cidr);
return ((toInt(ip) & mask) >>> 0) === network;
};
const PRIVATE = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'];
const isPrivate = (ip) => PRIVATE.some((range) => contains(range, ip));
const [cidrArg, ipArg] = process.argv.slice(2);
if (cidrArg) {
describe(cidrArg);
if (ipArg) console.log(` ${ipArg} is ${contains(cidrArg, ipArg) ? 'inside' : 'outside'} this network`);
} else {
['10.0.0.0/16', '10.0.1.0/24', '192.168.1.64/26'].forEach(describe);
console.log('\nWhich network is it in?');
for (const ip of ['10.0.1.57', '10.0.2.5']) {
console.log(` ${ip.padEnd(12)} in 10.0.1.0/24? ${contains('10.0.1.0/24', ip)}`);
}
console.log('\nPrivate or public?');
for (const ip of ['192.168.1.20', '172.20.3.4', '10.8.0.1', '8.8.8.8', '203.0.113.10']) {
console.log(` ${ip.padEnd(12)} ${isPrivate(ip) ? 'private (only reachable inside a network)' : 'public'}`);
}
}
Previous
Branching Strategies: GitFlow vs Trunk-Based Development
Next
DNS: How Domain Names Become Addresses
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready