Apache is usually thought of as a server for PHP sites, but with mod_proxy it is also a capable reverse proxy. If your server already runs Apache, you can publish a Node.js, Python, Go or Java app on its own domain or path with a few lines, keeping one web server for everything.
Before you start
- Apache 2.4 on a VPS with root access, and the app listening locally, e.g. on
127.0.0.1:3000. - A domain pointing at the server. For the Nginx equivalent see Nginx reverse proxy.
Enable the proxy modules
sudo a2enmod proxy proxy_http proxy_wstunnel headers rewrite ssl
sudo systemctl restart apache2On AlmaLinux/Rocky these modules are loaded by default from /etc/httpd/conf.modules.d/00-proxy.conf.
Proxy a whole domain
<VirtualHost *:80>
ServerName app.example.com
ProxyPreserveHost On
ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
RequestHeader set X-Forwarded-Proto expr=%{REQUEST_SCHEME}
ErrorLog ${APACHE_LOG_DIR}/app-error.log
CustomLog ${APACHE_LOG_DIR}/app-access.log combined
</VirtualHost>ProxyPassforwards requests to the backend.ProxyPassReverserewritesLocationheaders in backend redirects so users never see127.0.0.1:3000.ProxyPreserveHost Onpasses the original Host header, which many apps need to build URLs.ProxyRequests Offkeeps Apache from acting as an open forward proxy. Never turn it on for a public server.- mod_proxy adds
X-Forwarded-ForandX-Forwarded-Hostautomatically; theRequestHeaderline adds the scheme.
Enable the site, test with apachectl configtest, reload, then add HTTPS with sudo certbot --apache -d app.example.com.
Proxy only a path
ProxyPass /api/ http://127.0.0.1:4000/
ProxyPassReverse /api/ http://127.0.0.1:4000/Keep the trailing slashes consistent on both sides. To exclude a path from a broader rule, put an exclusion first: ProxyPass /static/ !.
Proxy WebSockets
Apache 2.4.47+ can upgrade connections directly:
ProxyPass / http://127.0.0.1:3000/ upgrade=websocketOn older versions, use mod_rewrite to send upgrade requests to ws://:
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) ws://127.0.0.1:3000/$1 [P,L]Timeouts and connection reuse
ProxyPass / http://127.0.0.1:3000/ timeout=120 keepalive=On retry=0timeout: seconds to wait for the backend; raise for slow reports or exports.retry=0: retry a failed backend immediately instead of waiting 60 seconds.
Balance across several backends
sudo a2enmod proxy_balancer lbmethod_byrequests<Proxy "balancer://appcluster">
BalancerMember http://10.0.0.11:3000
BalancerMember http://10.0.0.12:3000
ProxySet lbmethod=byrequests
</Proxy>
ProxyPass / balancer://appcluster/
ProxyPassReverse / balancer://appcluster/Other methods: bytraffic, bybusyness (fewest active requests), heartbeat.
Troubleshooting
- 503 Service Unavailable: the backend is down or the port is wrong. Check
ss -tlpn. - AlmaLinux: "(13)Permission denied: AH00957": SELinux blocks Apache's outgoing connections. Run
sudo setsebool -P httpd_can_network_connect 1. - Redirects show the backend address:
ProxyPassReversemissing or paths don't match. - WebSocket connection fails: proxy_wstunnel or the upgrade option missing.
FAQ: Apache reverse proxy
Is Apache slower than Nginx as a proxy?
With the event MPM the difference is small for most sites. Nginx uses less memory at very high connection counts.
Can I proxy apps on cPanel hosting?
cPanel offers its own Node.js and Python app tools; see Node.js behind Nginx, Apache or LiteSpeed.
Do I need ProxyPassReverse?
Yes whenever the backend sends redirects.
