Most tutorials stop at a basic DocumentRoot and a2ensite.
In this post, I’m collecting advanced Apache configurations I actually use on a VPS for modern JS/TS and backend apps.
The goal is to have a few copy‑pasteable templates you can adapt:
- Multiple virtual hosts (domains & subdomains)
- Reverse proxy to Node/JS apps
- Force HTTPS and redirects
- Security headers (CSP, HSTS, etc.)
- Compression & caching for performance
💡 All examples assume Ubuntu/Debian with
/etc/apache2/layout.
1. Multiple sites with name-based virtual hosts
Most of the time, I host several projects on the same VPS:
example.com→ main site (maybe static / landing)blog.example.com→ blog (Astro / static)app.example.com→ app (Node/SSR behind proxy)
Apache handles this with separate virtual host files.
Example: two simple static sites
/etc/apache2/sites-available/example.com.conf:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/dist
<Directory /var/www/example.com/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>
/etc/apache2/sites-available/blog.example.com.conf:
<VirtualHost *:80>
ServerName blog.example.com
DocumentRoot /var/www/blog/dist
<Directory /var/www/blog/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/blog_error.log
CustomLog ${APACHE_LOG_DIR}/blog_access.log combined
</VirtualHost>
Enable both:
sudo a2ensite example.com.conf
sudo a2ensite blog.example.com.conf
sudo systemctl reload apache2
Apache will serve each site based on the Host header (the domain you hit).
2. Reverse proxy to a Node/JS app
For SSR apps or APIs (Next.js, NestJS, Express, etc.), I often run the app on an internal port, e.g.:
# Inside app folder
npm install
npm run build
npm start # listening on 127.0.0.1:3000
Then I put Apache in front.
Enable proxy modules
sudo a2enmod proxy proxy_http
sudo systemctl restart apache2
Virtual host that proxies everything to Node
/etc/apache2/sites-available/app.example.com.conf:
<VirtualHost *:80>
ServerName app.example.com
# Preserve original host header (useful for apps that rely on it)
ProxyPreserveHost On
# Option 1: simple reverse proxy for the whole site
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
ErrorLog ${APACHE_LOG_DIR}/app_error.log
CustomLog ${APACHE_LOG_DIR}/app_access.log combined
</VirtualHost>
If you only want to proxy a sub-path (e.g. /api), and keep static files in Apache:
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/dist
<Directory /var/www/example.com/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# Proxy only /api to Node on port 4000
ProxyPreserveHost On
ProxyPass /api http://127.0.0.1:4000/api
ProxyPassReverse /api http://127.0.0.1:4000/api
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>
3. Force HTTPS and redirect all HTTP to HTTPS
Once HTTPS is enabled (with Let’s Encrypt for example), I like to:
- Serve HTTPS in the main vhost
- Redirect all HTTP to HTTPS, including
www→ root domain if needed
Step 1: HTTPS vhost (simplified)
When you use Certbot, it usually creates a :443 vhost like:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/dist
<Directory /var/www/example.com/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# (We'll add security headers later)
</VirtualHost>
Step 2: HTTP vhost that only redirects
For :80, I use a minimal config that just redirects to HTTPS:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
RewriteEngine On
RewriteRule ^/(.*)$ https://example.com/$1 [R=301,L]
</VirtualHost>
- All HTTP traffic → HTTPS
- Redirect from
www.example.com→https://example.com
Don’t forget to enable mod_rewrite:
sudo a2enmod rewrite
sudo systemctl restart apache2
4. Security headers with mod_headers
This is where we can add some basic hardening: HSTS, clickjacking protection, XSS filters, etc.
Enable headers module:
sudo a2enmod headers
sudo systemctl restart apache2
Then, inside your HTTPS virtual host (:443):
<VirtualHost *:443>
ServerName example.com
# ...
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "1; mode=block"
# HSTS: force HTTPS for this domain (and optionally subdomains)
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Optional: basic Content-Security-Policy example
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline';"
# ...
</VirtualHost>
⚠️ CSP warning: Content-Security-Policy can break your site if you load scripts from CDNs or inline JS. Adjust according to your real needs.
5. Compression (gzip / Brotli)
Compression reduces bandwidth and speeds up responses for text assets (HTML, CSS, JS, JSON, etc.).
Enable mod_deflate (gzip)
sudo a2enmod deflate
sudo systemctl restart apache2
I usually create a small config file:
/etc/apache2/conf-available/compression.conf:
<IfModule mod_deflate.c>
# Compress HTML, CSS, JS, JSON, XML, etc.
AddOutputFilterByType DEFLATE text/html text/plain text/xml
AddOutputFilterByType DEFLATE text/css text/javascript
AddOutputFilterByType DEFLATE application/javascript application/x-javascript
AddOutputFilterByType DEFLATE application/json application/xml
AddOutputFilterByType DEFLATE application/rss+xml application/atom+xml
</IfModule>
Enable it:
sudo a2enconf compression
sudo systemctl reload apache2
(Optional) Brotli
If your Apache build has mod_brotli:
sudo a2enmod brotli
sudo systemctl restart apache2
Then configure:
<IfModule mod_brotli.c>
BrotliCompressionQuality 5
AddOutputFilterByType BROTLI_COMPRESS text/html text/plain text/xml text/css text/javascript application/javascript application/json
</IfModule>
You can keep both gzip and Brotli; browsers will use what they support.
6. Basic caching for static assets
Apache can send Cache-Control and Expires headers so that browsers cache static files like JS, CSS, images, fonts.
I like to put this in a config file or inside the vhost:
# Long cache for static assets (adjust paths/extensions as you like)
<IfModule mod_expires.c>
ExpiresActive On
# Default: no cache
ExpiresDefault "access plus 0 seconds"
# Static assets: 1 month
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 month"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|png|jpe?g|svg|woff2)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
</IfModule>
Enable mod_expires:
sudo a2enmod expires
sudo systemctl restart apache2
Note: use cache-busting in your filenames (e.g. app.1234abcd.js) so you can cache assets aggressively.
7. Combining static site + reverse proxy + HTTPS
Here’s a more complete example that combines many things:
- HTTPS with Let’s Encrypt
- Static files from
/var/www/example.com/dist /apiproxied to Node at127.0.0.1:4000- Security headers
- Compression + caching
<VirtualHost *:443>
ServerName example.com
DocumentRoot /var/www/example.com/dist
<Directory /var/www/example.com/dist>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# SSL
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Proxy /api to Node/JS backend
ProxyPreserveHost On
ProxyPass /api http://127.0.0.1:4000/api
ProxyPassReverse /api http://127.0.0.1:4000/api
# Security headers
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# (Optional) CSP - adjust carefully
# Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline';"
# Compression (assuming mod_deflate + mod_brotli configured globally)
# Caching for static assets
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
ExpiresByType font/woff2 "access plus 1 month"
</IfModule>
<IfModule mod_headers.c>
<FilesMatch "\.(js|css|png|jpe?g|svg|woff2)$">
Header set Cache-Control "public, max-age=2592000, immutable"
</FilesMatch>
</IfModule>
ErrorLog ${APACHE_LOG_DIR}/example_error.log
CustomLog ${APACHE_LOG_DIR}/example_access.log combined
</VirtualHost>
Then add a minimal :80 vhost that just redirects to HTTPS:
<VirtualHost *:80>
ServerName example.com
RewriteEngine On
RewriteRule ^/(.*)$ https://example.com/$1 [R=301,L]
</VirtualHost>
8. Advanced Apache cheat sheet (what I enable)
On a fresh VPS, when I know I’ll host multiple modern web apps, I usually end up with:
# Core modules
sudo a2enmod rewrite headers proxy proxy_http expires deflate
# (Optional) if available
sudo a2enmod brotli
# Custom confs
sudo a2enconf compression # if I created /etc/apache2/conf-available/compression.conf
sudo systemctl restart apache2
Then I manage:
- One vhost per domain/subdomain
- HTTPS for each with Certbot
- Reverse proxy for any Node/JS backends
- Security headers and caching in HTTPS vhosts
This is the level of Apache configuration that feels “advanced” enough to be powerful, but still simple enough to maintain on a VPS without going crazy.
In future posts, I might go deeper into:
- Load balancing with
mod_proxy_balancer - Using Apache behind a CDN
- Tuning
mpm_eventvsmpm_preforkfor performance