jakubboucek / nette-http-request-strict-proxy
Strict trusted proxy verification for Nette HTTP: trust the connecting proxy (CDN) only when the request carries a valid pre-shared key (PSK) header.
Package info
github.com/jakubboucek/nette-http-request-strict-proxy
pkg:composer/jakubboucek/nette-http-request-strict-proxy
Requires
- php: ~8.1
- nette/di: ^3.2
- nette/http: ^3.2
- nette/schema: ^1.2
- nette/utils: ^3.2 || ^4.0
Requires (Dev)
- nette/tester: ^2.5
- phpstan/phpstan: ^2.1
This package is auto-updated.
Last update: 2026-07-17 18:33:17 UTC
README
Strict trusted proxy verification for Nette HTTP: the connecting peer (your CDN / reverse proxy) is trusted only when the request carries a valid pre-shared key (PSK) header — never based on its IP address alone.
The problem
When your app runs behind a CDN (Cloudflare, Fastly, …), Nette needs to know the CDN's
IP addresses (http › proxy configuration)
to read the real client IP from the X-Forwarded-For header. That brings two problems:
- The CDN's published IP ranges change over time, so your app would have to fetch and refresh them dynamically.
- If an attacker discovers your origin server's IP address, they can connect to it directly and spoof the forwarding headers — unless the trusted proxy list is perfectly accurate.
The solution
Configure your CDN to add a secret header to every request it forwards to your origin server (e.g. a Cloudflare Request Header Transform Rule). The header value is a static PSK — a shared secret between the CDN and the origin.
This package hooks into the http.request service creation:
- If the incoming request carries the PSK header with a valid key, the connecting
peer's IP is passed to
Nette\Http\RequestFactory::setProxy()as a trusted proxy — whatever its IP address is. The forwarded client IP is then resolved by standard Nette logic. - If the header is missing or invalid, nothing is trusted dynamically (fail-closed) and the request behaves as a direct connection.
Highlights:
- No IP allowlist maintenance — works with any CDN IP, present or future.
- Timing-safe key comparison (
hash_equals()). - Multiple keys supported — enables seamless key rotation.
- Plays nice with a statically configured
http › proxylist (both are honored). - The original
Nette\Http\RequestFactoryservice (including its configuration) is reused, not replaced — autowiring stays intact.
Installation
composer require jakubboucek/nette-http-request-strict-proxy
Requires PHP 8.1+ and Nette HTTP 3.2+.
Configuration
Register the extension and set the key(s):
extensions: strictProxy: JakubBoucek\HttpStrictProxy\DI\StrictProxyExtension strictProxy: pskKeys: - your-secret-psk-key
All options:
strictProxy: # List of valid keys; more than one allows seamless key rotation (default: []) pskKeys: - your-secret-psk-key # Name of the HTTP header carrying the PSK (default: X-Proxy-Verify-PSK) header: X-Proxy-Verify-PSK
With an empty pskKeys list nothing is ever trusted — the extension is fail-closed.
Generate a strong key, e.g.:
php -r 'echo bin2hex(random_bytes(32)) . PHP_EOL;'
CDN setup (Cloudflare example)
Create a Request Header Transform Rule (Rules → Transform Rules → Modify Request Header):
- Set static header
X-Proxy-Verify-PSKto your secret key value.
Cloudflare then adds the header to every request forwarded to your origin. Anyone hitting the origin directly doesn't know the key and cannot forge it.
Defense in depth: block direct access at the web server
This package makes proxy-header spoofing harmless, but you may want to reject direct (non-CDN) requests entirely before they reach PHP. On Apache:
## Protection to prevent bypassing the CDN proxy # Check the PSK in the request header, set the TRUST_PROXY_VERIFIED env variable if it passes SetEnvIf X-Proxy-Verify-PSK "^your-secret-psk-key$" TRUST_PROXY_VERIFIED=Cloudflare # Allow access from the local devstack (http://localhost/, http://localhost:8080/) SetEnvIf Host "^localhost(:\d+)?$" TRUST_PROXY_VERIFIED=Localhost # Deny all access which bypasses the CDN Order Deny,Allow Deny from all Allow from env=TRUST_PROXY_VERIFIED
Key rotation
- Add a new key to
pskKeys(keep the old one) and deploy. - Change the header value in the CDN rule to the new key.
- Remove the old key from
pskKeysand deploy.
No downtime, no window with an untrusted CDN.
Security notes
- Treat the PSK like a password: don't commit it to the repository, load it from local configuration or an environment variable.
- The PSK header is compared in constant time against every configured key, so the comparison does not leak which (or whether a) key matched.
- The header travels between the CDN and the origin — make sure that connection uses TLS (e.g. Cloudflare SSL mode Full (strict)), otherwise the key could be sniffed.
- Avoid logging raw request headers on the origin, or exclude the PSK header from logs.
Standalone usage (without Nette DI)
use JakubBoucek\HttpStrictProxy\ProxyRequestFactory; $factory = new ProxyRequestFactory(['your-secret-psk-key']); $request = $factory->createRequest(); // Nette\Http\Request
Tests
composer install
composer run test
composer run phpstan
License
MIT. See the LICENSE file.