Safeguard
Supply Chain Security•

HTTP/1.1 Explained: RFC 9110 and RFC 9112 Messages, Methods and Security

HTTP/1.1 is a text protocol defined by RFC 9112, with shared semantics in RFC 9110. How messages are framed, and why framing bugs become security bugs.

Safeguard Security Team
40 min read

HTTP/1.1 is the text based version of the Hypertext Transfer Protocol: a client sends a request line, header fields and an optional body over a TCP connection, and the server answers with a status line, header fields and a body. Since June 2022 it has been defined by RFC 9112 (the HTTP/1.1 message syntax), RFC 9110 (the semantics every HTTP version shares) and RFC 9111 (caching), which together replaced RFC 7230 to RFC 7235.

The protocol is old, simple and still everywhere. Browsers can negotiate HTTP/2 or HTTP/3 with servers that support them, but HTTP/1.1 remains the language of reverse proxies talking to application servers, of internal services, health checks, IoT devices, command line tools and plenty of APIs. That matters for security, because almost every serious HTTP/1.1 weakness comes from one thing: two pieces of software reading the same stream of bytes and disagreeing about where one message ends and the next begins.

Key takeaways

  • HTTP/1.1 is now specified across three documents published in June 2022: RFC 9110 "HTTP Semantics" (STD 97), RFC 9111 "HTTP Caching" (STD 98) and RFC 9112 "HTTP/1.1" (STD 99). RFC 9110 obsoletes RFC 2818 and RFC 7230 to 7235 (except 7234), RFC 9111 obsoletes RFC 7234, and RFC 9112 obsoletes the message syntax parts of RFC 7230.
  • A message is a start line, zero or more field lines, an empty line and an optional body. The default port is TCP 80 for http URIs and TCP 443 for https URIs, per RFC 9110 sections 4.2.1 and 4.2.2.
  • The body length is decided by a strict precedence list in RFC 9112 section 6.3. Transfer-Encoding overrides Content-Length, and a message carrying both "ought to be handled as an error". Disagreement over that rule between a front end and a back end is the root of request smuggling.
  • Plain HTTP has no confidentiality or integrity of its own. RFC 9112 says the http scheme "provides no protection against accidental or malicious modification of messages". Use https, then HSTS (RFC 6797, November 2012) so browsers stop trying plain HTTP at all.
  • RFC 9112 tightened parsing on purpose: bare CR characters are banned outside content, whitespace between a field name and the colon must be rejected with 400, obsolete line folding must be rejected or replaced, and a request with both Content-Length and Transfer-Encoding forces the server to close the connection after responding.
  • Defender checks are short: curl -I, curl -v --http1.1, curl -w '%{http_version}', nginx -T and apachectl configtest, run only against systems you own or are authorised to test.

What HTTP/1.1 is for: the layer and the standards

HTTP is an application layer protocol. RFC 9110 calls it "a stateless application-level protocol for distributed, collaborative, hypertext information systems".

HTTP/1.1 runs over a reliable, ordered byte stream. RFC 9110 defines the http URI scheme in terms of "an HTTP origin server listening for TCP connections on a given port", and the https scheme as the same thing plus a TLS connection "secured for HTTP communication", which RFC 9110 defines as server authentication plus confidentiality and integrity protection. If you want a refresher on what the transport underneath does and does not hide, the post on what an eavesdropper can see and change in TCP and UDP covers it, and What is TLS covers the encryption layer.

What the June 2022 RFCs replaced

Before 2022, RFC 7230 to RFC 7235 mixed version independent ideas (what GET means, what a 404 is, how caching works) with HTTP/1.1 specific syntax. Once HTTP/2 arrived with binary framing, that mixing became awkward, because HTTP/2 shared the semantics but not the syntax.

The IETF split the documents along that line:

DocumentTitleSTDPublishedObsoletes
RFC 9110HTTP Semantics97June 2022RFC 2818, 7230, 7231, 7232, 7233, 7235, 7538, 7615, 7694; updates RFC 3864
RFC 9111HTTP Caching98June 2022RFC 7234
RFC 9112HTTP/1.199June 2022Portions of RFC 7230
RFC 9113HTTP/2n/aJune 2022(HTTP/2 framing, referenced by RFC 9110)
RFC 9114HTTP/3n/aJune 2022(HTTP/3 over QUIC, referenced by RFC 9110)

RFC 9110 now holds everything that applies to every HTTP version: methods, status codes, header field semantics, content negotiation, conditional requests, range requests, authentication, and the http and https schemes. RFC 9110 states that all three major versions of HTTP rely on its semantics and that "they have not obsoleted each other". RFC 9112 keeps only what is specific to HTTP/1.1: the text message format, how to find the end of a message, transfer codings such as chunked, and connection management. RFC 9111 defines caches and the fields that control them.

Two other standards come up constantly in HTTP security work. RFC 6797 (November 2012) defines HTTP Strict Transport Security. RFC 6265 (April 2011, obsoleting RFC 2965) defines cookies. Its successor, draft-ietf-httpbis-rfc6265bis, was at revision 22 (dated 1 December 2025) and showed an IESG state of "RFC Ed Queue" on datatracker when we checked on 23 October 2026, so it is approved but not yet published as an RFC. The IANA HTTP Field Name Registry already points the Set-Cookie entry at that draft. RFC 9651 (September 2024) defines Structured Field Values for HTTP, a common syntax newer header fields use instead of inventing their own parsing rules; it obsoletes RFC 8941.

How HTTP/1.1 works on the wire

The message format

RFC 9112 section 2.1 defines an HTTP/1.1 message with this grammar:

HTTP-message   = start-line CRLF
                 *( field-line CRLF )
                 CRLF
                 [ message-body ]

start-line     = request-line / status-line
request-line   = method SP request-target SP HTTP-version
status-line    = HTTP-version SP status-code SP [ reason-phrase ]
field-line     = field-name ":" OWS field-value OWS

Every line ends in CRLF (carriage return, line feed). An empty line marks the end of the header section. Whatever follows is the body, and the body has no terminator of its own: its length has to be worked out from the header fields, which is the single most important fact about HTTP/1.1 security.

A minimal exchange looks like this. The example request mirrors the one in RFC 9110 section 7.2.

GET /pub/WWW/ HTTP/1.1
Host: www.example.org
User-Agent: curl/8.x
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 1234
Date: Thu, 23 Oct 2026 09:00:00 GMT

(1234 octets of body)

The parts of a message, field by field

ElementWhereWhat it holdsNotes from RFC 9112 and RFC 9110
methodrequest lineGET, POST, PUT and so onCase sensitive token. A server that sees a method longer than any it implements should answer 501
request-targetrequest linePath and query, or a full URI, or host:port, or *Four forms: origin-form, absolute-form (to proxies), authority-form (CONNECT), asterisk-form (server wide OPTIONS). No whitespace allowed
HTTP-versionboth start linesHTTP/1.1Case sensitive. Intermediaries must send their own version, not forward the client's blindly
status-codestatus lineThree digitsThe first digit is the class
reason-phrasestatus lineHuman text such as OKOptional; clients should ignore it
field-nameeach field lineCase insensitive nameNo whitespace allowed between the name and the colon
field-valueeach field lineThe valueLeading and trailing optional whitespace is stripped
empty lineafter fieldsCRLF aloneEnds the header section
message-bodyafter empty lineContent, possibly transfer codedLength set by the rules in section 6.3

Host and the four request target forms

HTTP/1.0 assumed one site per IP address. HTTP/1.1 made the Host header mandatory so one server could host many names, and RFC 9112 Appendix C.2.1 calls the Host requirement one of "the most important changes" HTTP/1.1 introduced. Today RFC 9112 section 3.2 is strict about it: a server must respond 400 to any HTTP/1.1 request that lacks a Host field, has more than one Host field line, or has an invalid Host value.

The request target comes in four shapes:

FormExample shapeUsed for
origin-form/where?q=nowNormal requests to an origin server, with Host carrying the name
absolute-formhttp://www.example.org/where?q=nowRequests to a forward proxy
authority-formwww.example.org:443CONNECT only, to open a tunnel
asterisk-form*Server wide OPTIONS

Recipients of a malformed request line should answer 400 or redirect with a properly encoded target, and RFC 9112 warns against quietly autocorrecting, "since the invalid request-line might be deliberately crafted to bypass security filters along the request chain".

Finding the end of the body: the precedence rules

RFC 9112 section 6.3 lists, in order, how a recipient decides how long a body is. Paraphrased:

  1. A response to HEAD, and any 1xx, 204 or 304 response, ends at the empty line. No body, no matter what the fields say.
  2. A 2xx response to CONNECT turns the connection into a tunnel right after the header section.
  3. If both Transfer-Encoding and Content-Length are present, Transfer-Encoding wins. The RFC adds that such a message "might indicate an attempt to perform request smuggling" and "ought to be handled as an error". An intermediary that forwards it must strip Content-Length first.
  4. If Transfer-Encoding is present and chunked is the final coding, read chunks until the last one. If chunked is not final in a request, the server must answer 400 and close the connection.
  5. If there is no Transfer-Encoding and Content-Length is invalid, the framing is broken and the recipient must treat it as unrecoverable (400 and close for a server, 502 for a proxy). A list of identical valid values is the only tolerated exception.
  6. A valid Content-Length gives the length in octets.
  7. A request with neither field has no body.
  8. A response with neither field runs until the server closes the connection.

Two further rules from section 6.1 matter for security. A sender must never send Content-Length in a message that has Transfer-Encoding. A server that receives a request with both may reject it or process it by Transfer-Encoding alone, but "regardless, the server MUST close the connection after responding to such a request to avoid the potential attacks".

Chunked transfer coding

Chunked coding lets a sender stream content of unknown size while keeping the connection reusable. RFC 9112 section 7.1 defines it:

chunked-body   = *chunk
                 last-chunk
                 trailer-section
                 CRLF

chunk          = chunk-size [ chunk-ext ] CRLF
                 chunk-data CRLF
chunk-size     = 1*HEXDIG
last-chunk     = 1*("0") [ chunk-ext ] CRLF

Each chunk starts with its size in hexadecimal, then the data, then CRLF. A chunk of size zero ends the body, optionally followed by trailer fields and a final empty line. RFC 9112 asks recipients to "anticipate potentially large hexadecimal numerals" and avoid integer overflow when parsing sizes, and says a server "ought to limit the total length of chunk extensions" it accepts. Trailer fields must not be merged into the header section unless that field's definition explicitly allows it, a rule RFC 9112 tightened compared with RFC 7230.

A connection, start to finish

HTTP/1.1 has no handshake of its own; the TCP handshake (and TLS, for https) comes first. After that the exchange is a simple alternation on one connection:

Client                                         Server
  |  TCP SYN / SYN-ACK / ACK                      |
  |  (TLS ClientHello ... Finished, for https;    |
  |   ALPN may select "http/1.1")                 |
  |---------------------------------------------->|
  |  GET /a HTTP/1.1  + fields + empty line       |
  |---------------------------------------------->|
  |  HTTP/1.1 200 OK  + fields + body             |
  |<----------------------------------------------|
  |  GET /b HTTP/1.1  (same connection, reused)   |
  |---------------------------------------------->|
  |  HTTP/1.1 200 OK  + fields + body             |
  |<----------------------------------------------|
  |  GET /c HTTP/1.1  Connection: close           |
  |---------------------------------------------->|
  |  HTTP/1.1 200 OK  Connection: close + body    |
  |<----------------------------------------------|
  |  server half-closes, reads until client FIN   |
  |  TLS close_notify (https), TCP FIN / ACK      |

For https, RFC 9112 section 9.7 says the HTTP client acts as the TLS client, sends the ClientHello and starts sending requests once the handshake finishes. RFC 9112 section 12.4 registers the ALPN protocol identifier http/1.1 (the bytes 0x68 0x74 0x74 0x70 0x2f 0x31 0x2e 0x31), which is how a TLS client and server agree on HTTP/1.1 rather than HTTP/2.

Default ports and registrations

ItemValueSource
http default portTCP 80RFC 9110 section 4.2.1
https default portTCP 443RFC 9110 section 4.2.2
ALPN identifierhttp/1.1RFC 9112 section 12.4
Header field namesIANA "Hypertext Transfer Protocol (HTTP) Field Name Registry"RFC 9110 section 16.3.1
Transfer codingsIANA HTTP Transfer Coding registryRFC 9112 section 7.3

Methods, status codes, persistent connections and pipelining

Methods

RFC 9110 section 9 defines eight methods and classifies each as safe (read only in intent) and idempotent (repeating it has the same intended effect as sending it once):

MethodPurposeSafeIdempotent
GETTransfer a current representation of the target resourceyesyes
HEADSame as GET, but no response contentyesyes
POSTResource specific processing of the request contentnono
PUTReplace all current representations with the request contentnoyes
DELETERemove all current representationsnoyes
CONNECTEstablish a tunnel to the server named by the targetnono
OPTIONSDescribe the communication options for the targetyesyes
TRACEMessage loop back test along the pathyesyes

These properties are not trivia. RFC 9110 explains that idempotent requests "can be repeated automatically if a communication failure occurs", and that a proxy "MUST NOT automatically retry non-idempotent requests". TRACE reflects the received request back to the client, and RFC 9110 warns that a client must not put sensitive data in a TRACE request and that the final recipient should strip fields likely to carry it.

Status codes

RFC 9110 section 15 groups three digit codes by their first digit:

ClassMeaningCommon examples
1xxInformational, an interim response100 Continue, 101 Switching Protocols
2xxSuccessful200 OK, 201 Created, 204 No Content, 206 Partial Content
3xxRedirection301, 302, 303, 304 Not Modified, 307, 308
4xxClient error400, 401, 403, 404, 405, 411 Length Required, 414 URI Too Long
5xxServer error500, 501 Not Implemented, 502 Bad Gateway, 503, 504

Some codes are "heuristically cacheable" (RFC 9110 lists 200, 203, 204, 206, 300, 301, 308, 404, 405, 410, 414 and 501), meaning a cache may reuse them without explicit freshness information.

Persistent connections

HTTP/1.0 opened a new TCP connection for every request unless both sides used the nonstandard Keep-Alive mechanism. HTTP/1.1 reversed the default. RFC 9112 section 9.3 says HTTP/1.1 "defaults to the use of persistent connections", and a connection stays open unless either side sends Connection: close. A server that does not support persistence must send close on every response.

Persistence has a hard requirement that leads directly to the security issues below: every message on the connection must have a self defined length. RFC 9112 says a server must read the entire request body or close the connection, "otherwise, the remaining data on a persistent connection would be misinterpreted as the next request". Leftover bytes are not discarded; they become the start of the next message.

Pipelining

A client on a persistent connection may pipeline, which means sending several requests without waiting for each response. RFC 9112 section 9.3.2 lets a server process pipelined requests with safe methods in parallel, but it "MUST send the corresponding responses in the same order that the requests were received". Responses carry no identifier tying them to requests; order is the only link. Clients should not pipeline after a non idempotent method until its final response arrives.

That ordering rule is why HTTP/1.1 suffers from head of line blocking: one slow response holds up every response behind it. RFC 9112 section 9.4 notes that multiple parallel connections are "typically used to avoid the head-of-line blocking problem", while also asking clients to be conservative, because each connection costs server resources. HTTP/2 was designed largely to fix this with multiplexed streams.

Versions and history

Version or documentDateWhat changed
HTTP/0.9Before HTTP/1.0, no RFCRequest line only, no header fields, so no name based virtual hosting (RFC 9112 Appendix C.1)
HTTP/1.0, RFC 1945May 1996Header fields in requests and responses; a new connection per request unless both sides used the nonstandard Keep-Alive
HTTP/1.1, RFC 2068January 1997First Standards Track HTTP/1.1: Host, chunked coding, persistent connections by default, caching controls
RFC 2616June 1999Revised HTTP/1.1; the reference for most of the 2000s
RFC 7230 to RFC 7235June 2014HTTP/1.1 split into six documents (message syntax, semantics, conditional requests, range requests, caching, authentication)
RFC 9110, 9111, 9112June 2022Version independent semantics separated from HTTP/1.1 syntax; stricter parsing to resist smuggling
HTTP/2, RFC 9113June 2022A multiplexed session layer with field compression over TLS and TCP, sharing RFC 9110 semantics
HTTP/3, RFC 9114June 2022HTTP over QUIC, a secure multiplexed transport over UDP

RFC 9110 says HTTP/1.1 "was introduced in 1995" before its 1997 publication. RFC 9112 Appendix C.3 lists the changes from RFC 7230 that matter most for defenders:

  • Bare CR characters are now prohibited outside content.
  • Recipients are required to avoid smuggling and splitting attacks when they see ambiguous framing (the Content-Length plus Transfer-Encoding rules above).
  • Trailer fields are stored separately by default and only merged into headers when a field's definition allows it.

What is deprecated in practice: obsolete line folding (a field value continued on a new line that starts with whitespace), which RFC 9112 section 5.2 now treats as something a server must reject with 400 or replace with spaces; HTTP/1.0 style Keep-Alive; and reliance on pipelining, which remains legal but suffers from head of line blocking. What is still deployed: practically all of HTTP/1.1, especially between load balancers and application servers, in service meshes that downgrade, and in embedded devices.

Security properties of HTTP/1.1

Start with what the protocol itself guarantees, which is very little:

PropertyPlain httphttps (HTTP/1.1 over TLS)
ConfidentialityNone. RFC 9112 section 11.4: HTTP "relies on underlying transport protocols"Yes, from TLS
IntegrityNone. Section 11.3: the http scheme "provides no protection against accidental or malicious modification"Yes, from TLS authenticated encryption, with a caveat about truncation by connection closure
Server authenticationNone; trust rests on DNS and IP routing (RFC 9110 section 17.1)Yes, if the client verifies the certificate against the URI authority
Client authenticationOnly what the application adds (cookies, tokens, HTTP authentication)Same, plus optional TLS client certificates
Replay protectionNone at the HTTP layerTLS protects the record stream; application level replay is still the application's job
AvailabilityText parsing with no fixed limits is open to slow and oversized input (RFC 9110 section 17.5)Same, plus TLS handshake cost

RFC 9110 section 17.1 makes the point about plain HTTP directly: for http URIs, "any attack on a user's network host table, cached names, or name resolution libraries becomes an avenue for attack on establishing authority". Section 17.2 adds that intermediaries "are inherently situated for on-path attacks" and are "no more trustworthy than the people and policies under which they operate".

Weakness classes, explained as concepts

None of the following includes attack steps. Each is described at the level the RFCs themselves use: what goes wrong and why the design permits it.

Message framing ambiguity and request smuggling

HTTP/1.1 is a stream of bytes in which message boundaries are inferred, not marked. A request passes through a chain: perhaps a CDN, a load balancer, a reverse proxy, then the application server. Each hop parses the stream independently. If two hops reach different conclusions about where a request ends, the leftover bytes that one hop treats as part of the first request are treated by the next hop as the beginning of a second request.

RFC 9112 section 11.2 defines request smuggling as a technique "that exploits differences in protocol parsing among various recipients to hide additional requests (which might otherwise be blocked or disabled by policy) within an apparently harmless request". The consequences can include bypassing front end access controls, attaching another user's request to attacker supplied content, and poisoning caches.

The design allows this because of several historical tolerances:

  • Two length signals. Content-Length and Transfer-Encoding can both appear. RFC 9112 explains that early implementations sent both, a chunked body plus an estimated length "for use by progress bars", which is why the standard made Transfer-Encoding override Content-Length rather than forbidding the combination. Any hop that does not follow that rule exactly, or that only speaks HTTP/1.0, can disagree with the others.
  • Lenient whitespace and line endings. RFC 9112 lets recipients treat a bare LF as a line ending and lets them parse request lines on any whitespace. It immediately warns that "lenient parsing can result in request smuggling security vulnerabilities if there are multiple recipients of the message and each has its own unique interpretation of robustness".
  • Field name oddities. Whitespace before the colon used to be handled differently by different parsers, so RFC 9112 section 5.1 now requires a server to reject it with 400. RFC 9110 section 17.10 describes a related trap in gateway interfaces such as CGI, which map header names to variables by replacing hyphens with underscores; a field named with an underscore could map to the same variable as Transfer-Encoding, "resulting in a potential request smuggling vulnerability".

RFC 9112's answer is to make the strict reading mandatory: treat both length fields as an error or strip Content-Length before forwarding, reject invalid or conflicting Content-Length values, reject chunked that is not final in a request, reject whitespace before colons and bare CRs, and close the connection whenever framing was questionable.

Header injection and response splitting

RFC 9112 section 11.1 describes response splitting, also called CRLF injection. An application takes input from a request, decodes it, and echoes it into a response header such as Location. If the decoded input contains CR and LF characters, the application's one response can look like two, and the second one is controlled by whoever supplied the input. On a persistent connection, or through a shared cache, an intermediary may then match that forged second response to a different request.

The RFC is clear that filtering for encoded CR and LF in input is weak, because applications transform data in many ways (charset transcoding, entity decoding, base64). Its recommended defence is architectural: "prevent anything other than the server's core protocol libraries from sending a CR or LF within the header section", which means only setting headers through APIs that reject bad octets.

Cache poisoning

A shared cache stores a response once and serves it to many clients. RFC 9111 section 7.1 defines cache poisoning as inserting malicious content into a cache through implementation flaws or other techniques, and names a common vector: "differences in message parsing on proxies and in user agents". The cache key is, at minimum, the method and target URI, extended by request fields named in the Vary response header (RFC 9111 sections 2 and 4.1). Trouble starts when a response depends on a request field that is not part of the key, because the cache then stores a response shaped by one client's input and serves it to everyone.

RFC 9111 section 7.3 adds a quieter risk: caching sensitive data by mistake. It notes that Set-Cookie "does not inhibit caching", so a cacheable response that sets a session cookie can be served to other users unless Cache-Control says otherwise.

Host header trust

The Host field tells a multi tenant server which site the client wants. RFC 9110 section 7.2 warns that because host and port act as "an application-level routing mechanism", they are "a frequent target for malware seeking to poison a shared cache or redirect a request to an unintended server". Problems arise when an application builds absolute URLs, password reset links or redirects from the Host value without checking it against a list of names it actually serves, or when a proxy routes to internal servers based on Host alone.

The defence is to treat Host like any other client input: configure an explicit default server that rejects unknown names, generate links from configured canonical names, and make the proxy validate the name before routing. The same principle applies to the forwarding headers that proxies add, which our post on which client IP to log behind a proxy covers in depth.

Cleartext exposure

Over plain http, every request line, cookie, token and body crosses the network readable and editable by anyone on the path. RFC 9110 section 17.9 adds that URIs "are intended to be shared, not secured": servers, proxies and user agents log or display them, so secrets do not belong in query strings even over TLS. The only real fix is https everywhere, with HSTS so a browser never makes the first insecure request. The post on defending against man in the middle attacks with TLS and HSTS covers the attack side; here we focus on configuration.

Length and slowness

RFC 9110 section 17.5 notes that because HTTP uses "mostly textual, character-delimited fields", parsers are often vulnerable to "very long (or very slow) streams of data". The standard sets minimum sizes that implementations should support but leaves the maximums to operators, and warns that failing to limit method names, field names, numeric values and chunk lengths "can result in arbitrary code execution due to buffer or arithmetic overflows". That is why server limits on header size, header count, body size and read timeouts are security settings, not just tuning.

Which security headers belong in HTTP semantics

Several protective mechanisms ride on HTTP header fields. Two are IETF standards that live alongside RFC 9110.

HSTS (RFC 6797)

Strict-Transport-Security tells a browser to use only https for a host for a period of time. RFC 6797 defines two directives: max-age (required, in seconds) and includeSubDomains (optional, extending the policy to all subdomains). The RFC's own example is max-age=15768000 ; includeSubDomains, and max-age=0 tells the browser to forget the policy.

Three rules from RFC 6797 trip people up:

  • The header only counts over a secure connection. An HSTS host "MUST NOT include the STS header field in HTTP responses conveyed over non-secure transport", and browsers ignore it there.
  • Over plain HTTP, the server should instead send a permanent redirect to the https URL (section 7.2).
  • The first visit is unprotected. Section 14.6 calls this the "bootstrap MITM vulnerability", and section 12.3 describes a pre loaded list built into browsers as the mitigation.

Cookies (RFC 6265 and its successor)

RFC 6265 defines Set-Cookie and Cookie and two security attributes. Secure limits a cookie to secure channels, and RFC 6265 notes it "protects only the cookie's confidentiality", since an active network attacker can still overwrite Secure cookies from an insecure channel. HttpOnly keeps a cookie out of non HTTP APIs such as page scripts. The draft successor, rfc6265bis, adds the SameSite attribute and the __Secure- and __Host- name prefixes; as noted above, it was in the RFC Editor queue on 23 October 2026, so cite it as an approved draft rather than an RFC number.

Secure configuration, with defender side checks

Only run the commands below against systems you own or are authorised to test. Every directive and default quoted here comes from the official nginx documentation, the Apache HTTP Server 2.4 documentation or the curl manual, fetched on 23 October 2026, with URLs in the Sources list.

1. Check what version and headers you actually serve with curl

The curl manual describes -I, --head as fetching "the headers only" using HEAD, and --http1.1 as forcing HTTP/1.1 (the default for http:// URLs).

# Response headers only, via HEAD
curl -I https://your-site.example/

# Force HTTP/1.1 and watch the full exchange verbosely
curl -v --http1.1 https://your-site.example/ -o /dev/null

# Print only the HTTP version curl actually used
curl -s -o /dev/null -w '%{http_version}\n' https://your-site.example/

The curl manual defines the http_version write out variable as "the http version that was effectively used". In the -v output, look for Strict-Transport-Security, Set-Cookie attributes, Cache-Control on pages that set cookies, and a Server value that does not advertise a version.

To confirm TRACE is disabled on your own server, the curl manual's -X, --request option sets a custom method:

curl -s -o /dev/null -w '%{http_code}\n' -X TRACE https://your-site.example/

A 405 means TRACE is refused. Note the curl manual's own warning that -X passes the method string "verbatim" without filtering, so use it only for a deliberate check like this one.

2. nginx

Relevant directives from the official ngx_http_core_module, ngx_http_headers_module and ngx_http_proxy_module documentation:

DirectiveDefaultWhat to do
server_tokensonSet off so the version is not shown in the Server header or error pages
ignore_invalid_headersonKeep on so header fields with invalid names are ignored
underscores_in_headersoffKeep off; names with underscores are then treated as invalid
large_client_header_buffers4 8kSize to your real needs; this caps request line and header length
max_headers1000Caps the number of request header lines (added in 1.29.8)
client_max_body_size1mSet per location to the largest body you expect
client_header_timeout60sLower it to limit slow header attacks
keepalive_timeout75sLower it if idle connections are a resource concern
keepalive_requests1000Requests per persistent connection before it is closed
proxy_http_version1.1Default since 1.29.7; before that it was 1.0. Use 1.1 or 2 for back end keep alive

For HSTS, the headers module documentation says add_header only applies to certain success and redirect codes unless the always parameter is given, and that add_header directives are inherited only if the current level defines none. A typical setting in an https server block:

server_tokens off;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Then confirm what nginx actually loaded. The nginx command line documentation describes -t as testing the configuration and -T as the same "but additionally dump configuration files to standard output":

nginx -t
nginx -T | grep -E 'server_tokens|underscores_in_headers|ignore_invalid_headers|Strict-Transport-Security|proxy_http_version'
nginx -V

nginx -V prints the version and build options, which you need when matching against advisories.

3. Apache HTTP Server 2.4

From the official core and mod_headers documentation:

DirectiveDefaultWhat to do
HttpProtocolOptionsStrict LenientMethods Allow0.9Keep Strict; consider RegisteredMethods and Require1.0 if no client needs HTTP/0.9 or unregistered methods
TraceEnableonSet off; the docs say this makes the core server and mod_proxy return 405
ServerTokensFullSet Prod so the Server header does not list OS and modules
LimitRequestLine8190Bytes allowed in the request line
LimitRequestFieldSize8190Bytes allowed in one request header field
LimitRequestFields100Number of request header fields
KeepAliveTimeout5Seconds to wait for the next request on a persistent connection

The Apache docs describe HttpProtocolOptions Strict as applying the RFC 7230 rules to the request line and header fields by default, with Unsafe existing only to restore legacy behaviour for old clients. Leave it strict.

mod_headers distinguishes the onsuccess table (the default) from always, which also applies to error and redirect responses:

ServerTokens Prod
TraceEnable off
HttpProtocolOptions Strict
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"

Test before reloading. The apachectl documentation describes configtest as running "a configuration file syntax test", equivalent to apachectl -t:

apachectl configtest

4. Close the framing gaps between hops

Configuration alone cannot guarantee that every hop in a chain parses identically, but these choices narrow the gap:

  • Keep every proxy, load balancer and application server on current releases. The 2022 parsing rules only help once your software implements them.
  • Prefer HTTP/2 or HTTP/1.1 consistently between hops. Where a front end speaks HTTP/2 and downgrades to HTTP/1.1 to reach the back end, the downgrade is where framing differences reappear; the HTTP/2 hardening guide covers that boundary.
  • Keep parsers strict: invalid header names ignored or rejected, underscores refused, obsolete line folding rejected.
  • Do not let applications write raw bytes into the response header section; use the framework's header API.
  • Make Cache-Control: private or no-store the norm on responses that set cookies or vary by user, and make sure anything that changes a response is part of the cache key or in Vary.

5. Enforce TLS and redirect plain HTTP

Serve https everywhere, send a permanent redirect on port 80, and add HSTS on the https responses only. The TLS side, including protocol versions to disable, is covered in TLS 1.3 vs TLS 1.2: what to disable.

Where Safeguard fits

Safeguard does not sit in your request path, and it does not inspect, filter or block HTTP traffic. Its role is around the software that implements HTTP. Software composition analysis, generally available, builds a full transitive inventory of the HTTP servers, client libraries and parsers inside your repositories and container images, and SBOM generation and analysis in SPDX and CycloneDX, also generally available, records them. Vulnerability management with EPSS and KEV enrichment and reachability analysis, both generally available, rank which parser advisories affect code your services actually call. DAST, generally available, runs first party dynamic tests against HTTP targets and APIs you have verified you own, which is a practical way to confirm what headers and behaviour a deployed service really exposes. If you want to see how quickly an SBOM answers "which services ship this HTTP library version", the post on using SBOMs to find affected products fast walks through it.

When to use HTTP/1.1 versus HTTP/2 and HTTP/3

Use caseHTTP/1.1HTTP/2 (RFC 9113)HTTP/3 (RFC 9114)
TransportTCP, optional TLSTCP, TLS in practice for browsersQUIC over UDP, always encrypted
ConcurrencyOne request at a time per connection; responses in orderMany streams on one connectionMany streams, no TCP head of line blocking
FramingText, inferred from fieldsBinary frames with explicit lengthsBinary frames over QUIC streams
DebuggabilityReadable with any text toolNeeds protocol aware toolsNeeds protocol aware tools and UDP access
Smuggling exposureHighest, because framing is inferredLower end to end, but downgrades to 1.1 at proxies reintroduce itSame downgrade caveat
Good fitSimple clients, embedded devices, internal health checks, legacy integrations, streaming with chunkedPublic websites and APIs, gRPC, internal service to service with TLSMobile and lossy networks, latency sensitive public traffic
Operational costLowest; near universal supportModerate; stream and flow control limits to tuneHighest; UDP must be allowed through firewalls and load balancers must support QUIC

A practical rule: use HTTP/2 or HTTP/3 at the edge for browsers and public APIs, and be deliberate about any hop that falls back to HTTP/1.1. If a hop must speak HTTP/1.1, run current software with strict parsing on both sides. For internal tools and constrained devices, HTTP/1.1 over TLS remains a perfectly reasonable choice; its simplicity is a feature as long as framing is strict.

Common mistakes

  • Treating HTTP/1.1 as "solved" because the edge speaks HTTP/2. Many architectures still use HTTP/1.1 between the edge and the application. That internal hop is where framing disagreements live.
  • Assuming filtering %0D%0A stops header injection. RFC 9112 explains why input filtering misses transformed data. Set headers only through APIs that reject CR and LF.
  • Trusting the Host header. Building reset links or redirects from Host without checking it against configured names lets a client influence URLs your application generates.
  • Sending HSTS over plain HTTP. Browsers ignore it there by design. Redirect port 80 to https and send HSTS on the secure response.
  • Forgetting always in nginx or Apache. Without it, HSTS and other headers disappear from error and some redirect responses.
  • Caching responses that set cookies. RFC 9111 states Set-Cookie does not stop caching. Mark personalised responses private or no-store.
  • Loosening parsers to fix one broken client. HttpProtocolOptions Unsafe or enabling underscores in header names to support a legacy client widens parsing differences for every client.

FAQ

What port does HTTP/1.1 use?

TCP port 80 for http URIs and TCP port 443 for https URIs, as defined in RFC 9110 sections 4.2.1 and 4.2.2. Any other port can be used if it is written in the URI.

Is HTTP/1.1 encrypted?

Not by itself. HTTP/1.1 sends text over whatever connection it is given. Over plain TCP it is readable and modifiable by anyone on the path. Over TLS (https), the messages are encrypted and integrity protected by TLS, not by HTTP.

What replaced RFC 2616 and RFC 7230?

RFC 2616 (1999) was replaced by RFC 7230 to 7235 in June 2014, and those were replaced in June 2022 by RFC 9110 (semantics), RFC 9111 (caching) and RFC 9112 (HTTP/1.1 message syntax and connection management).

What is HTTP request smuggling?

A class of weakness where two HTTP processors in a chain disagree about where a request ends, so bytes one treats as body the other treats as a new request. RFC 9112 section 11.2 describes it, and the 2022 parsing rules in section 6.3 exist to reduce it.

HTTP/1.1 vs HTTP/2: which should I use?

Use HTTP/2 (or HTTP/3) for public sites and APIs where many requests share one connection. HTTP/1.1 is fine for simple clients and internal calls if both ends run current, strictly parsing software. Watch any proxy that downgrades HTTP/2 to HTTP/1.1.

What does Transfer-Encoding: chunked mean?

The body is sent as a series of chunks, each prefixed by its size in hexadecimal, ending with a zero size chunk. It lets a server stream content of unknown length while keeping the connection open for later requests.

How do I check which HTTP version my server uses?

Run curl -s -o /dev/null -w '%{http_version}\n' https://your-site.example/ against a server you own. Add --http1.1 to force HTTP/1.1, or -v to see the connection details and headers.

Does HSTS work on the first visit?

No. RFC 6797 calls this the bootstrap problem. A browser only learns the policy from a secure response, so a site's first insecure request is unprotected unless the domain is on a browser's pre loaded HSTS list.

Fix it without waiting for upstream

HTTP/1.1 parsing bugs almost never live in your own code. They live in the reverse proxy, web server, framework or HTTP client library your software depends on, and the fix arrives as a new release of that dependency. The faster those releases reach every image you run, the shorter the window in which two hops can disagree.

Whatever the vulnerability, most of the risk sits in two gaps. The first is the time between a fix being published and that fix actually running in your production builds. The second, and worse, is the time before any fix exists at all, while a maintainer works out a patch and a release schedule. Waiting through both gaps is a habit, not a requirement. Safeguard gives teams three ways to close them.

1. Autonomous remediation: pull requests, not tickets

When a fixed version exists, Safeguard's Griffin models open a tested, policy gated pull request that moves the affected dependency to the safe version. You can run it on one repository or in bulk across every project that carries the vulnerable version. Remediation covers eight categories, including dependencies, licenses, security posture and findings. Reachability analysis ranks the vulnerable code paths your application actually calls first, so the first pull requests you merge are the ones that matter most. Autonomous remediation ships switched off, with every category set to Manual, so your team decides how much runs on its own and what still needs a human approval.

2. Gold Open Source: the safe version in seconds, free

gold.safeguard.sh is free and needs no login. Search any CVE or package to see affected and fixed versions, CVSS, EPSS, CISA KEV status and a recommended safe version, or use the "Am I affected?" checker. The same data is available through a free read API, README status badges and RSS threat feeds. The safeguard-sh/gold-scan-action GitHub Action can fail a build on a known exploited vulnerability, on any known vulnerability, or never, so a version you have just removed cannot quietly come back in next week's merge.

3. Gold Registry: start from components that are already fixed

registry.safeguard.sh provides hardened, drop in replacements for upstream packages and container images. Every Gold artifact is held continuously to zero exploitable critical or high CVEs, zero malware and a completed license check, with SLSA L3 or higher provenance, a Sigstore signature and a public evidence page you can hand to an auditor. An artifact loses its Gold badge if it regresses. Teams can point their own package directory at the registry so hardened equivalents resolve without changing how developers install anything.

When upstream has no fix yet, or will not ship one, Safeguard first works to get the fix accepted upstream. If the issue is severe or upstream will not move, Safeguard builds a fork, fixes the vulnerability there and publishes a hardened Gold build, so you are not stuck on a maintainer's release schedule. You can also request a hardened, compatibility tested build of a specific package. This fork and patch flow is in early access today, so ask which packages it covers before you plan around it.

Your situationWhat to use
A fixed version existsAutonomous remediation opens the upgrade pull request; Gold Open Source confirms the safe version
You need to know today whether you are affectedGold Open Source lookup and "Am I affected?" checker, free and without login
You want vulnerable versions to stop shippingThe Gold scan GitHub Action in CI, plus Safeguard policies and gates
Upstream has no fix yet, or will not ship oneA custom Gold request; fork and patch (early access)
You want to start clean on new projectsPull hardened components and images from Gold Registry

One honest limit: when the vulnerable code lives in a vendor appliance, a hosted service or an operating system runtime you do not build yourself, the fix still has to come from that vendor or distribution. The steps above apply to the open source dependencies and container images you build and ship, which is where most teams carry the largest and least tracked share of their exposure.

See how Safeguard would help

These demos show dependency, SBOM and remediation workflows, not traffic inspection. You can download Safeguard and follow the CLI docs to try them on your own projects.

This demo sets up the Safeguard runner on your own machine, approves it with its device code and then scans from the terminal. Pointed at the container image for a reverse proxy or web application, the same scan lists the HTTP servers, frameworks and client libraries inside it with their versions and known vulnerabilities, so you know exactly which parsers sit on each hop.

Click through it at your own pace, or open it full screen.

This demo is a real CLI session: install, open the interactive terminal, ask it to fix the lodash CVE in a repository, and watch it read the finding and advisory, ask before each command, move lodash to 4.17.21, run npm install and npm test with approval, and print a summary without pushing anything. The same approve each step flow applies when the package to bump is an HTTP server, proxy library or parser pinned in your manifest.

Click through it at your own pace, or open it full screen.

This demo connects Claude to the Safeguard MCP server with OAuth and lets you choose which tools Claude can use. Once connected, you can ask in plain language which of your projects ship a particular HTTP library version, and get the answer from your own scan data rather than from memory.

Click through it at your own pace, or open it full screen.

Quick check

A request arrives with both Content-Length and Transfer-Encoding: chunked. What does RFC 9112 require?

Transfer-Encoding overrides Content-Length, and the message ought to be treated as an error. A server may reject it or process it by Transfer-Encoding alone, but either way it must close the connection after responding. A proxy that forwards it must remove Content-Length first.

Your site sends Strict-Transport-Security on its port 80 responses but not on HTTPS. Is it protected?

No. RFC 6797 forbids sending the header over non secure transport and browsers ignore it there. Redirect plain HTTP to HTTPS with a permanent redirect, and send HSTS on the HTTPS responses, using the always option in nginx or Apache so error responses carry it too.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.