Apache can act as a reverse proxy, forwarding requests to a backend application server (Node.js, a Python app, another internal service) while presenting a unified public-facing domain and handling SSL termination.
<VirtualHost *:443>
ServerName app.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/app.example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.example.com/privkey.pem
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
# Needed for WebSocket support (e.g. a Node.js app using Socket.IO)
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:3000/$1 [P,L]
</VirtualHost>
sudo a2enmod proxy proxy_http proxy_wstunnel rewrite sudo systemctl restart apache2
ProxyPreserveHost On matters more than it looks — without it, the backend application sees Apache’s internal hostname instead of the real public domain, which breaks anything relying on the Host header (redirects, generated URLs, some auth logic).