Table of Contents

Reverse Proxy

A reverse proxy sits in front of one or more backend servers, forwarding client requests to them and returning their responses. Apache implements this with the mod_proxy family of modules. Benefits include SSL/TLS termination, hiding backend topology, centralized logging, caching, and routing multiple applications behind one entry point.

Enabling the Proxy Modules

Reverse proxying needs mod_proxy and a protocol handler (mod_proxy_http for HTTP backends). Enable them in httpd.conf:

LoadModule proxy_module      modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module    modules/mod_headers.so
Warning

Never leave Apache configured as an open (forward) proxy. Ensure ProxyRequests Off is set (it is the default). ProxyRequests On combined with a permissive <Proxy> block would let anyone relay arbitrary traffic through your server.

Initial Setup

Create a project directory with a place for the Apache config and two backend web roots:

# Create the project structure
mkdir apache-demo && cd apache-demo
mkdir app1 app2 apache

# Simple backend content
echo '<h1>App 1</h1>' > app1/index.html
echo '<h1>App 2</h1>' > app2/index.html

Basic Reverse Proxy Configuration

Create apache/httpd.conf. This forwards all requests to a single backend:

# apache/httpd.conf
ServerRoot "/usr/local/apache2"
Listen 80
ServerName proxy.example.com

# Required modules
LoadModule mpm_event_module  modules/mod_mpm_event.so
LoadModule proxy_module      modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule headers_module    modules/mod_headers.so
LoadModule unixd_module      modules/mod_unixd.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule log_config_module modules/mod_log_config.so
LoadModule mime_module       modules/mod_mime.so

User daemon
Group daemon

# Do not act as a forward proxy
ProxyRequests Off
<Proxy *>
    Require all granted
</Proxy>

# Pass the original Host and client details to the backend
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "http"

# Forward everything to app1
ProxyPass        "/" "http://app1:80/"
ProxyPassReverse "/" "http://app1:80/"

# Logging
ErrorLog /proc/self/fd/2
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /proc/self/fd/1 combined

Reverse-Proxy Directive Reference

Directive Purpose
ProxyRequests Off Disables the forward proxy. Required for a secure reverse proxy.
ProxyPass "/" "http://app1:80/" Maps a URL path to a backend. Requests for / are forwarded to app1.
ProxyPassReverse Rewrites Location, Content-Location, and URI headers in backend responses so redirects point at the proxy, not the backend.
ProxyPreserveHost On Sends the client's original Host header to the backend instead of the backend's hostname — the backend can then select the right virtual host.
<Proxy *> Require all granted Access control for proxied requests. Restrict this in production (e.g. Require ip 10.0.0.0/8).

Headers passed to the backend

mod_proxy automatically adds these request headers when forwarding:

Header Set by Meaning
X-Forwarded-For mod_proxy (automatic) The client IP, appended to any existing chain of proxies.
X-Forwarded-Host mod_proxy (automatic) The original Host requested by the client.
X-Forwarded-Server mod_proxy (automatic) The hostname of the proxy server itself.
X-Forwarded-Proto you (via RequestHeader) Whether the client used http or https. Not automatic — set it explicitly, especially on the HTTPS vhost (RequestHeader set X-Forwarded-Proto "https").
Note

ProxyPreserveHost On forwards the client Host, but the X-Forwarded-* headers are conventions the backend must be configured to trust. Only accept them from proxies you control — a client connecting directly can forge them.

Docker Compose Configuration

Run Apache in front of two containerized backends:

# docker-compose.yml
services:
  app1:
    image: httpd:2.4
    container_name: app1
    volumes:
      - ./app1:/usr/local/apache2/htdocs:ro
    networks:
      - web

  app2:
    image: httpd:2.4
    container_name: app2
    volumes:
      - ./app2:/usr/local/apache2/htdocs:ro
    networks:
      - web

  proxy:
    image: httpd:2.4
    container_name: apache-proxy
    ports:
      - "80:80"
    volumes:
      - ./apache/httpd.conf:/usr/local/apache2/conf/httpd.conf:ro
    depends_on:
      - app1
      - app2
    networks:
      - web

networks:
  web:
    driver: bridge

Start it and verify:

docker compose up -d
docker compose ps

# The proxy forwards to app1
curl http://localhost/

# Validate the Apache config without restarting
docker exec apache-proxy httpd -t

Name-Based Virtual Hosts

To serve multiple sites/apps from one Apache instance, use name-based virtual hosts that route by Host header. Replace the single ProxyPass with per-host <VirtualHost> blocks:

# apache/httpd.conf (virtual host section)
ProxyRequests Off
ProxyPreserveHost On

<VirtualHost *:80>
    ServerName app1.example.com
    ProxyPass        "/" "http://app1:80/"
    ProxyPassReverse "/" "http://app1:80/"
    RequestHeader set X-Forwarded-Proto "http"
</VirtualHost>

<VirtualHost *:80>
    ServerName app2.example.com
    ProxyPass        "/" "http://app2:80/"
    ProxyPassReverse "/" "http://app2:80/"
    RequestHeader set X-Forwarded-Proto "http"
</VirtualHost>

Test with explicit Host headers (or add entries to /etc/hosts):

curl -H "Host: app1.example.com" http://localhost/
curl -H "Host: app2.example.com" http://localhost/

Path-Based Routing

To route by URL path instead of hostname, map multiple ProxyPass directives within one virtual host. Order matters — most specific paths first:

<VirtualHost *:80>
    ServerName example.com

    ProxyPreserveHost On

    # More specific paths must come before "/"
    ProxyPass        "/api/"  "http://api-backend:8080/"
    ProxyPassReverse "/api/"  "http://api-backend:8080/"

    ProxyPass        "/app/"  "http://app-backend:3000/"
    ProxyPassReverse "/app/"  "http://app-backend:3000/"

    # Catch-all last
    ProxyPass        "/" "http://web-backend:80/"
    ProxyPassReverse "/" "http://web-backend:80/"
</VirtualHost>
Tip

Exclude a path from proxying with ProxyPass "/static/" "!" — useful when Apache should serve some paths locally (for example static assets) while proxying the rest.

WebSocket Proxying

WebSocket backends need mod_proxy_wstunnel and a rewrite that upgrades matching requests:

LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
LoadModule rewrite_module        modules/mod_rewrite.so

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

    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} =websocket [NC]
    RewriteRule ^/?(.*) "ws://backend:8080/$1" [P,L]

    ProxyPass        "/" "http://backend:8080/"
    ProxyPassReverse "/" "http://backend:8080/"
</VirtualHost>

◄ Overview · Apache Overview · Load Balancing ►