Reverse Proxy Configuration
In production, PRISM sits behind a reverse proxy (nginx, Apache, or Caddy) that handles TLS termination, static assets, and request routing.
Your reverse proxy must strip the X-Prism-Bypass header from incoming requests. This header is used internally by PRISM to prevent render loops. If an external client sends it, they can bypass rendering entirely.
Ready-to-use versions of the configurations below ship in the repository at
dist/reverse-proxy/nginx-prism.conf and dist/reverse-proxy/apache-prism.conf,
so they can be copied rather than transcribed out of prose. Both are syntax-checked
against nginx and httpd.
Nginx
:::danger Put every proxy_set_header in the same block
proxy_set_header directives are not merged across levels. A location
that defines even one of its own discards all of them from the enclosing
server block. Writing the bypass strip at server level and
proxy_set_header Connection "" inside location / therefore silently drops
the strip, and a client can forge X-Prism-Bypass.
Verified: with the directives split across levels a forged header arrives at the backend intact; with them together it arrives empty. :::
upstream prism {
server 127.0.0.1:4000;
keepalive 32;
}
server {
listen 443 ssl;
http2 on;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.com.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
# Timeouts — allow time for Chrome rendering
proxy_read_timeout 30s;
proxy_connect_timeout 5s;
location / {
proxy_pass http://prism;
proxy_http_version 1.1;
# Every header this proxy sets must be listed here, in this block.
# Moving any of them to `server` level would take the rest with it.
proxy_set_header Connection "";
# CRITICAL: strip the bypass header so a client cannot forge it
proxy_set_header X-Prism-Bypass "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Serve static assets straight from the origin, bypassing rendering.
location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|wasm|map)$ {
proxy_pass http://your-origin:3000;
# Repeated deliberately: this block has its own proxy_set_header, so it
# inherits nothing from above.
proxy_set_header X-Prism-Bypass "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Check the strip is working before trusting it:
curl -s -H 'X-Prism-Bypass: forged' https://example.com/ -o /dev/null -w '%{http_code}\n'
# then confirm the origin logged no X-Prism-Bypass value
Apache
Requires proxy, proxy_http, headers and ssl:
a2enmod proxy proxy_http headers ssl # Debian/Ubuntu
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.pem
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# Never act as a forward proxy.
ProxyRequests Off
# CRITICAL: strip the bypass header so a client cannot forge it.
# `early` runs before the proxy hands the request on; without it the unset
# can be evaluated too late to matter on some configurations.
RequestHeader unset X-Prism-Bypass early
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
ProxyPass / http://127.0.0.1:4000/
ProxyPassReverse / http://127.0.0.1:4000/
# Allow time for Chrome rendering
ProxyTimeout 30
# Reuse upstream connections
<Proxy http://127.0.0.1:4000/>
ProxySet connectiontimeout=5 timeout=30 keepalive=On
</Proxy>
</VirtualHost>
mod_proxy appends the client address to X-Forwarded-For itself, so it does
not need setting; ProxyPreserveHost On passes the original Host.
Caddy
example.com {
# Strip bypass header
request_header -X-Prism-Bypass
reverse_proxy 127.0.0.1:4000 {
header_up X-Forwarded-Proto {scheme}
transport http {
keepalive 30s
keepalive_idle_conns 32
}
}
}
X-Prism-Variant for CDN Cache Keying
PRISM returns an X-Prism-Variant response header valued bot-mobile, bot-desktop, human-mobile or human-desktop. Use it as a CDN cache-key component — not as a Vary entry, which cannot work because Vary names request headers and nothing sends this one. To key the edge cheaply, inject your own normalised request header and list it in [cache] vary; see CDN Integration.
In nginx, pass it through:
Do not add X-Prism-Variant to Vary here — Vary names request
headers, no client sends this one, and a cache that keys on it collapses every
variant into a single object. The CDN Integration page opens with
exactly this warning; the working pattern is a normalised request header your
edge injects, listed in PRISM's [cache] vary.
Trusted Proxies
When PRISM sits behind a reverse proxy, configure trusted_proxies so PRISM only accepts X-Forwarded-Proto from your proxy IPs:
[server]
trusted_proxies = ["127.0.0.1", "10.0.0.0/8", "172.16.0.0/12"]
When empty — the default — PRISM trusts no proxy. It ignores X-Forwarded-For and X-Forwarded-Proto entirely and treats whatever opened the socket as the client.
Behind a proxy that is every visitor collapsed onto one address: your proxy's. The per-IP render limit (rate_limit_per_ip, three by default) then applies to that one address on behalf of all your traffic, and renders stop after the third visitor. The list above is not optional once something sits in front.