Why This Thing Won't Talk
to That Thing
DNS, ports, localhost, firewalls, SSH, CORS, TLS — and a systematic way to find what's broken in under five minutes.
Who This Is For
This guide is for people who run into walls when code meets a network. Your local server isn't reachable. Your API call is getting blocked. SSH won't connect. Something works on your machine but breaks when deployed. A CORS error appeared and you have no idea why.
You don't need to be a network engineer. You need to understand how connections actually work well enough to diagnose what's broken rather than randomly trying things until one of them accidentally fixes it.
Plan for 35 minutes. By the end, you'll have a mental model that makes connection failures diagnosable rather than mysterious, working knowledge of the specific concepts that block people most — DNS, ports, localhost, firewalls, SSH, CORS — and a debugging approach that finds the problem in minutes rather than hours.
Why This Matters Now
Running local servers, calling APIs, deploying to cloud infrastructure, and building services that talk to each other used to be specialist territory. Now it's part of the normal workflow for developers, data scientists, technical product managers, and anyone operating in a technical environment.
The tooling got more accessible. The underlying network concepts didn't. "Why can't I connect to this thing" is one of the most common blocking problems across every technical discipline, and it's almost never covered in tutorials because it's not specific to any one technology. It's the infrastructure the tutorials assume you understand.
The good news: the vast majority of connection failures have the same small set of root causes. Learn to recognize them and you can diagnose most failures in under five minutes.
The Mental Model: A Connection Is a Chain
Here's the insight that changes how you debug connection problems: a network connection isn't one thing — it's a chain of discrete steps, each of which can fail independently.
When your browser tries to connect to api.example.com:443, the following must all succeed:
api.example.com to an IP address2. A route exists from your machine to that IP
3. A firewall somewhere between you and the server allows the traffic
4. The server's firewall allows the incoming connection on port 443
5. A process is actually listening on port 443 on the server
6. The process accepts your connection and speaks the protocol you expect
7. If TLS, the certificate is valid and trusted
8. The application-level response comes back the same way in reverse
Any one of these links failing produces "connection failed" — but each one fails differently and has a different fix. The person who can connect a symptom to a specific link in the chain diagnoses in minutes. The person who thinks of it as one undifferentiated failure randomizes their fixes until something works.
DNS: The Phonebook You Can't See
DNS (Domain Name System) translates human-readable names like api.example.com into IP addresses like 93.184.216.34 that computers actually route to. It's the first link in the chain, and it fails in ways that look like other things.
When your browser connects to api.example.com, before any network connection happens, your computer asks a DNS resolver: "what's the IP for api.example.com?" The resolver returns an answer. Your computer then connects to that IP.
What Can Go Wrong
The name doesn't exist. Either you mistyped it, or the domain isn't configured, or the DNS record was deleted. Error: NXDOMAIN (non-existent domain) or "server not found."
The name resolves to the wrong IP. Common when you've just changed DNS records and the old value is cached. DNS records have TTLs (time to live) — the duration in seconds that a resolver caches the answer. If you updated a record but the TTL was 86400 (one day), resolvers worldwide continue returning the old value for up to 24 hours.
The resolver itself is unreachable. If your DNS resolver is down or you're on a network that blocks DNS, no names resolve. This looks like "everything is broken" but is actually "DNS is broken" — you can still connect to things by IP address.
The answer is being intercepted. Corporate networks, captive portals, and some ISPs intercept DNS queries and return different answers. This is why example.com might resolve to different IPs depending on which network you're on.
How to Check DNS
# Look up a domain's IP addresses dig api.example.com # Simpler output, same lookup nslookup api.example.com # Specify a particular DNS server (bypasses your system's default) dig api.example.com @8.8.8.8 # Check all DNS record types dig api.example.com ANY # See what your system would actually resolve (includes /etc/hosts) getent hosts api.example.com # Linux
dig is the primary tool. The output shows you what record was returned, from which server, and the TTL remaining on the cached answer. When you've just changed a DNS record and it's not taking effect, dig @8.8.8.8 (using Google's DNS rather than your local resolver) tells you whether the change is live at the authoritative level, even if your local cache still has the old value.
The /etc/hosts File
The /etc/hosts file is a local override for DNS. Before your computer makes any DNS query, it checks /etc/hosts. If a name appears there, that IP is used — period, regardless of what DNS says. This is how developers fake domains locally (127.0.0.1 myapp.local), and it's also why your machine might resolve something differently from every other machine on the network.
# Common entries in /etc/hosts: # 127.0.0.1 localhost # 127.0.0.1 myapp.local ← manual override
/etc/hosts first. A stale entry there overrides all DNS lookups.IP Addresses and What They Actually Mean
An IP address is a numeric identifier for a machine on a network. The most common form is IPv4: four numbers, each 0–255, separated by dots. Most connection problems involve one of three IP address categories, and confusing them is one of the most common sources of errors.
Public IPs
Globally routable — any machine on the internet can connect to them. Your server at AWS, a web API, a CDN endpoint. These are what DNS records usually point to.
Private IPs
Reserved for internal networks and not routable on the public internet. Three ranges:
10.0.0.0 – 10.255.255.255 172.16.0.0 – 172.31.255.255 192.168.0.0 – 192.168.255.255
Your home router probably has a public IP from your ISP and assigns private IPs to devices on your network. Your devices reach the internet through NAT (Network Address Translation), where the router translates their private addresses to the public one.
Loopback
127.0.0.1 and the entire 127.x.x.x range is special. Traffic to 127.0.0.1 never leaves your machine — it loops back immediately. localhost is just a hostname that resolves to 127.0.0.1 (or ::1 in IPv6). Anything listening on 127.0.0.1 is only accessible from the machine itself.
localhost:3000 isn't accessible from another machine. It's not blocked by a firewall — it's not even trying to reach the network. The address itself says "stay local."IPv6
The modern addressing system with a much larger address space. Addresses look like 2001:0db8:85a3:0000:0000:8a2e:0370:7334 or in shorthand 2001:db8::1. ::1 is the IPv6 loopback equivalent of 127.0.0.1. Most tools handle both automatically, but when something works on one and not the other, IPv6 is the first thing to check.
What's My IP?
The question has two answers: your machine's local IP (what other machines on your network see) and your public IP (what the internet sees). They're different, and conflating them is a frequent source of confusion.
# Your machine's network interfaces and their IPs ip addr show # Linux ifconfig # Mac/older Linux # Your public IP (what external services see) curl ifconfig.me curl api.ipify.org
Ports: The Apartment Numbers
If an IP address is the building address, a port is the apartment number. A single machine runs many services simultaneously — a web server, an SSH daemon, a database, your local dev server. Ports are how the operating system routes incoming connections to the right process.
Ports are numbers from 0 to 65535. The conventions:
Well-Known Ports (0–1023)
Assigned to standard protocols. Port 80 is HTTP. Port 443 is HTTPS. Port 22 is SSH. Port 5432 is PostgreSQL. Port 3306 is MySQL. Port 6379 is Redis. On Unix systems, binding to ports below 1024 requires root privileges.
Registered and Ephemeral Ports
Registered ports (1024–49151) are used by applications and services. Not formally reserved, but conventionally associated with specific software.
Ephemeral ports (49152–65535) are assigned temporarily by the OS for outbound connections. When your browser connects to a server, the server gets your IP and one of these randomly assigned ports for that specific connection.
When you see :8080 or :3000 or :8000 in a development URL, those are chosen arbitrarily to avoid conflicting with system services. :8080 is the informal convention for "HTTP, but not on the real port 80."
Listening vs. Connecting
A process that "listens" on a port is waiting for incoming connections. A process that "connects" to a port is initiating. You can only connect to a port where something is actively listening. If nothing is listening, you get "connection refused" — not a firewall block, not a DNS failure, just nothing home.
How to Check What's Listening
# What's listening on your machine and which process owns it ss -tlnp # Linux (preferred) netstat -tlnp # Linux (older) lsof -i -P -n # Mac # Is something specifically listening on port 5432? ss -tlnp | grep 5432 # Can you reach a port on a remote host? nc -zv hostname 5432 # netcat: verbose connection test telnet hostname 5432 # older equivalent
ss output shows you the address the process is bound to. 0.0.0.0:5432 means listening on all interfaces — reachable from anywhere that can reach the machine. 127.0.0.1:5432 means listening only on loopback — only accessible locally. This distinction matters enormously.localhost and the Binding Problem
localhost trips people up constantly when they first run services and try to reach them from another machine or container.
When you start a development server — Django, Rails, Next.js, FastAPI — the default is typically to bind to 127.0.0.1 (loopback). That means only code running on the same machine can connect to it. It's a deliberate security default: you don't want a dev server accidentally exposed to your whole network.
The Symptom and the Fix
The symptom: your server is running. You can hit it from your browser on the same machine. Another device on your network, or another Docker container, cannot connect. connection refused.
The fix: bind to 0.0.0.0 instead of 127.0.0.1. This tells the process to listen on all available network interfaces — loopback, your local IP, whatever else is configured.
# Common examples of binding to all interfaces python -m http.server 8000 --bind 0.0.0.0 # Python flask run --host=0.0.0.0 python manage.py runserver 0.0.0.0:8000 # Django uvicorn main:app --host 0.0.0.0 --port 8000 # FastAPI
Docker Networking
In Docker, this is especially important. Each container has its own network namespace. localhost inside a container refers to the container itself, not your host machine. A server bound to 127.0.0.1 inside a container is only accessible inside that container.
When containers need to talk to each other, they use a Docker network — either the default bridge network (containers reach each other by container name as hostname) or a user-defined network. When a container needs to reach a service on the host machine, the special hostname host.docker.internal resolves to the host's IP (on Mac and Windows).
# docker-compose.yml: containers on the same network
# can reach each other by service name
services:
web:
image: myapp
ports:
- "8000:8000" # host_port:container_port
db:
image: postgres
# 'web' container reaches this as host: db, port: 5432-p host_port:container_port) publishes a container port to the host. -p 8080:80 makes the container's port 80 accessible on the host's port 8080. Without this, the container's ports are accessible only from within Docker networks.Firewalls: The Bouncers
A firewall is a filter that decides which network traffic to allow and which to block, based on rules about source, destination, port, and protocol. There are firewalls on your machine, between machines, at cloud provider edges, and in your operating system's network stack.
Timeout vs. Refused
"Connection refused" means something received your connection attempt and rejected it. "Connection timed out" usually means a firewall silently dropped the packet — nobody answered the door. This is the single most useful distinction for firewall debugging.
Host-Based Firewalls
Run on the machine itself. On Linux, iptables and its modern replacement nft (nftables) are the underlying mechanism, usually managed through a friendlier tool:
# Ubuntu/Debian: ufw (Uncomplicated Firewall) sudo ufw status # see current rules sudo ufw allow 8080/tcp # allow incoming on port 8080 sudo ufw deny 3306/tcp # block MySQL from outside # Check iptables directly sudo iptables -L -n -v
On macOS, the firewall is in System Settings → Network → Firewall. It's off by default and rarely the culprit for development issues.
Cloud Provider Security Groups
Firewalls managed at the cloud level, outside your VM. AWS Security Groups, GCP Firewall Rules, Azure Network Security Groups — these control what traffic can reach your instances. They're stateful: you define inbound and outbound rules.
Inbound: 443/tcp from 0.0.0.0/0
Inbound: 22/tcp from your IP (or a bastion)
Outbound: all (default)
VPNs and Corporate Networks
Add another firewall layer. If you're on a corporate VPN, traffic may be routed through the corporate network and subject to its firewall rules — rules that may block ports, block certain domains, or intercept traffic. The tell: something works fine off VPN and fails on VPN, or vice versa.
How to Test Through a Firewall
# Test if a port is reachable from your machine # timeout = blocked, refused = port closed, success = open nc -zv -w5 hostname 443 # Trace the path packets take (where does it stop?) traceroute hostname # Mac/Linux tracert hostname # Windows mtr hostname # interactive, more detailed # Test with curl to see exact error curl -v https://api.example.com # -v shows the full connection attempt
SSH: The Encrypted Tunnel
SSH (Secure Shell) is how you connect to remote machines securely. It's also a powerful tool for bypassing firewall and network limitations through tunneling. If you're running servers, you will use SSH constantly.
Basic Connection
ssh user@hostname ssh user@hostname -p 2222 # non-standard port ssh -i ~/.ssh/mykey.pem user@hostname # specify key file
Key-Based Authentication
You generate a key pair — a private key that stays on your machine and a public key that gets placed on the server in ~/.ssh/authorized_keys. When you connect, you prove you have the private key without sending it. Password auth still works but is discouraged.
# Generate a key pair ssh-keygen -t ed25519 -C "[email protected]" # Creates: ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public) # Copy public key to a server ssh-copy-id user@hostname
SSH Config
~/.ssh/config eliminates typing long connection strings:
Host myserver
HostName 93.184.216.34
User ubuntu
IdentityFile ~/.ssh/mykey.pem
Port 22Now ssh myserver works instead of the full command.
SSH Tunnels
The underrated capability. An SSH tunnel forwards a local port through the SSH connection to a port on the remote host (or any host the remote host can reach). This lets you access services that aren't exposed to the internet — databases, internal APIs, admin interfaces.
Local forwarding (-L): makes a remote port accessible locally.
# Forward local port 5433 → remote port 5432 (database not exposed publicly) ssh -L 5433:localhost:5432 user@server # Now connect to the database via localhost:5433 from your machine psql -h localhost -p 5433 -U myuser mydb
Remote forwarding (-R): exposes a local port to the remote server.
# Make your local port 3000 accessible on the server's port 8080 ssh -R 8080:localhost:3000 user@server # Now hitting server:8080 reaches your local:3000
Jump hosts (-J): connect to a machine that's only reachable through another machine.
# Connect to private-server through bastion
ssh -J user@bastion user@private-server
# In config:
Host private-server
HostName 10.0.0.5
User ubuntu
ProxyJump bastionCommon SSH Failures
Permission denied (publickey) — the server doesn't have your public key, you're specifying the wrong key, or the key file permissions are wrong. Private key files must be chmod 600 (readable only by owner); SSH refuses to use them otherwise.
Connection timed out — firewall is blocking port 22, or the host is unreachable.
Connection refused — SSH daemon isn't running, or it's on a different port.
Host key verification failed — the server's host key changed (common after rebuilding a server). Remove the old entry: ssh-keygen -R hostname.
# Verbose output shows exactly where SSH fails ssh -vvv user@hostname 2>&1 | head -50
CORS: The Error That Isn't What It Looks Like
CORS (Cross-Origin Resource Sharing) is the most misunderstood error in frontend development. Every developer hitting an API from a browser encounters it eventually, and the confusion is almost universal because the error message points at the wrong place.
What Actually Happens
You have a frontend app at http://localhost:3000 making a fetch request to http://localhost:8000/api/data. In the browser console you see:
Access to fetch at 'http://localhost:8000/api/data' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Why This Policy Exists
JavaScript running on evil.com shouldn't be able to read responses from yourbank.com when you're logged in. CORS is the browser's enforcement that a response can only be read by JavaScript on the same origin that made the request, unless the response explicitly grants access.
What "same origin" means: same protocol (http vs https), same hostname, same port. http://localhost:3000 and http://localhost:8000 are different origins even though they're on the same machine.
How to Fix It
The fix lives on the server, not in the browser. The server must respond with headers that say "I allow requests from this origin." The key header: Access-Control-Allow-Origin.
# FastAPI
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_methods=["*"],
allow_headers=["*"],
)
# Express (Node.js)
const cors = require("cors");
app.use(cors({ origin: "http://localhost:3000" }));
# Django
# settings.py
INSTALLED_APPS += ["corsheaders"]
MIDDLEWARE = ["corsheaders.middleware.CorsMiddleware", ...MIDDLEWARE]
CORS_ALLOWED_ORIGINS = ["http://localhost:3000"]Preflight Requests
Before making certain requests (those with custom headers, or using methods other than GET/POST, or sending credentials), the browser first sends an OPTIONS request to the same endpoint to ask "are you going to allow this?" The server must respond to this OPTIONS request with the appropriate CORS headers, or the actual request never gets made. Most CORS middleware handles this automatically.
curl, Python's requests, Postman, your backend server code — none of these enforce CORS. If a request works in Postman but fails in the browser with a CORS error, that's not a network problem. It's exactly correct behavior. Fix the server headers.TLS/HTTPS: When the Certificate Is the Problem
HTTPS encrypts traffic using TLS (Transport Layer Security). When TLS fails, you can't connect — but the error messages are specific and diagnosable.
A TLS certificate is issued by a Certificate Authority (CA). Your browser (or OS) has a built-in list of trusted root CAs. When you connect to a server, it presents a certificate. Your client verifies: is this certificate signed by a CA I trust? Is it valid for this hostname? Has it expired? If any check fails, connection refused.
Common TLS Failures
Certificate has expired — the certificate's notAfter date passed. Someone needs to renew it. If you're using Let's Encrypt, auto-renewal may have failed.
Unable to get local issuer certificate — the server presented a certificate but not the full chain up to a trusted root. Intermediate certificates are missing. Server configuration issue.
Certificate is not valid for hostname — the certificate was issued for a different domain. Either connecting to the wrong host, or the certificate needs a Subject Alternative Name (SAN) for this hostname.
Self-signed certificate in certificate chain — the certificate was signed by itself or an internal CA your client doesn't trust. Common in internal development environments.
Inspecting a Certificate
# Show certificate details for a host openssl s_client -connect api.example.com:443 -servername api.example.com # Show certificate expiry specifically echo | openssl s_client -connect api.example.com:443 2>/dev/null | \ openssl x509 -noout -dates # Check a local certificate file openssl x509 -in cert.pem -noout -text
Development Workarounds
Bypassing certificate verification is occasionally necessary for internal development (self-signed certs, internal CAs). Never do this in production code.
curl -k https://internal.mycompany.com # curl: skip verification
mkcert generates locally-trusted certificates:brew install mkcertmkcert -install — install local CA into trust storemkcert localhost 127.0.0.1 ::1 — generate trusted local certThe Debugging Chain: How to Find What's Broken
This is the practical payoff. When something won't connect, walk the chain in order. Don't skip steps.
Step 1: DNS
dig the-hostname-you're-trying-to-reach # Expected: ANSWER SECTION with an IP # Problem: NXDOMAIN or empty answer → DNS failure
Step 2: Reachability (ping)
ping -c 4 the-ip-or-hostname # Expected: responses with low latency # Problem: 100% packet loss, timeout → network/firewall blocking ICMP # Note: some hosts block ping; failure here doesn't prove unreachable
Step 3: Port Reachability
nc -zv -w5 hostname 443 # Expected: "Connection to hostname 443 port [tcp/https] succeeded!" # Problem: timeout → firewall dropping; "Connection refused" → port closed
Step 4: What's Listening
# On the server, is anything listening on the expected port? ss -tlnp | grep 8080 # Expected: a line showing a process bound to that port # Problem: nothing → process isn't running or bound to wrong interface
Step 5: Firewall Rules
# On Linux server sudo iptables -L -n -v | grep 8080 # For cloud providers: check security groups / firewall rules in console # For corporate networks: this is where you escalate to IT
Step 6: TLS (for HTTPS)
openssl s_client -connect hostname:443 -servername hostname # Look for: "Verify return code: 0 (ok)" # Problems: cert errors appear here
Step 7: Application Layer
# Test the full HTTP response curl -v https://hostname/path # -v shows every step: DNS, connection, TLS, headers, body # This is the most information-dense single command for HTTP debugging
curl -v is the most useful debugging tool for HTTP. It shows you the DNS resolution, the connection, the TLS handshake, the request headers, the response code, the response headers, and the body — in order. When something fails, it shows you exactly where in that sequence the failure occurred.
api.myapp.com:8080. You spend 30 minutes redeploying your app, restarting services, changing your code, trying a different HTTP client library, and asking in Slack.Good: you can't connect to
api.myapp.com:8080. You run dig api.myapp.com — resolves correctly. You run nc -zv api.myapp.com 8080 — times out. Not refused but times out — firewall. You check the cloud security group — port 8080 is not in the inbound rules. You add the rule. Connection works. Total time: four minutes.
Common Scenarios Decoded
The patterns that come up again and again, with their root causes and fixes.
"It works on my machine but not in production"
Almost always one of: environment variables missing or different, a service bound to 127.0.0.1 instead of 0.0.0.0, firewall rules blocking a port that's open on localhost, TLS certificate required in prod but not local, or a dependency available locally that's missing in the deployed environment.
Debug by comparing: what's different between environments? Network interfaces, firewall rules, environment variables, service configuration. curl -v from inside the production environment (via SSH) isolates whether it's a network issue or an application issue.
"ECONNREFUSED" / "Connection refused"
Something actively refused the connection. This means the network packet got to the destination but nothing was listening on that port to accept it. Either the service isn't running, or it's listening on a different port, or it's bound to 127.0.0.1 and you're connecting from somewhere else. Check with ss -tlnp on the server.
"Connection timed out" / request just hangs
A firewall is silently dropping the packet. No response means a black hole in the network. Walk the chain: can you reach the host at all (ping)? Can you reach the port (nc -zv)? If both fail, it's a network or firewall problem above the application. If nc hangs, check cloud security groups.
Database not accessible from application
Classic scenario: database is running, application can't connect. First check: what address is the database listening on? ss -tlnp | grep 5432. If it shows 127.0.0.1:5432, it's only accessible locally. Check the database configuration — PostgreSQL has listen_addresses in postgresql.conf and a separate pg_hba.conf controlling who can authenticate. MySQL has bind-address in my.cnf. Then check firewall rules.
HTTPS certificate error in development
Either you're hitting a self-signed cert or the browser is redirecting to HTTPS somewhere. Options: use http:// consistently in local development, use mkcert to generate a locally-trusted certificate, or accept the browser security warning.
API returns 200 from curl but fails from browser
Correct and expected behavior. The server isn't sending CORS headers. Fix is on the server: add Access-Control-Allow-Origin headers. Not a network issue. Not something to fix in the browser or in client code.
Where This Breaks Down
The mental model in this guide covers the vast majority of connection problems you'll encounter. But there are situations where it simplifies too much.
Corporate and enterprise networks add complexity that goes beyond individual diagnosis. Deep packet inspection, proxy servers, traffic interception, and policy-based routing can produce behavior that doesn't follow the standard rules. On a corporate network, "connection timed out" might mean the proxy is blocking you, not a firewall at the destination.
IPv6 confusion is increasingly common. A hostname might have both A records (IPv4) and AAAA records (IPv6). Your client might try IPv6 first, fail, and fall back to IPv4 — or not fall back. When curl hostname fails but curl -4 hostname works, you have an IPv6 routing problem.
NAT traversal — making two machines behind NAT firewalls talk directly to each other — is genuinely hard and not covered here. WebRTC, VPN protocols, and peer-to-peer applications deal with this.
Load balancers, CDNs, and proxies add layers that can absorb, transform, or reject connections in ways that don't match the direct-connection model. A curl that goes directly to an IP gives different results than one that goes through Cloudflare.
DNS propagation timers are real and annoying. When you change a DNS record, the old value is cached at resolvers worldwide for the duration of the TTL. There's no way to force all resolvers to flush their caches. If you need fast propagation, set low TTLs before making the change.
Quick Reference
Everything you need in one place.
Check DNS
dig hostname # full lookup dig hostname @8.8.8.8 # bypass local cache dig hostname +short # just the IP cat /etc/hosts # local overrides
Check Port Reachability
nc -zv -w5 hostname port # port open check (timeout=blocked, refused=closed) curl -v http://hostname:port # full HTTP debug
Check What's Listening
ss -tlnp # all listening ports + processes (Linux) lsof -i -P -n # same on Mac ss -tlnp | grep 8080 # specific port
Check Firewall (Linux)
sudo ufw status sudo iptables -L -n -v
SSH Tunnels
# Local: access remote port locally ssh -L local_port:remote_host:remote_port user@server # Remote: expose local port on server ssh -R server_port:localhost:local_port user@server # Jump host ssh -J user@bastion user@target
TLS Check
openssl s_client -connect hostname:443 -servername hostname echo | openssl s_client -connect hostname:443 2>/dev/null | openssl x509 -noout -dates
The Debugging Chain
1. dig hostname → DNS resolving? 2. ping hostname → Reachable at all? 3. nc -zv hostname port → Port reachable? 4. ss -tlnp | grep port → Something listening? (on server) 5. check firewall rules → Blocked by filter? 6. openssl s_client ... → TLS valid? (HTTPS only) 7. curl -v https://hostname/path → Full HTTP/application debug
CORS Mental Model
Error in browser console? → Server needs CORS headers Works in curl / Postman? → Confirms it's CORS, not network Fix is always server-side → Add Access-Control-Allow-Origin header Never fix by disabling browser security
Binding Addresses
127.0.0.1 → local only, not reachable from other machines/containers 0.0.0.0 → all interfaces, reachable from anywhere that can reach the machine ::1 → IPv6 loopback (same as 127.0.0.1) ::: → IPv6 all interfaces (same as 0.0.0.0)
What You Can Do Now
Find something that's currently not connecting — or pick a service you run locally — and walk the chain deliberately rather than instinctively.
Run curl -v against it. Not curl, but curl -v. Watch every line of output. See where it stops. That line tells you exactly which link in the chain is broken.
If you're not currently fighting a connection problem, open a terminal and run ss -tlnp (or lsof -i -P -n on Mac) right now. Look at what's actually listening, on which ports, bound to which addresses. You'll almost certainly find something you didn't know was there, and you'll have a concrete understanding of your machine's network posture that you didn't have five minutes ago.
The chain is always there. Walking it is the whole skill.
Continue Learning
Related field guides that build on what you've learned here:
The Terminal — the command line where all these diagnostic tools live.
Security Basics — SSH keys, API keys, and the security habits that keep you safe.
APIs — what APIs are, how they work, and how to build things that talk to external services.
Debugging — a systematic method for finding bugs when something goes wrong.
Coding Basics — variables, functions, and the judgment to know when code works.