Just the status code
-o /dev/null throws the body away, -w prints the code. Code 000 means
there was no HTTP response at all: the connection never opened or broke. The
same request with -sS instead of -s shows why.
HTTP is the web protocol: a client sends a method, path and headers; the server replies with a code, headers, body.
curl -i http://portal.lab.test/
code, headers and body
curl -I http://portal.lab.test/
headers only (HEAD)
curl -v -o /dev/null http://portal.lab.test/
the whole exchange
curl -s -o /dev/null -w '%{http_code}\n' URL
just the code
curl -H 'Host: docs.lab.test' http://127.0.0.1/
a site by name
curl -X POST -d 'a=1' http://portal.lab.test/f
POST with a body
curl -L http://portal.lab.test/old
follow 301 and 302
sudo tail /var/log/nginx/access.log
which codes nginx returned
HTTP is the protocol a browser, curl or an application uses to talk to a web
server. The client opens a TCP connection — port 80, or 443 for HTTPS — and
sends a request. The server sends back a response. Both are text: a
first line, headers, an empty line, a body.
curl -v shows the whole exchange: lines with > are the request, < the
response, * what curl itself did (address, connection, TLS).
The first line of a request is the method, the path and
the protocol version: GET / HTTP/1.1.
| Method | What the client asks for |
|---|---|
GET |
return the resource |
HEAD |
the same without a body (curl -I) |
POST |
accept data: a form, JSON, a file |
PUT, DELETE |
replace or delete a resource (APIs) |
nginx serves files from disk for GET and HEAD. A POST or DELETE to
such a file gets 405 Not Allowed.
HTTP/1.1 requires exactly one request header — Host, the
site name from the URL. The server uses it to pick a site when several share
one address and port (virtual hosts). nginx rejects
a request without Host with 400 Bad Request.
curl takes Host from the URL, but you can set it by hand:
curl -H 'Host: docs.lab.test' http://127.0.0.1/ connects to 127.0.0.1 and
asks for the site docs.lab.test. If the server has no such name, the
default server answers.
The first line of a response is the version, the status
code and a phrase: HTTP/1.1 200 OK. The first digit is the class:
| Class | Meaning | Common codes |
|---|---|---|
| 1xx | interim response | 101 |
| 2xx | success | 200, 204 |
| 3xx | resource is elsewhere | 301, 302, 304 |
| 4xx | server refused the request | 400, 403, 404, 405, 413 |
| 5xx | failure on the server side | 500, 502, 503, 504 |
Whoever answers sets the code. Behind a reverse proxy
that is either nginx or the application behind it. 502 and 504 come from the
proxy itself: it could not reach the application or gave up waiting. The logs
tell who really answered: nginx access.log records the code of every
request, the application log only the requests that reached it.
The response headers you need most: Content-Type (what the body is),
Content-Length (body size in bytes), Location (where to go on 3xx) and
Server (which server answered).
A 3xx response with a Location header sends the client to another address —
a redirect. 301 Moved Permanently is for good: the
browser remembers it and goes straight to the new address next time, search
engines move the page there. 302 Found is temporary: the old address stays
the main one.
Location is a full address including the path and the parameters after ?.
If it loses them, the user lands on the home page instead of the page they
wanted. curl -L follows redirects by itself.
HTTPS is the same HTTP inside an encrypted TLS connection. Before the first byte of HTTP the client checks the server certificate; after that methods, codes and headers are the same. The default port is 443. Details are in openssl-tls.
-o /dev/null throws the body away, -w prints the code. Code 000 means
there was no HTTP response at all: the connection never opened or broke. The
same request with -sS instead of -s shows why.
The first command changes only the Host header. It does not work for HTTPS:
the name is also needed to check the certificate. --resolve supplies the
address for the name, and curl behaves as if DNS had returned it.
This shows HTTP is plain text: every line ends with \r\n, headers end with
an empty line (the last argument ''). Connection: close asks the server to close the connection after
the response; otherwise nc keeps waiting.
An access.log line holds the client address, time, request line, code and
response size: "POST / HTTP/1.1" 405 166. If the request is in access.log
but not in the application log, nginx produced the response — look for the
reason in error.log.
The request breaks HTTP rules. Common causes: no Host header (a hand-made
request) or plain HTTP sent to an HTTPS port — then the body says
The plain HTTP request was sent to HTTPS port. Check the scheme in the URL:
https:// for port 443.
The server understood the request and refused. nginx has two common causes,
both visible in error.log. (13: Permission denied) — the www-data user
cannot read the file or enter a directory: namei -l shows permissions
along the whole path. directory index of "…" is forbidden — a directory was
requested and it has no index file.
There is no file at the path the server built. nginx writes that path to
error.log: open() "/var/www/portal/nope.html" failed (2: No such file or
directory). Compare it with where the file actually is.
The method does not fit the resource: POST or DELETE to a static file.
Check the method in curl -v and make sure the request goes to the
application, not to a file on disk.
The request body is larger than allowed. nginx's default limit is 1 MB;
error.log says client intended to send too large body. The
client_max_body_size directive raises it.
A proxy response: it could not reach the application (502) or gave up waiting
for an answer (504). How to read the error.log lines is in
nginx.
The server sends you to the address in the Location header. nginx does this
for a directory without a trailing / (/empty → /empty/) and when moving
from HTTP to HTTPS. curl -L follows redirects, curl -I shows where each
one leads.
The reverse of 400 … sent to HTTPS port: https:// went to a port where the
server speaks plain HTTP (https://host:80/). Fix the scheme or the port.
There is no HTTP response: nothing listens on that address and port, or a
firewall drops the connection. This is below HTTP — sudo ss -tlnp on the
server shows who listens on the port. curl: (6) Could not resolve host fails
even earlier: the name did not turn into an address.
Without TLS the request travels as plain text: a form password, cookies and the
Authorization header can be read by anyone on the packet's path —
tcpdump shows them in full. Logins and anything carrying secrets go over
https:// only.
-v, -I, -w, -H.Found an inaccuracy? Report an error in the article