TLS & HTTPS
Fast can terminate TLS itself, on every runtime, with no reverse proxy in front. It stays dependency-free by default; turning TLS on opts into ext-openssl, which ships with most PHP builds (including native Windows). You can still terminate at a proxy if you prefer, but you no longer have to.
Pass a Fast\Tls\TlsConfig to serve(tls: ...) and every accepted connection negotiates TLS before its first byte is read. The listener never uses an ssl:// transport; crypto is enabled per connection after accept, so a slow handshake can never block the accept loop.
Static certificate
Use this when you already hold a certificate and private key on disk, whether a self-signed pair for local work or a certificate issued by any CA.
use Fast\Tls\TlsConfig;
serve(
port: 443,
tls: TlsConfig::certificate(
cert: __DIR__ . '/certs/fullchain.pem',
key: __DIR__ . '/certs/privkey.pem',
),
);
A default certificate is always required, even when you add per-host certificates: a client that sends no SNI extension, or an unknown SNI host, still has to be answered with something.
serve(
port: 443,
tls: TlsConfig::certificate(
cert: __DIR__ . '/certs/fullchain.pem',
key: __DIR__ . '/certs/privkey.pem',
sniCerts: [
'api.example.com' => [
'cert' => __DIR__ . '/certs/api-fullchain.pem',
'key' => __DIR__ . '/certs/api-privkey.pem',
],
],
minVersion: '1.2',
),
);
The default floor is TLS 1.2 with a modern cipher suite and server-side cipher ordering. Pass minVersion: '1.3' to require TLS 1.3.
Automatic Let's Encrypt
On POSIX, Fast obtains and renews a certificate for you over ACME v2, a pure-PHP client with no third-party libraries.
serve(
port: 443,
tls: TlsConfig::letsEncrypt(
domains: ['example.com', 'www.example.com'],
email: 'admin@example.com',
),
);
On first boot the owner process (the prefork master, or the single process) stands up a temporary :80 responder to answer the HTTP-01 challenge, completes the order, and writes the certificate chain to storage/acme/ before binding :443. From then on it renews well before expiry and rolls the worker pool so the fresh certificate goes live without dropping traffic. Binding :80 and :443 needs root or CAP_NET_BIND_SERVICE.
While wiring a deployment up, point at Let's Encrypt staging to avoid the strict production rate limits:
use Fast\Tls\Acme\AcmeConfig;
serve(
port: 443,
tls: TlsConfig::letsEncrypt(
domains: ['example.com'],
email: 'admin@example.com',
directoryUrl: AcmeConfig::LETS_ENCRYPT_STAGING,
),
);
What ACME does and does not cover
- ACME is POSIX-only. On the native Windows proc pool,
letsEncrypt()is
rejected with a clear message; use a static certificate there, or run under WSL2 for the fork-based runtime with full ACME.
- The client always verifies the CA's own TLS; there is no opt-out. A trusted CA
bundle (openssl.cafile or the system store) must be available on the host.
- HTTP-01 only. DNS-01 and TLS-ALPN-01 are out of scope, as are ALPN, HTTP/2,
and OCSP stapling. The TLS listener speaks HTTP/1.1.
- A certificate that is still comfortably valid is reused with no network traffic,
so booting is cheap once issued.
Forcing HTTPS
request()->secure is true for any request that arrived over TLS, including the loopback workers behind the Windows proc pool once the master has terminated TLS in front of them.
The redirect_https() middleware factory redirects plaintext requests to their HTTPS origin and can emit HSTS on secure responses:
// 308-redirect http -> https, and send HSTS for ~180 days on secure responses
use_middleware(redirect_https(hstsMaxAge: 15552000));
Register it on the plaintext :80 app you control, or as global middleware on the secure app. Cookies already default to secure, so nothing else changes.