Skip to content

Reverse Proxy

Overview

A reverse proxy is a server that sits in front of one or more backend servers and forwards client requests to them. Unlike a forward proxy (which acts on behalf of clients), a reverse proxy acts on behalf of servers — clients interact with the proxy without knowing the actual backend.

Reverse proxies provide several benefits: security (backend servers are not directly exposed), TLS termination (the proxy handles encryption), port normalization (internal services on arbitrary ports appear on standard ports 80/443), load balancing (distributing requests across multiple backends), and unified logging.

How It Works

Forward Proxy vs Reverse Proxy

A forward proxy sits in front of clients and forwards their requests to the internet (e.g., corporate web filters). Servers don't know who the real client is.

A reverse proxy sits in front of servers and forwards client requests to backend services. Clients don't know which backend is serving them.

Forward:  Client → [Proxy] → Internet → Server
Reverse:  Client → Internet → [Proxy] → Backend Server(s)

How Reverse Proxying Works in Apache

Apache's mod_proxy module enables reverse proxy functionality. The key directives are:

  • ProxyPass — maps a URL path to a backend server
  • ProxyPassReverse — rewrites response headers (Location, Content-Location, URI) so the client sees the proxy's URL, not the backend's
  • ProxyPreserveHost On — forwards the original Host header to the backend, important when the backend uses virtual hosting

Example virtual host configuration:

<VirtualHost *:80>
    ServerName app.example.com
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:5000/
    ProxyPassReverse / http://127.0.0.1:5000/
</VirtualHost>

This routes all requests for app.example.com to a backend service running on localhost port 5000.

Common Reverse Proxy Patterns

Port Normalization

Internal services often run on non-standard ports (3000, 5000, 8080). A reverse proxy makes them accessible on standard ports 80/443:

  • app.example.com:80localhost:5000
  • api.example.com:80localhost:3000

Container Proxying

Reverse proxies are essential for exposing Docker containers. Three approaches, in order of preference:

  1. Direct IP proxy — proxy to the container's Docker IP. Problem: IP changes when container is recreated.
  2. Port-forwarded proxy — forward container port to localhost, proxy to localhost. Container IP changes don't break the config.
  3. Dynamic proxy (Traefik) — automatically detects containers and configures routes. Used in Kubernetes.

TLS Termination

The reverse proxy handles TLS encryption/decryption, so backend services can run in plaintext (simpler configuration). The proxy presents the certificate to clients while communicating with backends over unencrypted HTTP.

Key Terminology

Proxy
An intermediary server that forwards requests between clients and servers.
Upstream / Backend
The server behind the reverse proxy that actually processes the request.
TLS Termination
Handling TLS encryption at the proxy level so backend services don't need their own certificates.
Load Balancing
Distributing requests across multiple backend servers for performance and redundancy.

Why It Matters

As a system administrator, you will:

  • Expose internal services (containers, application servers) through a reverse proxy on standard ports
  • Centralize TLS termination to simplify certificate management
  • Add logging, security headers, and rate limiting at the proxy level
  • Enable multiple services on a single IP using name-based virtual hosting + proxy rules

Common Pitfalls

  1. Forgetting ProxyPreserveHost — without it, the backend receives the proxy's hostname instead of the client's requested hostname, breaking virtual hosting on the backend.
  2. Hardcoding container IPs — container IPs change on recreation. Proxy to localhost:<forwarded-port> instead.
  3. Missing ProxyPassReverse — without it, redirect responses from the backend expose the internal URL to the client.
  4. Not enabling mod_proxy — the proxy modules must be loaded in Apache (mod_proxy, mod_proxy_http).
  5. SELinux blocking outbound connections — on RHEL/CentOS, Apache may need setsebool -P httpd_can_network_connect 1 to connect to backend services.

Further Reading