Proxy sites
A proxy site gives a dev server a real hostname. Your Vite, Next.js, Rails or Bun process
keeps running on localhost:3000 exactly as it does now; Apache sits in front of it and
answers for myapp.test, on 80 and — if you want it — on 443.
This is what you want whenever the thing serving your project is a process rather than a folder.
Why bother
Cookies and storage behave. localhost:3000 and localhost:5173 share an origin for
some purposes and not others. Separate hostnames make each project a real, separate origin.
HTTPS without touching your dev server. Apache terminates TLS and forwards plain HTTP
to your process. Nothing in your project needs a certificate, a proxy flag or a
--experimental-https switch.
Secure-context APIs work. Service workers, the clipboard API, camera and microphone,
and Secure cookies all want a secure origin. https://myapp.test is one.
OAuth callbacks and webhooks get a stable URL that does not change when a port does.
Creating one
Press New, set the ServerName, choose Proxy, and give it the port your dev server listens on. That is the whole form — no document root, because Apache never touches disk for this site.
Start order does not matter. Apache will return 503 for as long as nothing is listening on the port, and start forwarding the moment your dev server comes up.
What gets written
<VirtualHost *:80>
ServerName myapp.test
ProxyPreserveHost On
ProxyRequests Off
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
ProxyTimeout 300
LogLevel warn
ErrorLog "/opt/homebrew/var/log/httpd/myapp_error.log"
CustomLog "/opt/homebrew/var/log/httpd/myapp_access.log" common
</VirtualHost>
ProxyPreserveHost On forwards the original Host header, so your framework sees
myapp.test and generates links with it rather than localhost.
The two RewriteCond lines catch WebSocket upgrades and hand them to mod_proxy_wstunnel,
so hot module reload keeps working. This is the part people usually get wrong by hand.
ProxyTimeout 300 is five minutes, which is enough for a slow first compile or a long
streaming response.
Modules it needs
Proxying needs four Apache modules: mod_proxy, mod_proxy_http, mod_proxy_wstunnel and
mod_rewrite. The Apache panel shows which are loaded and turns the missing ones on —
see Apache and services.
If a proxy site returns 500 and the error log mentions an invalid ProxyPass or an unknown
RewriteRule, a module is commented out.
HTTPS in front of a proxy
Enabling HTTPS adds a :443 block that terminates TLS and forwards to the same port, with
one addition:
RequestHeader set X-Forwarded-Proto "https"
Read that header in your framework so it knows to generate https:// URLs and to mark
cookies Secure. Most frameworks need to be told to trust it — Laravel's TrustProxies,
Express's trust proxy, Next.js behind a proxy, and so on.
The connection between Apache and your dev server stays plain HTTP on localhost, which is what you want: your dev server never needs a certificate.
When the upstream is missing
Vhostly watches for this specifically. If a proxy site is enabled and nothing is listening on its port, you get a warning naming the site and the port —
myapp.test forwards to localhost:3000, but nothing is listening there
It is a notification, not an error: most of the time it just means you have not started the dev server yet. Turn it off under Settings → Tell me when a proxy host points at a port nothing is listening on if you would rather not hear about it.
The Ports panel is where you check the other direction: what is actually holding a port right now. See ports.