Advanced Apache Configurations for Modern Web Apps

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:

💡 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:

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:

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>

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:

<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:


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:

aria

© 2025 Aria

Instagram 𝕏 GitHub