Velocity beside Apache or nginx
This page is for system administrators at hosting companies whose machines already run Apache or nginx and who add Velocity to them. It answers: which shapes work, what each needs (trusted proxies, one user for the files, shared sessions, one deploy), and how to check that a forwarded header cannot be forged. The sources are Chapter 8, sections 8.7 and 8.8 and Forwarded headers are trusted only from configured proxies.
Contents
- The three shapes
- Shape 1: a proxy in front of Velocity
- Who trusts the proxy
- Shape 2: both serving the same installation
- Shape 3: side by side for a comparison
- Certificates behind a proxy
- Checks
- Checklist
The three shapes
| Shape | When | What Velocity binds |
|---|---|---|
| 1. Apache or nginx in front of Velocity | one public address carries several services; several customers' Velocity instances behind one front server; Velocity must run as an ordinary user on a high port | Host=127.0.0.1 (shipped), a high port |
| 2. Velocity and Apache/nginx serving the same files | a migration from PHP-FPM to Velocity, with the old server left in place on the same files | its own ports |
| 3. Both on their own ports for a test | comparing engines on the customer's own pages | its own ports |
Velocity does not need a proxy for TLS; it serves HTTPS itself. A proxy is still the right shape for the cases above or when a load balancer or CDN already terminates TLS.
Shape 1: a proxy in front of Velocity
nginx in front of Velocity (derived example from chapter 8):
location / {
proxy_pass http://127.0.0.1:8088;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
# appending is safe: Exponential reads the list from the right
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
}Apache in front of Velocity (derived example; needs mod_proxy, mod_proxy_http and mod_headers):
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8088/
ProxyPassReverse / http://127.0.0.1:8088/
RequestHeader set X-Forwarded-Proto "https"For several customers on one machine, give each customer's Velocity its own port and its own worker user, and one
front-server virtual host per customer pointing at that port. The proxy passes the original Host header; Velocity
does not use X-Forwarded-Host.
Who trusts the proxy
Two lists, because Velocity decides before any PHP of the application runs:
| List | Where | Default | Decides |
|---|---|---|---|
Q.webserver.proxy.trusted | the engine's configuration (a snippet in /etc/vc/conf-available/) | 127.0.0.1, ::1 | under Velocity: the visitor's address and whether the request is HTTPS |
[HTTPHeaderSettings] TrustedProxies[] | settings/override/site.ini.append.php | 127.0.0.1, ::1 | under Apache or nginx with PHP-FPM: which forwarded headers the kernel believes |
A proxy on the same machine is covered by both defaults. A proxy on another address must be added. For Velocity:
cat > /etc/vc/conf-available/reverse-proxy.conf <<'EOF'
{ "Q": { "webserver": { "proxy": { "trusted": ["127.0.0.1", "::1", "192.0.2.10", "10.0.0.0/8"] } } } }
EOF
php bin/php/console exp:velocity conf enable --apply reverse-proxy --allow-root-user
php bin/php/console exp:velocity restart --allow-root-userFrom a trusted proxy, Velocity takes the visitor's address from X-Forwarded-For (read from the right) and hands it
to the kernel as REMOTE_ADDR, so ClientIpByCustomHTTPHeader is not needed under Velocity. It treats a plain
connection as HTTPS when a trusted proxy sends X-Forwarded-Proto: https, CloudFront-Forwarded-Proto: https or
CF-Visitor: {"scheme":"https"}.
Warning
Reading the forwarded protocol only from trusted proxies arrived in Exponential Velocity 0.0.4.44. Up to and
including 0.0.4.43, a visitor on a plain listener could make a request look like HTTPS. Check with
composer show se7enxweb/exponential-velocity; on an older engine keep Velocity's plain port unreachable from
outside whenever a proxy is in front (Chapter 8, section 8.8).
Shape 2: both serving the same installation
When Apache (PHP-FPM) and Velocity serve the same installation, they share var/, the caches and the sessions
(Chapter 8, One user, or one group):
- One user. Give the PHP-FPM pool and Velocity's workers the same user (
[ServerSettings] UserandGroup), or a common group with group-writable, setgid directories and the[FileSettings]permissions of chapter 8. Test with both running and look at the owner of new files invar/after a few requests. - One deploy.
exp:velocity deployreloads the PHP-FPM that serves the site ([DeploySettings] PhpFpmService=autofinds it) and restarts Velocity, then clears the rendered-output caches. Do not setPhpFpmService=disabledin this shape. - Sessions. With the role-aware HTTP cache on, both servers must keep sessions in the same directory: the PHP-FPM
pool's
session.save_pathand Velocity's[PHPSettings] IniOptions[]=session.save_path=0;0660;<dir>(HTTP cache). - Caches. Clearing a cache in Setup > Caches reaches both, because the files are shared. Two things do not:
Velocity keeps settings in memory (restart it after clearing INI caches) and answers pages from its own response
cache (
exp:velocity cache clear) (Caches). - OPcache and APCu belong to each server's own processes.
Shape 3: side by side for a comparison
Each engine has its own port, pid file and logs, so Velocity can run next to the existing server for a test. Then measure both back to back:
php bin/php/console exp:benchmark --compare=https://www.example.com,https://www.example.com:8080 --allow-root-user
php bin/php/console exp:benchmark --compare=https://www.example.com,https://www.example.com:8080 --cold --runs=30 --allow-root-userCompare only rows with the same cache answer: a cache hit against a render is not a comparison of engines
(Benchmarking a hosted site).
Certificates behind a proxy
The proxy owns port 80, so Velocity's ACME HTTP-01 challenge cannot reach it directly. Either let the proxy obtain
the certificate, or forward /.well-known/acme-challenge/ to Velocity, or set the engine's acme.webroot to a
directory the proxy serves (Velocity HTTPS certificates).
Checks
A forwarded header sent by a visitor must change nothing. Against the site directly (no proxy):
curl -s -o /dev/null -w '%{redirect_url}\n' -H 'X-Forwarded-Proto: https' http://example.com/user/logout
# expected: http://example.com/ -- an https:// address here means the header was believedThrough the proxy, absolute links and redirects must carry https://, and the debug bar's "Is my address listed?"
shows the address the kernel uses for the visitor.
| 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 in the proxy |
| every visitor has the proxy's address | the proxy is not trusted (or, under PHP-FPM, ClientIpByCustomHTTPHeader not set) | list the proxy; set the header setting |
| a redirect loop after switching to HTTPS | the proxy speaks HTTPS to the visitor and plain HTTP to the server, and the server does not know | as the first row |
root-owned files appear in var/ | a script or a server runs as root | run everything as the site's user |
Checklist
- To do: Velocity bound to
127.0.0.1when a proxy is in front; its plain port not reachable from outside. - To do: Engine 0.0.4.44 or later; the proxy listed in
Q.webserver.proxy.trusted(andTrustedProxies[]for PHP-FPM). - To do: The proxy sets
X-Forwarded-Protoand passesHost; the curl check passes. - To do: In shape 2: one user for both servers;
deploy --dry-runshows the FPM reload and the Velocity restart. - To do: Certificate route decided (proxy, forwarded challenge or
acme.webroot).
Related pages
- Velocity for hosts
- Varnish and reverse proxies
- Apache with PHP-FPM
- nginx with PHP-FPM
- 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.