This covers how to configure Apache to serve an Angular single-page application while proxying /api requests to a backend service over AJP. It handles three things that are easy to get wrong: Angular's client-side router, static file caching, and making sure API requests bypass the Angular rewrite rules.
Required Modules
Enable the proxy, AJP, rewrite, and headers modules, then restart Apache:
sudo a2enmod proxy proxy_ajp rewrite headers
sudo systemctl restart apache2
VirtualHost Configuration
<VirtualHost *:443>
ServerAdmin webmaster@example.com
DocumentRoot /var/www/my-app
ServerName my-app.example.com
ErrorLog \${APACHE_LOG_DIR}/my-app.error.log
CustomLog \${APACHE_LOG_DIR}/my-app.access.log combined
# Reverse proxy to backend (AJP)
ProxyRequests Off
ProxyPass "/api" "ajp://127.0.0.1:9298/api"
ProxyPassReverse "/api" "ajp://127.0.0.1:9298/api"
<Directory /var/www/my-app>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
# Handle Angular routing — exclude /api from rewrite
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/api
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ /index.html [QSA,L]
</Directory>
# Disable caching for index.html so Angular updates are picked up immediately
<filesMatch "\.(html)$">
FileETag None
<ifModule mod_headers.c>
Header unset ETag
Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
Header set Pragma "no-cache"
Header set Expires "Wed, 11 Jan 1984 05:00:00 GMT"
</ifModule>
</filesMatch>
SSLEngine on
SSLCertificateFile /opt/certs/example.crt
SSLCertificateKeyFile /opt/certs/example.key
</VirtualHost>
Key Points
ProxyRequests Off — disables forward proxy mode. This is required when using Apache as a reverse proxy. Without it, Apache could be exploited as an open proxy.
ProxyPass + ProxyPassReverse — both are needed. ProxyPass forwards the request to the backend; ProxyPassReverse rewrites any redirect URLs in the backend's response so they point back to the correct public address instead of the internal one.
+FollowSymLinks — required in the <Directory> block or Apache will refuse to evaluate RewriteRule directives.
RewriteCond %{REQUEST_URI} !^/api — prevents Angular's catch-all rewrite from intercepting API requests. Without this, every /api/... request would be silently rewritten to index.html instead of being proxied to the backend.
RewriteCond %{REQUEST_FILENAME} !-f — serves static files (JS, CSS, images, fonts) directly from disk without rewriting. Only requests for paths that don't map to real files get forwarded to index.html.
HTML cache headers — Angular's build output includes content-hashed filenames for JS and CSS bundles, so those can be cached aggressively by the browser. index.html itself has no hash, so browsers will cache the old version indefinitely unless you explicitly disable caching on it. The <filesMatch> block forces the browser to always revalidate index.html.
Build and Deploy
# Build the Angular app for production
ng build --configuration production
# Copy the build output to the web root
cp -r dist/my-app/ /var/www/my-app/
# Fix ownership and permissions
sudo chown -R www-data:www-data /var/www/my-app
sudo chmod -R 755 /var/www/my-app
Deploying to a Subdirectory
If the app is not at the root of the domain (e.g. example.com/my-app/ instead of example.com/), set the base href at build time so Angular generates the correct asset paths and router links:
ng build --base-href /my-app/