An Apache reverse proxy puts one public web server in front of internal application services, so backend ports and hostnames stay private while requests arrive through a stable public URL. That centralizes routing, TLS termination, logging, and access control without exposing each backend directly.

Reverse proxying in Apache is handled by mod_proxy plus a protocol module such as mod_proxy_http. ProxyPass maps an incoming path to a backend URL, while ProxyPassReverse rewrites redirect headers from the backend so clients stay on the public proxy address instead of being sent to an internal host.

Debian and Ubuntu systems enable proxy features per module with a2enmod, then publish the finished virtual host with a2ensite. Keep ProxyRequests set to Off so the server does not become a forward proxy, keep ProxyPass and ProxyPassReverse slashes aligned, and be ready to rewrite cookie paths or domains when an application is exposed under a sub-path.

Steps to configure Apache as a reverse proxy:

  1. Confirm each backend responds on its internal address from the Apache host.
    $ 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 checks usually work only from the reverse proxy host or another system on the same private network.

  2. Enable the mod_proxy and mod_proxy_http modules.
    $ 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:
      service apache2 restart
    • Debian and Ubuntu use a2enmod to create module symlinks under /etc/apache2.
    • RHEL-family systems usually load proxy modules from packaged files under /etc/httpd/conf.modules.d instead of using a2enmod.
  3. Create or edit the Debian or Ubuntu virtual host file for the public proxy name.
    $ sudo vi /etc/apache2/sites-available/proxy.example.net.conf
  4. Add the reverse proxy rules to the virtual host configuration.
    <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/"
     
        ErrorLog /var/log/apache2/proxy.example.net.error.log
        CustomLog /var/log/apache2/proxy.example.net.access.log combined
    </VirtualHost>
    Directive/Option Description
    ProxyRequests Off Keeps the server in reverse-proxy mode instead of accepting forward-proxy requests from arbitrary clients.
    ProxyPreserveHost On Passes the original public Host header to the backend instead of the backend host from ProxyPass.
    ProxyPass Maps an incoming URL prefix to the backend URL that should receive the request.
    ProxyPassReverse Rewrites redirect headers so backend redirects stay on the public proxy URL.
    ErrorLog / CustomLog Writes vhost-specific logs that make proxy troubleshooting easier.

    Leave ProxyPreserveHost at its default Off if the backend should receive its own internal host name instead of the public proxy host.

    If the backend sets cookies for its internal host or root path, add ProxyPassReverseCookieDomain app.internal.example proxy.example.net or ProxyPassReverseCookiePath / /app/ so session cookies still match the public URL.

    Keep the trailing slashes on ProxyPass and ProxyPassReverse aligned on both sides of the mapping to avoid broken sub-path routing.

  5. Enable the new site configuration.
    $ sudo a2ensite proxy.example.net.conf
    Enabling site proxy.example.net.
    To activate the new configuration, you need to run:
      service apache2 reload

    Disabling 000-default.conf avoids unexpected matches when another default virtual host is still listening on port 80.

  6. Test the Apache configuration syntax before reloading the service.
    $ sudo apache2ctl -t
    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 203.0.113.10. Set the 'ServerName' directive globally to suppress this message
    Syntax OK

    The AH00558 message is a missing global ServerName warning on fresh Debian or Ubuntu installs; Syntax OK still confirms the configuration parsed successfully.

  7. Reload the apache2 service to apply the reverse proxy configuration.
    $ sudo systemctl reload apache2

    The helper output from a2enmod and a2ensite still prints service apache2 ... on current Debian and Ubuntu packages, but systemctl reload apache2 is the normal equivalent on systemd hosts.

  8. Request the proxied paths through the public Apache URL.
    $ curl -sS http://proxy.example.net/app/
    I am app.internal.example
    $ curl -sS http://proxy.example.net/api/
    I am api.example.net

    If a backend redirect still points to its internal host name, review ProxyPassReverse and any cookie rewrite directives for that path.

  9. Inspect the virtual host access log for successful proxy requests.
    $ sudo tail -n 4 /var/log/apache2/proxy.example.net.access.log
    127.0.0.1 - - [08/Apr/2026:04:29:39 +0000] "GET /app/ HTTP/1.1" 200 234 "-" "curl/8.5.0"
    127.0.0.1 - - [08/Apr/2026:04:29:39 +0000] "GET /api/ HTTP/1.1" 200 229 "-" "curl/8.5.0"
    127.0.0.1 - - [08/Apr/2026:04:29:39 +0000] "HEAD /app/ HTTP/1.1" 200 185 "-" "curl/8.5.0"

    A 200 status for the public path plus the expected backend response confirms the reverse proxy mapping is working end to end.