DevOpsil
Apache
92%
Needs Review

Apache as a Reverse Proxy: mod_proxy Configuration Guide

Dev PatelDev Patel5 min read

Running Apache as a reverse proxy is one of the most common production patterns for serving web applications. You get a battle-tested HTTP server handling SSL termination, compression, and caching in front of your Node.js, Python, Java, or any other backend. This guide covers practical mod_proxy configuration you can deploy today.

Why Apache as a Reverse Proxy?

Your backend application server (Gunicorn, Express, Tomcat) is good at running application code — not so good at serving the raw internet. Apache sits in front and handles:

  • SSL/TLS termination so your backend speaks plain HTTP internally
  • Static file serving without touching your app process
  • Request buffering to protect slow backends from fast clients
  • Header manipulation and URL rewriting
  • Access control and rate limiting at the edge

Enabling the Required Modules

Apache ships with proxy modules disabled. Enable what you need:

# On Debian/Ubuntu
sudo a2enmod proxy proxy_http proxy_balancer lbmethod_byrequests headers rewrite
sudo systemctl restart apache2

# On RHEL/CentOS/Rocky — edit /etc/httpd/conf.modules.d/00-proxy.conf
# Uncomment these lines:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so

sudo systemctl restart httpd

Verify modules are loaded:

apache2ctl -M | grep proxy
# Expected output:
# proxy_module (shared)
# proxy_http_module (shared)
# proxy_balancer_module (shared)

Basic Reverse Proxy Configuration

The simplest case: forward all requests to a single backend.

<VirtualHost *:80>
    ServerName app.example.com

    # Disable forward proxying — critical for security
    ProxyRequests Off

    # Preserve the original Host header
    ProxyPreserveHost On

    # Pass X-Forwarded-For so your app sees real client IPs
    RequestHeader set X-Forwarded-For "%{REMOTE_ADDR}s"
    RequestHeader set X-Forwarded-Proto "http"

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    # Timeout settings
    ProxyTimeout 60

    ErrorLog  /var/log/apache2/app-error.log
    CustomLog /var/log/apache2/app-access.log combined
</VirtualHost>

ProxyPassReverse rewrites Location, Content-Location, and URI headers in responses — without it, redirects from your backend will send clients directly to the internal address.

Path-Based Routing to Multiple Backends

Route different URL paths to different services — useful for a microservices setup or for hosting a separate API alongside a frontend:

<VirtualHost *:443>
    ServerName platform.example.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/platform.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/platform.example.com/privkey.pem

    ProxyRequests Off
    ProxyPreserveHost On

    # API service on port 8080
    ProxyPass        /api/ http://127.0.0.1:8080/api/
    ProxyPassReverse /api/ http://127.0.0.1:8080/api/

    # Admin panel on port 9000
    ProxyPass        /admin/ http://127.0.0.1:9000/
    ProxyPassReverse /admin/ http://127.0.0.1:9000/

    # Everything else goes to the frontend (port 3000)
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    # Strip internal headers before passing upstream
    RequestHeader unset X-Internal-Token

    # Set custom headers for backend identification
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Real-IP "%{REMOTE_ADDR}s"
</VirtualHost>

Order matters here: Apache matches ProxyPass directives top-to-bottom. Put more specific paths before general ones.

Load Balancing Across Multiple Backend Instances

For production deployments running multiple backend processes, use mod_proxy_balancer:

<VirtualHost *:443>
    ServerName api.example.com

    SSLEngine on
    SSLCertificateFile    /etc/letsencrypt/live/api.example.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/api.example.com/privkey.pem

    # Define the backend pool
    <Proxy "balancer://api_cluster">
        BalancerMember http://10.0.1.10:8080 loadfactor=1
        BalancerMember http://10.0.1.11:8080 loadfactor=1
        BalancerMember http://10.0.1.12:8080 loadfactor=2  # gets twice the traffic

        # Balancing algorithm
        ProxySet lbmethod=byrequests

        # Mark a member hot-standby (only used if all others fail)
        # BalancerMember http://10.0.1.13:8080 status=+H
    </Proxy>

    ProxyRequests Off
    ProxyPreserveHost On

    ProxyPass        / balancer://api_cluster/
    ProxyPassReverse / balancer://api_cluster/

    # Balancer manager UI — restrict to internal IPs only
    <Location "/balancer-manager">
        SetHandler balancer-manager
        Require ip 10.0.0.0/8
        Require ip 127.0.0.1
    </Location>
</VirtualHost>

Balancing Methods Comparison

MethodDirectiveBest For
Round robinbyrequestsUniform, stateless requests
Least connectionsbybusynessVariable request duration
Weightedbyrequests + loadfactorMixed-capacity backends
Consistent hashbytrafficSoft session affinity needs

Sticky Sessions with Session Persistence

If your application isn't fully stateless, use sticky sessions to route the same client to the same backend:

<Proxy "balancer://sticky_cluster">
    BalancerMember http://10.0.1.10:8080 route=node1
    BalancerMember http://10.0.1.11:8080 route=node2

    ProxySet lbmethod=byrequests
    ProxySet stickysession=ROUTEID
</Proxy>

ProxyPass        / balancer://sticky_cluster/
ProxyPassReverse / balancer://sticky_cluster/

Apache reads the ROUTEID cookie and routes accordingly. Your backend must set this cookie — or use mod_headers to inject it.

WebSocket Proxying

Modern apps often need WebSocket support. Enable mod_proxy_wstunnel:

sudo a2enmod proxy_wstunnel
# Upgrade WebSocket connections
RewriteEngine On
RewriteCond %{HTTP:Upgrade} =websocket [NC]
RewriteRule /ws/(.*) ws://127.0.0.1:3000/ws/$1 [P,L]

# Regular HTTP traffic
ProxyPass        / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/

Timeout and Buffer Tuning

Default timeouts kill long-running requests. Tune these in your VirtualHost or globally in apache2.conf:

# How long to wait for a backend to accept a connection
ProxyTimeout 120

# How long to wait for backend response headers
# Set via the timeout parameter on BalancerMember
<Proxy "balancer://api_cluster">
    BalancerMember http://10.0.1.10:8080 timeout=30 retry=5
</Proxy>

# Buffer entire request body before sending upstream
# Prevents slow-client attacks against your backend
EnableSendfile Off
ProxyIOBufferSize 65536

Debugging Proxy Issues

When something isn't routing correctly, these commands help:

# Test config syntax before reloading
apache2ctl configtest

# Watch access logs in real time
tail -f /var/log/apache2/app-access.log

# Check which virtual host is matching a request
apache2ctl -t -D DUMP_VHOSTS

# Test headers being sent to backend (install curl)
curl -v -H "Host: app.example.com" http://127.0.0.1:3000/healthz

# Check the balancer manager (if configured)
curl http://localhost/balancer-manager

Enable proxy debug logging temporarily when investigating issues:

# In your VirtualHost — remove in production
LogLevel proxy:debug proxy_http:trace3

Security Checklist

Before going live, verify these settings:

CheckSettingValue
Forward proxy disabledProxyRequestsOff
Internal headers strippedRequestHeader unset X-Internal-*Applied
Backend not directly accessibleFirewall rulesPort 8080 blocked externally
Balancer manager restrictedRequire ipInternal IPs only
TRACE method disabledTraceEnableOff
Server tokens hiddenServerTokensProd

Add these global security settings to your main Apache config:

# /etc/apache2/conf-available/security.conf
ServerTokens Prod
ServerSignature Off
TraceEnable Off
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options SAMEORIGIN

With this configuration in place, Apache handles the edge work reliably while your backend services focus on application logic.

Share:

Was this article helpful?

Dev Patel
Dev Patel

Cloud Cost Optimization Specialist

I find the money your cloud is wasting. FinOps practitioner, data-driven analyst, and the person your CFO wishes they'd hired sooner. Every dollar saved is a dollar earned.

Related Articles

More in Apache

View all →
ApacheQuick RefBeginnerNeeds Review

Fix Apache mod_rewrite Not Working

Diagnose and fix Apache mod_rewrite rules not being applied, covering module loading, AllowOverride settings, .htaccess issues, and rewrite logging.

Aareez Asif·
3 min read

Discussion