RFC Compliance Map

This page lists the standards that matter for OxPHP as an origin server: HTTP, TLS, static files and the gateway to PHP. It describes OxPHP 0.13.0. WebDAV, proxying and shared caches are outside its scope.

A standard counts as implemented when the behavior is there. Some of it is OxPHP's own work, and some comes from the HTTP, TLS and compression libraries that OxPHP is built on and configures.

Fully implemented

  • RFC 9112, HTTP/1.1: keep-alive, chunked bodies, Expect: 100-continue and pipelining. A timeout limits how long a client may take to send its headers.
  • RFC 9113, HTTP/2: over TLS, and over plain TCP for clients that connect with prior knowledge. Concurrent streams and header size are capped, the Rapid Reset and MadeYouReset attacks are blocked, and a draining server says so with GOAWAY.
  • RFC 7541, HPACK: HTTP/2 header compression, with a limit on decoded header size that stops "HPACK bomb" requests.
  • RFC 8446 and RFC 5246, TLS 1.3 and 1.2: both versions, with a minimum version you can raise to 1.3. 0-RTT early data is off, so replayed early requests can't happen.
  • RFC 7301, ALPN: the TLS handshake picks h2 or http/1.1.
  • RFC 9110, range requests: a single byte range on GET and HEAD, answered with 206 or 416. A request for several ranges gets the whole file with 200, which the standard allows. Ranges are turned off when the response is compressed.
  • RFC 9110, If-Range: honored only with a strong validator, as the standard requires.
  • RFC 9110, content coding: Brotli, gzip and Zstandard. Quality values in Accept-Encoding are respected, and q=0 means the client refuses that encoding. Compressed responses carry Vary: Accept-Encoding and a weak ETag. Event streams are never compressed.
  • RFC 9110, Retry-After: sent with 429 (the time left in the rate-limit window), with 503 while the server drains or shuts down, and with 529 when it is overloaded.
  • RFC 6585, 429 Too Many Requests: per-IP rate limiting answers 429 with Retry-After.
  • RFC 9110, HTTP dates: dates in headers are read and written in the standard format.
  • RFC 10008, the QUERY method: accepted with its body, which PHP reads the same way it reads a POST body. It has its own body size limit. A QUERY request without Content-Type gets 400.
  • RFC 7578, multipart/form-data: file uploads work the way they do in any PHP setup.
  • RFC 9113 and RFC 6265, split Cookie headers: when a client sends Cookie as several header lines, which HTTP/2 clients often do, OxPHP joins them into one before PHP sees them.
  • RFC 8615, well-known URIs: paths under /.well-known/ are served even though other dot paths are blocked. PHP files inside that directory are never executed.
  • RFC 1918, RFC 4193 and RFC 3927, private networks: the private keyword in the trusted proxy list covers all of these ranges. Carrier-grade NAT (RFC 6598) is not included and has to be listed by hand.
  • RFC 7034, X-Frame-Options: SAMEORIGIN by default, sent together with a matching CSP frame-ancestors and X-Content-Type-Options: nosniff. Headers set by your application take priority.

A few standards outside the RFC series are covered too:

Partially implemented

  • RFC 3875, CGI/1.1: the standard CGI variables are in $_SERVER, and every request header arrives as HTTP_*. Missing: a header sent as several lines is not joined into one value, which the standard requires. $_SERVER keeps the last line and the request API the first. REMOTE_HOST is not set.
  • RFC 7239, Forwarded: OxPHP reads Forwarded and X-Forwarded-* from trusted proxies, and CF-Connecting-IP behind Cloudflare. Missing: a quoted value that contains a comma or a semicolon is split in the wrong place. X-Real-IP is not supported.
  • RFC 9110, conditional requests: static files support If-None-Match and If-Modified-Since and answer 304. Missing: If-Match, If-Unmodified-Since and 412 Precondition Failed.
  • RFC 9111, HTTP caching: static files get Cache-Control with a configurable max-age, plus ETag, Last-Modified and Vary. Missing: there is no immutable, and every file gets the same policy whatever its type or path.
  • RFC 9110, request methods: bodies are read for POST, PUT, PATCH, DELETE and QUERY, and a body that is too large is refused with 413 before it is read. Missing: a static file answers every method, POST and DELETE included, with 200 and its content, where nginx answers 405 with an Allow header.
  • RFC 7617, Basic authentication: the Authorization header reaches PHP as HTTP_AUTHORIZATION. Missing: PHP_AUTH_USER, PHP_AUTH_PW and AUTH_TYPE are not filled in.
  • RFC 3986, URIs: percent-decoding and host parsing work, IPv6 addresses included, and paths with . or .. segments get 404. Missing: the trailing slash is dropped before routing, so /dir serves the directory index without a redirect to /dir/, and a file also answers at /file/.
  • RFC 6265, cookies: cookies that OxPHP's plugins set for themselves carry SameSite, HttpOnly, Secure and Max-Age. Your application's cookies go through PHP's setcookie() as usual. Missing: for a malformed cookie pair, the request API's cookie() and $_COOKIE can return different values.

Roadmap

These are proposals, sorted by priority. None of them is scheduled for a particular release.

High priority

  • RFC 3875: join a header that arrives as several lines into one comma-separated value, so PHP sees the whole X-Forwarded-For chain from a series of proxies.
  • RFC 7617: fill in PHP_AUTH_USER and PHP_AUTH_PW from Basic authentication. Applications that read them get empty values today, with no error.
  • RFC 6797, HSTS: send Strict-Transport-Security on every HTTPS response, static files and error pages included. PHP can't add it to static files, because it never sees those requests.

Medium priority

  • RFC 7239: parse quoted values in Forwarded correctly.
  • RFC 9110: answer 405 with Allow: GET, HEAD when a static file gets any other method, as nginx does.
  • RFC 3986: redirect /dir to /dir/ with 301 in Traditional routing, so relative links in older applications resolve.
  • RateLimit header fields (an IETF draft): send RateLimit and RateLimit-Policy, so a client sees its limit before it runs into it. Today the X-RateLimit-* headers appear only on a 429.
  • W3C Server-Timing: report queue wait and PHP execution time in a Server-Timing header. OxPHP already measures both for metrics and traces.

Low priority

  • RFC 9110: If-Match, If-Unmodified-Since and 412 for static files. Static files are read-only, so this would rarely matter.
  • RFC 6455 and RFC 8441, WebSocket: WebSocket over HTTP/1.1 and HTTP/2. The design is still being worked out.
  • RFC 8297, Early Hints: 103 Early Hints, so browsers can start preloading assets before the response is ready.
  • RFC 8446, client certificates: mutual TLS for zero-trust environments.
  • RFC 9114, HTTP/3, with RFC 7838, Alt-Svc: a large piece of work, and it isn't clear it would pay off.

Considered and rejected

The line runs between handling HTTP, which is the server's job, and content that belongs to a particular site, which is not.

  • RFC 9116, security.txt: the file lists the security contacts of whoever runs the site, and the server doesn't know them. A built-in default would be empty, or would wrongly point at the OxPHP maintainers. Put the file in your document root and OxPHP serves it like any other static file. nginx, Apache and FrankenPHP don't generate one either.
  • RFC 3875, PATH_TRANSLATED: the standard recommends it when PATH_INFO is set. It exists for CGI scripts that process some other file. In OxPHP the script being run is always SCRIPT_FILENAME, and the usual nginx with PHP-FPM setup and php -S don't set the variable either. Its value would be built from the PATH_INFO the client sent, so older code that includes $_SERVER['PATH_TRANSLATED'] would load a file of the client's choosing.
  • RFC 7540, h2c upgrade: RFC 9113 dropped the Upgrade: h2c handshake. OxPHP serves HTTP/2 without TLS to clients with prior knowledge, and that is enough.

See also

  • TLS: certificates, versions and HTTP/2 negotiation
  • Compression: how OxPHP picks Brotli, Zstandard or gzip
  • Trusted Proxies: getting the real client address behind a proxy or CDN
  • Superglobals: what PHP finds in $_SERVER and the other superglobals
  • Architecture Overview: how OxPHP handles a request from start to finish
Found a mistake? Report it →