History of HTTP: from CERN to HTTP/3
How a one-line GET request became HTTP/3, with the real RFC dates and authors, and where each decision still shows on your own server today.
Who invented HTTP?
The history of HTTP (Hypertext Transfer Protocol) starts with Tim Berners-Lee at CERN. His proposal for a hypertext system is dated March 1989. The first version of the protocol was a single line: the word GET, a space, and the address of a document. Every version since then is written down in an RFC (Request for Comments). RFCs are the numbered documents of the IETF (Internet Engineering Task Force), and each one left something you can still see on a server you run today.
This post follows that chain one document at a time. For each step it names the decision and shows where you meet it now. That might be a header in curl -v, a line in your nginx access log, or a port in your firewall. If you want the protocol itself explained first, start with what HTTP is and how a request and response work.
1989: a proposal about hypertext
The W3C keeps the original text of "Information Management: A Proposal" by Tim Berners-Lee, CERN. The page is dated March 1989. The same text was redistributed in May 1990, unchanged apart from the date. The proposal argues for linked documents that people could share and browse. It does not define HTTP. The protocol came later, out of the work of building the system.
CERN's "The World Wide Web project" page is still online. It describes the result in one line: "a wide-area hypermedia information retrieval initiative aiming to give universal access". Something had to carry those documents between machines, and the first protocol for that job was very small.
1991: the one-line protocol called HTTP/0.9
The W3C page titled "The Original HTTP as defined in 1991" is signed "Tim BL". It describes the whole protocol in a few paragraphs. The client opens a connection and sends the word GET, a space, and the document address. The server answers with hypertext markup. Then the server closes the connection, and that close is the signal that the document is complete.
The response was the document itself, with no headers. The page says this original form "is known as HTTP 0.9". The version number was given to it after later versions existed, so that software could tell the old form apart from the new ones.
A separate W3C draft, "HTTP: A protocol for networked information", sets out the model that later versions kept. HTTP runs over a TCP/IP connection (Transmission Control Protocol over Internet Protocol). It is basically stateless, which means each request stands on its own. One transaction is connect, request, response, close.
Does curl still accept an HTTP/0.9 response?
Not by default. A response with no headers looks the same as the output of a service that does not speak HTTP at all. The curl man page says this directly: an HTTP/0.9 response has no headers, "and therefore you can also connect with this to non-HTTP servers". For that reason curl turned HTTP/0.9 off by default in version 7.66.0, released on September 11, 2019. The man page now says "HTTP/0.9 is disabled by default". To accept such a response anyway, pass the flag:
curl --http0.9 http://203.0.113.10:8080/In programs that use libcurl, the same switch is the CURLOPT_HTTP09_ALLOWED option. It was added in 7.64.0, when libcurl still allowed HTTP/0.9 by default. Since 7.66.0, libcurl requires this option set to 1 to allow HTTP/0.9 responses. If curl refuses a reply from an old device or a small test server, check whether the server sends headers at all. A reply with no status line and no headers is HTTP/0.9, and curl needs the flag to read it.
1996: RFC 1945 writes down HTTP/1.0
HTTP was in use for years before it had a formal document. RFC 1945 describes HTTP/1.0. It was published in May 1996 by T. Berners-Lee, R. Fielding and H. Frystyk. Its abstract says HTTP "has been in use by the World-Wide Web global information initiative since 1990". Its category is Informational, and the memo says it "provides information for the Internet community". It did not set a standard.
HTTP/1.0 gave requests and responses headers, which the 1991 form did not have. RFC 1945 kept the old form as a compatibility case. Section 4.1 calls it Simple-Request and Simple-Response, which "do not allow the use of any header information and are limited to a single request method (GET)".
One thing did not change. Section 1.3 says that, "except for experimental applications", the client opens a connection before each request and the server closes it after sending the response. A page with ten images meant eleven separate TCP connections. That cost is one of the things the next version set out to remove.
1997 and 1999: HTTP/1.1 and the Host header
RFC 2068 defined HTTP/1.1 in January 1997 as a Proposed Standard. Its authors were R. Fielding, J. Gettys, J. Mogul, H. Frystyk and T. Berners-Lee. RFC 2616 replaced it in June 1999 and added two authors, L. Masinter and P. Leach. RFC 2616 stayed the reference for HTTP/1.1 until RFCs 7230 to 7235 obsoleted it.
Why HTTP/1.1 made the Host header mandatory
For anyone who runs a server, the most important rule in HTTP/1.1 is short. RFC 2068 says the Host request header "MUST accompany all HTTP/1.1 requests". It also says: "Servers MUST report a 400 (Bad Request) error if an HTTP/1.1 request does not include a Host request-header." The section that explains the change has a plain title: "Changes to Simplify Multi-homed Web Servers and Conserve IP Addresses".
Here is the problem it solved. A request line usually held only the path, such as /index.html. Without a Host header, a server with one IP address had no way to know which website the client wanted. So each site needed its own IP address. The Host header puts the site name inside the request, so one address can serve many sites. This is name-based virtual hosting. It is the saving that the RFC section title points to, and it is what made cheap shared hosting on one machine possible.
The rule is still current. RFC 9112 is the HTTP/1.1 standard published in 2022 by R. Fielding, M. Nottingham and J. Reschke. It says: "A server MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message that lacks a Host header field". Apache's documentation describes name-based virtual hosting in the same terms. The server relies on the client to report the hostname, so "many different hosts can share the same IP address".
You can watch this on your own server. Send a request to the IP address, and set the Host header yourself:
curl -sv -o /dev/null -H 'Host: example.com' http://203.0.113.10/The -v output prints the request lines that start with >. You should see > GET / HTTP/1.1 and then > Host: example.com. Change the Host value and the same IP address can return a different site. In nginx, the server_name directive decides which server block answers. nginx tries an exact name first, then the longest wildcard name that starts with an asterisk, then the longest wildcard name that ends with an asterisk, and then the first matching regular expression. An empty server_name "" lets a block handle requests that have no Host header at all. For how a web server uses these blocks, see what a web server does with each request.
Why HTTP/1.1 keeps the connection open
RFC 2068 also changed the connection model. Section 8.1.2 says "persistent connections are the default behavior of any HTTP connection". The client can send the next request on the same TCP connection. The cost of opening a connection is paid once per connection, and no longer once per image.
Some HTTP/1.0 software had already tried this with an experimental Keep-Alive token, which the client had to ask for. RFC 2068 section 19.7.1 calls those experimental implementations "faulty". The problem was proxies. An old proxy that did not understand the Connection header could pass Keep-Alive along to the server and then wait for a close that never came. The RFC concludes that "HTTP/1.0 clients must be prevented from using Keep-Alive when talking to proxies". HTTP/1.1 took a different route. Persistence became the default, and a client that wants the old behavior sends Connection: close. RFC 9112 keeps the same rule: "HTTP/1.1 defaults to the use of persistent connections".
The request line in your nginx access log
Open any nginx access log that uses the predefined combined format and you are reading HTTP request syntax. The format includes "$request", and nginx documents $request as the "full original request line". RFC 9112 gives the grammar of that line:
request-line = method SP request-target SP HTTP-versionSo a typical log entry looks like this, with the request line in quotes:
203.0.113.7 - - [02/Nov/2026:10:15:32 +0000] "GET /index.html HTTP/1.1" 200 612 "-" "Mozilla/5.0"The version at the end of that quoted string exists because there was more than one version to tell apart. The 1991 request had only the method and the address. nginx also exposes the version alone as $server_protocol. Its documentation lists the usual values as "HTTP/1.0", "HTTP/1.1", "HTTP/2.0", or "HTTP/3.0".
2000: HTTPS arrives as a wrapper around HTTP
HTTPS did not change HTTP messages. It placed them inside an encrypted channel. RFC 2818, "HTTP Over TLS", describes exactly that. It was written by E. Rescorla and published in May 2000 as an Informational RFC. TLS (Transport Layer Security) runs first. The HTTP client also acts as the TLS client, and it sends its first HTTP request only after the TLS handshake is complete. The RFC also sets the port: "When HTTP/TLS is being run over a TCP/IP connection, the default port is 443."
That design explains what you see on a server. HTTPS is a separate listener on port 443 with a certificate, and the requests inside it look the same as plain HTTP requests. Another path was also written down: RFC 2817, "Upgrading to TLS Within HTTP/1.1", which updated RFC 2616. Today RFC 2818 is obsoleted by RFC 9110, the HTTP Semantics standard published in June 2022 as STD 97. For the practical side of serving both, read how HTTP and HTTPS differ on your own server.
2015: SPDY at Google becomes HTTP/2
Google's SPDY whitepaper describes an experimental application-layer protocol designed for low latency. Its stated goals include "a 50% reduction in page load time" and allowing "many concurrent HTTP requests to run across a single TCP session". It also aimed to compress headers. Google built a SPDY-enabled Chrome and an open-source web server to test the idea.
The IETF published HTTP/2 as RFC 7540 in May 2015. It is a Proposed Standard by M. Belshe, R. Peon and M. Thomson. Its acknowledgements credit "SPDY contributors". On February 9, 2015, the Chromium blog published a post titled "Hello HTTP/2, Goodbye SPDY". RFC 7540 states that HTTP/2 is an alternative to, but does not obsolete, the HTTP/1.1 message syntax. That is why one server can speak both.
RFC 7540 also tied HTTP/2 closely to TLS. It required TLS 1.2 or higher for HTTP/2 over TLS, together with SNI (Server Name Indication). The client and server agree on HTTP/2 through ALPN (Application-Layer Protocol Negotiation), using the identifier h2. A separate identifier, h2c, names HTTP/2 over cleartext TCP, and it must not be used when negotiating over TLS. In 2022, RFC 9113 by M. Thomson and C. Benfield obsoleted RFC 7540. For what this means in a server config, see the differences between HTTP/1.1, HTTP/2 and HTTP/3 on a server.
2021 and 2022: QUIC and HTTP/3 move to UDP
HTTP/2 put many requests on one TCP connection, and that created a new problem. RFC 9114 explains it: because HTTP/2's parallel streams are "not visible to TCP's loss recovery mechanisms", a lost or reordered packet "causes all active transactions to experience a stall". One lost packet holds up every request on the connection. The fix needed a new transport.
The Chromium project's QUIC page gives the timeline. Google started working on QUIC in 2012. Chrome began small experiments with the original versions in 2013. Google brought QUIC to the IETF in 2015. Chrome began wide experiments with IETF QUIC in 2020. In 2021, the IETF published QUIC as RFC 9000. The page describes QUIC as "a new multiplexed transport built on top of UDP" (User Datagram Protocol).
RFC 9000 was published in May 2021. Its two editors are J. Iyengar of Fastly and M. Thomson of Mozilla. It defines version 1 of QUIC, and its packets "are carried in UDP datagrams". RFC 9114 followed in June 2022, edited by M. Bishop of Akamai. It defines HTTP/3, which "supports the same semantics over a new transport protocol: QUIC". Some HTTP/2 features are handled by QUIC itself, and the rest are rebuilt on top of QUIC. HTTP/3 uses the ALPN token h3.
The way a client finds HTTP/3 matters on a VPS. A server advertises an HTTP/3 endpoint with an Alt-Svc (alternative service) record, sent over an existing HTTP connection. A client that receives it may then try a QUIC connection to that host and port. If QUIC fails, for example because UDP is blocked, RFC 9114 says clients "SHOULD attempt to use TCP-based versions of HTTP in this case." So a firewall that opens only TCP 443 does not break the site. Clients simply keep using HTTP/2 or HTTP/1.1. To actually serve HTTP/3, the firewall must also allow UDP on the port the server advertises.
What each decision left on your server
- The Host header from RFC 2068 is why one IP address can serve many sites, and why nginx
server_nameand Apache name-based virtual hosts work at all. - Persistent connections as the default are why one TCP connection carries many requests without any extra header.
- The request line is the quoted string in every
combinednginx access log entry. - HTTPS as a TLS wrapper is why port 443 is a separate listener with its own certificate.
- SPDY's work became
h2in the TLS handshake, and QUIC is why HTTP/3 needs UDP open in your firewall.
HTTP sits on top of other systems with their own histories. Most requests start with a name lookup, which is covered in the history of DNS. The protocols that moved files and remote shells before the web are in the history of file transfer protocols and the history of SSH, from Telnet to OpenSSH.
FAQ
Who invented HTTP?
Tim Berners-Lee. He wrote the CERN proposal "Information Management: A Proposal", dated March 1989, and the W3C page "The Original HTTP as defined in 1991" is signed "Tim BL". He is also an author of RFC 1945 (HTTP/1.0, May 1996), RFC 2068 (HTTP/1.1, January 1997) and RFC 2616 (June 1999). Later versions, such as HTTP/2 and HTTP/3, have other authors.
Why does HTTP/1.1 require a Host header?
Without it, a server with one IP address cannot tell which website a request is for, so each site needed its own address. RFC 2068 made the Host header mandatory in HTTP/1.1, in a section about conserving IP addresses. A server must answer an HTTP/1.1 request that has no Host header with 400 Bad Request. This rule is what makes name-based virtual hosting possible.
Why does curl refuse a response that has no headers?
The server is answering in HTTP/0.9, which sends the document with no status line and no headers. curl disabled HTTP/0.9 by default in version 7.66.0, released on September 11, 2019. To accept such a response, pass --http0.9 on the command line, or set CURLOPT_HTTP09_ALLOWED to 1 in libcurl.
Why does HTTP/3 use UDP instead of TCP?
HTTP/3 runs over QUIC, and QUIC carries its packets in UDP datagrams (RFC 9000). With HTTP/2 over TCP, one lost or reordered packet stalls every active request on the connection, because TCP's loss recovery cannot see HTTP/2's separate streams. On a server, HTTP/3 works only if your firewall allows UDP on the advertised port. When UDP is blocked, clients fall back to HTTP/2 or HTTP/1.1 over TCP.