Varnish and reverse proxies
This page is for system administrators at hosting companies who run a reverse proxy, a caching proxy such as Varnish, or a load balancer in front of customer sites. It answers: whether Exponential needs one at all, what the proxy must send and be trusted for, what a caching proxy may store, and how to purge it. Varnish's own configuration language is not described here; check its documentation. The sources are Chapter 8, section 8.8, Forwarded headers are trusted only from configured proxies and Chapter 15, section 15.14.
Contents
- Do you need a proxy?
- What the proxy must send
- Trusting the proxy
- A caching proxy: what may be stored
- Purging: Exponential sends none
- Checks and symptoms
- Checklist
Do you need a proxy?
| Reason | Needed? |
|---|---|
| TLS termination | no for Velocity, which serves HTTPS itself; yes when a load balancer or CDN already terminates TLS |
| Several services or customer instances behind one public address | a plain reverse proxy is the right shape |
| Running Velocity as an ordinary user on a high port | a proxy or a firewall redirect in front |
| Caching anonymous pages | not necessarily: Velocity's response cache and the role-aware HTTP cache do this inside Exponential |
For sites migrated from older platforms that used Varnish, the installation book says: "Either drop Varnish (the HTTP cache or Velocity's response cache does the same job inside Exponential), or let it cache anonymous pages for a short time only ..., or purge it yourself."
What the proxy must send
| Header | Why |
|---|---|
X-Forwarded-Proto (set by the proxy, never passed through from the visitor) | so generated URLs, redirects and the session cookie's Secure flag are right |
X-Forwarded-For (appended or set) | the visitor's address; Exponential reads the list from the right, so appending is safe |
the original Host | Velocity does not use X-Forwarded-Host; under PHP-FPM a trusted X-Forwarded-Host replaces Host |
Derived examples for nginx and Apache in front of Velocity are on Velocity beside Apache or nginx.
Trusting the proxy
Forwarded headers count only from a trusted peer. There are two lists:
| Server behind the proxy | List | Default |
|---|---|---|
| Apache or nginx with PHP-FPM, FrankenPHP | site.ini [HTTPHeaderSettings] TrustedProxies[] | 127.0.0.1, ::1 |
| Velocity | Q.webserver.proxy.trusted in the engine configuration | 127.0.0.1, ::1 |
A proxy on the same machine (including Varnish on loopback) is covered. A proxy elsewhere must be listed:
# settings/override/site.ini.append.php
[HTTPHeaderSettings]
TrustedProxies[]
TrustedProxies[]=127.0.0.1
TrustedProxies[]=::1
TrustedProxies[]=10.0.0.0/16
ClientIpByCustomHTTPHeader=X-Forwarded-For- Entries are addresses or ranges, IPv4 and IPv6; host names are not resolved. An override that starts with
TrustedProxies[]on its own line replaces the list. - Comments go on lines of their own: after a value a
#is read as part of the value and the entry is ignored. ClientIpByCustomHTTPHeaderis needed under PHP-FPM only; under Velocity the engine hands the visitor's address over asREMOTE_ADDR.- Trusting a proxy means trusting it to set the headers. A local proxy that passes on a visitor's own
X-Forwarded-Protowould carry it through; if you cannot change such a proxy, setTrustedProxies[]empty. - Make sure the origin cannot be reached around the proxy (firewall, security group).
For Velocity, put the list in a snippet such as /etc/vc/conf-available/reverse-proxy.conf and enable it with
exp:velocity conf enable reverse-proxy (Velocity beside Apache or nginx).
Engine 0.0.4.44 or later is needed for the forwarded protocol to be read only from trusted proxies.
A caching proxy: what may be stored
Warning
A caching proxy must not store signed-in pages. Do not cache responses that set or depend on the session cookie (Chapter 8, section 8.8).
What Exponential gives a proxy to work with:
- Pages are
no-cacheby default. To let a proxy store anonymous pages, switch on[HTTPHeaderSettings]withOnlyForAnonymous=enabledand a shortmax-age(HTTP cache headers). - Pages that carry a form token are sent
private. - The session cookie's name follows
site.ini [Session] SessionNameHandler: withdefaultPHP names it; withcustomit isSessionNamePrefix(shippedeZSESSID) plus a per-siteaccess digest. The kernel also setsis_logged_infor signed-in visitors. A proxy rule that bypasses the cache on either cookie matches what Velocity's own response cache does. - Static files carry long lifetimes under Velocity (
StaticMaxAge) and can be cached freely.
Purging: Exponential sends none
Exponential sends no purge or ban requests to a proxy; there is no PURGE or BAN in the kernel. A proxy that keeps
pages longer than their max-age will serve stale pages after an edit. Options from the installation book:
- Short lifetimes only: let the proxy keep anonymous pages for the
max-ageyou configured, and nothing longer. - Purge by tag:
httpcache.ini [HttpCacheSettings] TagHeader=xkeynames each page's purge tags in a header for the Varnish xkey module (Surrogate-Keyfor Fastly,Cache-Tagfor Cloudflare). The setting and the tags belong to the role-aware HTTP cache (HTTP cache); test that pages carry them. - Purge on publish yourself: the kernel emits
content/cachewith the node and object ids on every publish (kernel/classes/ezcontentcachemanager.php), which a listener insite.ini [Event] Listeners[]can turn into purges. - Drop the proxy cache and use Velocity's response cache or the HTTP cache, which purge themselves.
Checks and symptoms
With the site reachable directly, a forwarded header from a visitor must change nothing:
curl -s -o /dev/null -w '%{redirect_url}\n' -H 'X-Forwarded-Proto: https' http://example.com/user/logout
# expected: http://example.com/ -- https:// here means the header was believed| Symptom | Cause | Fix |
|---|---|---|
redirects and absolute links go to http:// | the proxy is not trusted, or sends no X-Forwarded-Proto | list it; set the header |
| every visitor has the proxy's address (sign-in lockout, logs) | the proxy is not trusted, or ClientIpByCustomHTTPHeader not set under PHP-FPM | list the proxy; set the header setting |
| a redirect loop after switching to HTTPS | the server does not know the visitor used HTTPS | as the first row |
| signed-in pages served to other visitors | a caching proxy stores pages with a session cookie | bypass the cache on the session cookie and is_logged_in |
| pages stay stale behind Varnish | Exponential sends no purges | one of the purge options above |
Checklist
- To do: The proxy sets
X-Forwarded-Protoand appends or setsX-Forwarded-For; passesHost. - To do: The proxy is trusted (
TrustedProxies[], orQ.webserver.proxy.trustedfor Velocity); the origin is not reachable around it. - To do: The curl check passes directly and through the proxy.
- To do: A caching proxy bypasses requests with the session cookie or
is_logged_in, and stores onlypublicpages. - To do: A purge strategy is chosen and written into the plan.
Related pages
- Velocity beside Apache or nginx
- HTTP cache headers
- CDN
- Forwarded headers are trusted only from configured proxies
- Free documentation: this page is free to copy, translate and improve under the GNU FDL 1.3.
Become an Exponential Partner
Partners share their information, provide funding, contribute back and develop the products with us, and that is how Exponential grows. A partner website and dashboard are planned for mid-2027: please become a partner today to speed up our growth rate and land new features faster.