Learn how HTTP requests and responses work, what status codes mean for operations, how HTTPS and TLS keep traffic private, how certificates and the chain of trust work, and how to get free certificates with Let's Encrypt.

Bishal Biswas
Self Learner
The language of the web
You now know how machines find each other (IP addresses and ports) and how names become addresses (DNS). This lesson is about what they actually say to each other.
Almost everything you deploy speaks HTTP, the HyperText Transfer Protocol: websites, APIs, health checks, webhooks, container registries, even the tools that talk to Kubernetes. Being able to read HTTP traffic is one of the most useful debugging skills in DevOps.
Requests and responses
HTTP is a simple conversation. A client (a browser, curl, your app) sends a request, and a server sends back a response. One question, one answer.
Here is a real request, as it travels over the network:
GET /recipes/42 HTTP/1.1
Host: shop.example.com
User-Agent: curl/8.5.0
Accept: application/json
- The first line has the method (
GET), the path (/recipes/42) and the protocol version. - Then come headers:
Name: valuelines with extra information. - Requests that send data, such as
POST, add a blank line and a body after the headers.
And the server’s response:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 61
Cache-Control: max-age=60
{"id":42,"title":"Masala Dosa","minutes":45,"vegetarian":true}
- The first line has the status code (
200) and a short reason (OK). - Then headers, a blank line and the body: the actual content.
Methods
| Method | Meaning | Example |
|---|---|---|
GET |
Read something | Load a page, fetch a recipe |
POST |
Create something, or trigger an action | Submit a form, place an order |
PUT |
Replace something completely | Save an edited recipe |
PATCH |
Change part of something | Update just the title |
DELETE |
Remove something | Delete a comment |
HEAD |
Like GET, but headers only, no body |
Quick checks: “does this exist, how big is it?” |
Status codes
Status codes are grouped by their first digit. As an operator, these are the ones to know well:
| Code | Meaning | What it tells you |
|---|---|---|
| 2xx | Success | |
| 200 OK | It worked | |
| 201 Created | A new thing was created | Typical reply to a POST |
| 204 No Content | It worked; nothing to send back | |
| 3xx | Go somewhere else | |
| 301 / 308 | Moved permanently | Old URL, or http:// redirecting to https:// |
| 302 / 307 | Moved temporarily | Login redirects |
| 304 Not Modified | Your cached copy is still good | Caching is working |
| 4xx | The client did something wrong | |
| 400 Bad Request | The request made no sense | Malformed JSON, missing fields |
| 401 Unauthorized | Who are you? Log in first | Missing or expired token |
| 403 Forbidden | I know who you are, but no | Wrong permissions |
| 404 Not Found | Nothing at that path | Typo, or a route was not deployed |
| 429 Too Many Requests | Slow down | Rate limiting kicked in |
| 5xx | The server failed | |
| 500 Internal Server Error | The app crashed while handling it | Check the app’s logs |
| 502 Bad Gateway | A proxy could not get a valid answer from the app behind it | App is down or crashing |
| 503 Service Unavailable | Temporarily unable to serve | Overloaded, or in maintenance |
| 504 Gateway Timeout | A proxy waited too long for the app | App is too slow, or stuck |
The simple rule: 4xx means the caller should change something; 5xx means you should. A sudden rise in 5xx errors after a deployment is one of the clearest signs a release went wrong, and a key thing to watch in Monitoring, Logging and Alerting. You will see where 502, 503 and 504 come from in Reverse Proxies and Load Balancers.
Headers worth knowing
| Header | What it does |
|---|---|
Host |
Which website the request is for. One server can host many sites on the same IP; this header tells it which one. |
Content-Type |
The format of the body: text/html, application/json and so on. |
Cache-Control |
Whether, and for how long, browsers and CDNs may cache the response. |
Authorization |
Credentials, such as a token: Bearer eyJhbGci... |
Cookie / Set-Cookie |
Small pieces of data the browser stores and sends back, such as a login session. |
Location |
Where to go next, sent with 3xx redirects. |
X-Forwarded-For |
The original client’s IP, added by proxies. |
HTTP versions
- HTTP/1.1 (1997) is the plain-text version shown above, still everywhere.
- HTTP/2 (2015) sends many requests at once over one connection, in a compact binary format. Much faster for pages with lots of files.
- HTTP/3 (2022) runs over QUIC, which is built on UDP instead of TCP, so connections start faster and cope better with patchy mobile networks.
The meaning (methods, status codes, headers) is the same in all three. Your app usually does not need to care which one the browser used.
Talking HTTP with curl
curl lets you send any HTTP request from the terminal. It is the DevOps engineer’s Swiss Army knife:
curl https://example.com # GET a page and print the body
curl -I https://example.com # HEAD: show only the status and headers
curl -v https://example.com # verbose: show the whole conversation, TLS included
curl -L http://github.com # follow redirects
curl -X POST https://api.example.com/orders \
-H "Content-Type: application/json" \
-d '{"recipeId": 42}' # send JSON
curl -o /dev/null -s -w "%{http_code} %{time_total}s\n" https://example.com # status and timing only
That last line is a handy one-line health check: it prints only the status code and how long the request took.
Why HTTP is not enough: HTTPS
Plain HTTP sends everything as readable text. Anyone between you and the server, on the same café Wi-Fi, at your internet provider, on any network along the way, could:
- read it: passwords, cookies, personal data;
- change it: inject adverts or malware into a page;
- pretend to be the server you meant to reach.
HTTPS is HTTP sent through an encrypted tunnel built with TLS (Transport Layer Security). It gives you three guarantees:
| Guarantee | Meaning |
|---|---|
| Privacy | Nobody in between can read the traffic |
| Integrity | Nobody in between can change it without being detected |
| Authentication | You really are talking to the site you asked for |
Today HTTPS is simply the default. Browsers mark plain HTTP pages as “Not secure”, many browser features only work over HTTPS, and certificates are free. Every site you deploy should use it.
Note: You will still hear people say SSL and “SSL certificate”. SSL was TLS’s older predecessor and is long retired, along with TLS 1.0 and 1.1. Today you should use TLS 1.2 or TLS 1.3. The name “SSL certificate” has just stuck.
How TLS works, simply
When your browser connects to https://example.com, the two sides do a quick TLS handshake before any HTTP is sent:
- Hello. The browser says which TLS versions and encryption methods it supports, and which site it wants (this name is called SNI, Server Name Indication).
- Certificate. The server picks the best options and sends its certificate: a file that says “I am
example.com”, signed by someone the browser trusts. - Check. The browser checks the certificate: is it for this exact name? Is it still valid? Is it signed by a trusted authority?
- Shared key. Both sides use clever mathematics to agree on a secret session key, without ever sending the key itself over the network.
- Encrypted HTTP. All HTTP traffic from now on is encrypted with that key.
TLS 1.3 does all of this in a single round trip, so HTTPS adds very little delay.
Certificates and the chain of trust
A TLS certificate contains:
- the domain names it is valid for, such as
example.comandwww.example.com(or a wildcard,*.example.com); - the server’s public key;
- valid from and valid to dates;
- who issued it, and their digital signature.
Why should your browser believe a certificate? Because it was signed by a Certificate Authority (CA), an organisation whose job is to check that whoever asks for a certificate really controls the domain.
Your operating system and browser come with a built-in list of trusted root CAs. Root CAs rarely sign server certificates directly. Instead there is a chain:
Root CA (built into your OS / browser: trusted)
└── signs ──> Intermediate CA
└── signs ──> example.com certificate (sent by the server)
The browser follows the chain from the site’s certificate up to a root it trusts. If any link is missing or wrong, the connection is refused.
A self-signed certificate is one that signed itself, with no CA involved. It still encrypts traffic, but nobody can check that it is genuine, so browsers show a big warning. It is fine for testing on your own machine and never for a public site.
Free certificates with Let’s Encrypt
Certificates used to cost money and need manual renewal every year. Let’s Encrypt, a free, non-profit CA launched in 2015, changed that. It issues certificates automatically, using a protocol called ACME:
- A tool on your server asks Let’s Encrypt for a certificate for
example.com. - Let’s Encrypt replies: “prove you control it”, for example by placing a special file at
http://example.com/.well-known/acme-challenge/...or by adding a DNS TXT record. - Let’s Encrypt checks, then issues the certificate.
- The tool installs it and renews it automatically before it expires.
On an Ubuntu server running nginx, the most common tool is certbot:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
Certbot gets the certificate, updates the nginx configuration and installs a timer that renews it automatically. Many modern tools do all this with no extra steps at all: the Caddy web server, Kubernetes’ cert-manager, and most hosting platforms and cloud load balancers.
Note: Certificate lifetimes are getting shorter. Let’s Encrypt certificates last 90 days, and in 2025 the browser and CA industry agreed to cut the maximum lifetime of any public certificate from 398 days to 200 days in March 2026, then step by step down to 47 days by 2029. Manual renewal is no longer realistic: automate it.
HTTPS in practice
A few habits for every site you run:
- Redirect HTTP to HTTPS. Keep port 80 open only to send a
301redirect to thehttps://address (and for ACME challenges). - Send HSTS. The
Strict-Transport-Securityheader tells browsers “only ever use HTTPS for this site”, so they never even try plain HTTP again. - Monitor expiry. Even with automation, alert if a certificate has fewer than 14 days left. Expired certificates are one of the most common causes of very public outages.
- Terminate TLS in one place. Usually a reverse proxy or load balancer handles HTTPS, and talks to your app behind it over the private network. See Reverse Proxies and Load Balancers.
To check a certificate by hand:
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=SSL Corporation, CN=Cloudflare TLS Issuing ECC CA 3
notBefore=Sep 26 22:49:11 2026 GMT
notAfter=Dec 25 22:56:35 2026 GMT
Common HTTPS errors
| Browser or curl error | Meaning | Fix |
|---|---|---|
| Certificate expired | notAfter is in the past |
Renew it, then fix the automation that should have |
| Name mismatch | The certificate does not list this domain | Include every name (www too) when requesting it |
| Unknown issuer / incomplete chain | The server sent its certificate without the intermediate | Serve the full chain file (certbot’s fullchain.pem) |
| Self-signed certificate | No trusted CA signed it | Use a real certificate from a CA such as Let’s Encrypt |
| Mixed content | An HTTPS page loads images or scripts over http:// |
Load every resource over HTTPS |
Checking any site’s HTTPS
The code panel has a Node.js script that connects to a site the way a browser does. It reports the TLS version, whether the certificate is trusted, who it was issued to and by, and how many days are left, and then shows the HTTP response headers:
node tls-check.mjs
TLS connection to github.com:443
protocol TLSv1.3
cipher TLS_AES_128_GCM_SHA256
trusted yes
Certificate
issued to github.com
also valid github.com, www.github.com
issued by Sectigo Limited (Sectigo Public Server Authentication CA DV E36)
expires 2026-11-29 (35 days left)
HTTP response
status 200 OK
content-type text/html; charset=utf-8
cache-control max-age=0, private, must-revalidate
strict-transport-security max-age=31536000; includeSubdomains; preload
server github.com
Notice GitHub’s HSTS header, asking browsers to use HTTPS for a full year. To see a broken certificate safely, try the test site expired.badssl.com:
node tls-check.mjs expired.badssl.com
trusted NO (CERT_HAS_EXPIRED)
...
expires 2015-04-12 (EXPIRED)
Browsers and fetch() would refuse this connection, so stopping here.
Common misunderstandings
“HTTPS makes my site secure”
HTTPS protects data while it travels. It does nothing about bugs in your app, weak passwords or an unpatched server. It is necessary, but it is only one layer.
“A padlock means the site is trustworthy”
The padlock only means the connection is encrypted and the certificate matches the name. A scam site can have a perfectly valid certificate for its own scam domain.
“Internal traffic does not need encryption”
Traffic inside a private network used to be trusted by default. Modern security practice, often called zero trust, encrypts internal traffic too, especially between services that handle personal data. Start with HTTPS at the edge, and add internal encryption as your system grows.
Quick recap
- HTTP is a request/response conversation: method, path, headers, body; status code, headers, body.
- 4xx codes mean the client should change something; 5xx codes mean the server failed.
curl -I,curl -vandcurl -ware your everyday HTTP tools.- HTTPS is HTTP inside TLS, which gives privacy, integrity and authentication.
- Certificates prove a server’s identity through a chain of trust up to a root CA.
- Let’s Encrypt and ACME make certificates free and automatic. Automate renewal, and monitor expiry.
Next, you will put a single front door in front of your apps in Reverse Proxies and Load Balancers.
// tls-check.mjs: inspect a site's HTTPS connection and certificate, then its HTTP response.
// Run it with: node tls-check.mjs (checks github.com)
// or: node tls-check.mjs example.com
import tls from 'node:tls';
const host = process.argv[2] ?? 'github.com';
const DAY = 24 * 60 * 60 * 1000;
// 1. The TLS handshake: agree on encryption and check the certificate
// rejectUnauthorized: false lets us inspect a bad certificate instead of failing at once
const socket = tls.connect({ host, port: 443, servername: host, rejectUnauthorized: false });
await new Promise((resolve, reject) => socket.once('secureConnect', resolve).once('error', reject));
const cert = socket.getPeerCertificate();
const daysLeft = Math.floor((new Date(cert.valid_to) - Date.now()) / DAY);
const names = (cert.subjectaltname ?? '').replaceAll('DNS:', '').split(', ');
console.log(`TLS connection to ${host}:443`);
console.log(` protocol ${socket.getProtocol()}`);
console.log(` cipher ${socket.getCipher().name}`);
console.log(` trusted ${socket.authorized ? 'yes' : `NO (${socket.authorizationError})`}`);
console.log('Certificate');
console.log(` issued to ${cert.subject?.CN}`);
console.log(` also valid ${names.slice(0, 4).join(', ')}${names.length > 4 ? ', ...' : ''}`);
console.log(` issued by ${cert.issuer?.O} (${cert.issuer?.CN})`);
console.log(` expires ${new Date(cert.valid_to).toISOString().slice(0, 10)} (${daysLeft < 0 ? 'EXPIRED' : `${daysLeft} days left`})`);
if (daysLeft >= 0 && daysLeft < 14) console.log(' WARNING: renew this certificate soon!');
socket.end();
if (!socket.authorized) {
console.log('Browsers and fetch() would refuse this connection, so stopping here.');
process.exit(1);
}
// 2. The HTTP request that travels inside that encrypted tunnel
const res = await fetch(`https://${host}/`, { method: 'HEAD', redirect: 'manual' });
console.log('HTTP response');
console.log(` status ${res.status} ${res.statusText}`);
for (const name of ['content-type', 'location', 'cache-control', 'strict-transport-security', 'server']) {
if (res.headers.has(name)) console.log(` ${name.padEnd(26)} ${res.headers.get(name)}`);
}
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