A reverse proxy places Apache in front of one or more backend services, presenting a single public endpoint while keeping internal hosts and ports off the internet. This pattern simplifies routing (multiple apps behind one domain), centralizes logging and access control, and reduces operational churn when backend locations change.
Apache implements reverse proxying through mod_proxy plus a protocol module such as mod_proxy_http. Incoming requests are matched by URL path (or host) and forwarded upstream using ProxyPass, while ProxyPassReverse rewrites redirect headers so browsers stay on the proxy URL instead of being bounced to internal backends.
A reverse proxy must never be left as a forward proxy: keep ProxyRequests set to Off to avoid becoming the internet’s favorite anonymous relay. Path-based proxying depends on correct trailing slashes and matching ProxyPass and ProxyPassReverse prefixes, and some applications need cookie or header adjustments when served under a sub-path. Configuration changes should be syntax-tested before reloading apache2 to avoid downtime.
Related: Enable or disable Apache modules
Related: Test Apache configuration
Related: Restart Apache service
$ whoami user
$ sudo a2enmod proxy proxy_http Enabling module proxy. Considering dependency proxy for proxy_http: Module proxy already enabled Enabling module proxy_http. To activate the new configuration, you need to run: systemctl restart apache2
| Item | Debian/Ubuntu | openSUSE/SLES | Fedora/CentOS/RHEL |
|---|---|---|---|
| Helper to enable modules | a2enmod | a2enmod | install/enable module packages (no a2enmod) |
| HTTP proxy module | proxy_http | proxy_http | mod_proxy_http (packaged) |
| Base proxy module | proxy | proxy | mod_proxy (packaged) |
$ sudo vi /etc/apache2/sites-available/proxy.example.net.conf
<VirtualHost *:80> ServerName proxy.example.net ProxyRequests Off ProxyPreserveHost On ProxyPass "/app/" "http://app.internal.example:8081/" ProxyPassReverse "/app/" "http://app.internal.example:8081/" ProxyPass "/api/" "http://api.example.net:8082/" ProxyPassReverse "/api/" "http://api.example.net:8082/" </VirtualHost>
| Directive/Option | Description |
|---|---|
| ProxyRequests Off | Disables forward proxy requests so the host cannot be abused as an open proxy. |
| ProxyPreserveHost On | Preserves the original Host header so upstream applications can route or generate links based on the public host name. |
| ProxyPass | Maps an incoming URL prefix to an upstream base URL, so requests to http://proxy.example.net/app/ are forwarded to http://app.internal.example:8081/. |
| ProxyPassReverse | Rewrites Location, Content-Location, and URI headers in upstream redirect responses so clients stay on the reverse proxy URL instead of being redirected to internal backend names. |
Trailing slashes should match on both sides of ProxyPass and ProxyPassReverse to keep sub-path routing predictable.
$ sudo a2ensite proxy.example.net.conf Enabling site proxy.example.net. To activate the new configuration, you need to run: systemctl reload apache2
Disabling the default site (000-default.conf) avoids accidental routing through the wrong virtual host when multiple sites listen on port 80.
$ sudo apache2ctl -t Syntax OK
$ sudo systemctl reload apache2
$ curl -sS http://app.internal.example:8081/ I am app.internal.example $ curl -sS http://api.example.net:8082/ I am api.example.net
Direct backend access from an external client may fail by design when backends are private; run this check on the reverse proxy host (or any host with the same network access).
$ curl -sS http://proxy.example.net/app/ I am app.internal.example $ curl -sS http://proxy.example.net/api/ I am api.example.net
A browser check should show the same content under the proxied path without exposing the backend hostname.
$ sudo systemctl status apache2 --no-pager -l --lines=8
● apache2.service - The Apache HTTP Server
Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-01-10 13:43:16 +08; 35s ago
Docs: https://httpd.apache.org/docs/2.4/
Process: 7605 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
Process: 8301 ExecReload=/usr/sbin/apachectl graceful (code=exited, status=0/SUCCESS)
Main PID: 7608 (apache2)
Tasks: 55 (limit: 4546)
Memory: 5.4M (peak: 8.1M)
CPU: 40ms
CGroup: /system.slice/apache2.service
├─7608 /usr/sbin/apache2 -k start
├─8305 /usr/sbin/apache2 -k start
└─8306 /usr/sbin/apache2 -k start
Jan 10 13:43:16 host systemd[1]: Starting apache2.service - The Apache HTTP Server...
Jan 10 13:43:16 host systemd[1]: Started apache2.service - The Apache HTTP Server.
Jan 10 13:43:51 host systemd[1]: Reloading apache2.service - The Apache HTTP Server...
Jan 10 13:43:51 host systemd[1]: Reloaded apache2.service - The Apache HTTP Server.
##### snipped #####