Skip to content
DevOps devops webservers 5 min read

Apache as a Reverse Proxy

A reverse proxy is a server that sits in front of your application and forwards incoming requests to it, then sends the application’s response back to the client. Apache (a very popular open-source web server) can act as a reverse proxy using a module called mod_proxy. This is useful when your app (for example a Node.js, Python, or Java service) listens on an internal port like localhost:3000, and you want the public internet to reach it on the normal web ports (80 for HTTP and 443 for HTTPS) through a clean domain name. This page mirrors the Nginx reverse-proxy page so you can compare the two side by side.

Why put Apache in front of your app

Most application servers (the program that actually runs your code, like a Node.js process) are not designed to face the public internet directly. They are slow at serving static files, weak at handling many slow connections, and they usually run on a high port that users should never see.

A reverse proxy fixes this. Apache listens on port 80/443, handles the messy parts (connections, TLS encryption, logging), and quietly hands the real work to your backend (the app running on a local port).

When to use this: you have an app on localhost:<port> and want a real domain, ports 80/443, central logging, and a single place to add TLS later.

When NOT to use this: if you only serve plain static HTML/CSS files, you don’t need a proxy at all — just serve the files directly with a virtual host.

Step 1 — install Apache

If Apache is not installed yet, install it on Ubuntu 22.04 or 24.04 LTS with apt (the Ubuntu package manager):

sudo apt update
sudo apt install apache2 -y

Output:

Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  apache2 apache2-bin apache2-data apache2-utils
...
Setting up apache2 (2.4.58-1ubuntu8.5) ...

Step 2 — enable the proxy modules

A module in Apache is an optional feature you turn on. To act as a reverse proxy you need two modules: proxy (the core proxy engine) and proxy_http (which knows how to proxy HTTP traffic). Ubuntu ships them but leaves them disabled.

Enable them with a2enmod (Apache’s “enable module” helper), then restart Apache so the change takes effect:

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo systemctl restart apache2

Output:

Enabling module proxy.
Enabling module proxy_http.
To activate the new configuration, you need to run:
  systemctl restart apache2

Only enable the modules you actually need. Apache also ships mod_proxy_balancer, mod_proxy_ftp and others. Enabling extra modules widens the attack surface (the number of ways an attacker could try to break in) for no benefit.

Step 3 — write the reverse-proxy virtual host

A virtual host is a block of config that tells Apache how to handle requests for one domain. On Ubuntu these live in /etc/apache2/sites-available/. Create one for your app:

sudo nano /etc/apache2/sites-available/myapp.conf

Paste the following. This example assumes your backend app is running on http://127.0.0.1:3000:

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

    # Forward all requests to the backend app on localhost:3000
    ProxyPreserveHost On
    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/

    # Pass the real client info to the backend
    RequestHeader set X-Forwarded-Proto "http"
    RemoteIPHeader X-Forwarded-For

    ErrorLog  ${APACHE_LOG_DIR}/myapp-error.log
    CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined
</VirtualHost>

What each directive does

DirectiveWhat it does
ProxyPass / http://127.0.0.1:3000/Sends every incoming path (/) to the backend app.
ProxyPassReverse / http://127.0.0.1:3000/Rewrites Location headers in redirects so the client sees your domain, not 127.0.0.1:3000.
ProxyPreserveHost OnForwards the original Host header (your domain) to the backend instead of 127.0.0.1. Many apps need this to build correct URLs.
RequestHeader set X-Forwarded-ProtoTells the backend whether the original request was HTTP or HTTPS.
RemoteIPHeader X-Forwarded-ForLets the backend see the real visitor IP, not Apache’s.

Always use 127.0.0.1 (loopback) for the backend, not 0.0.0.0. Your app should bind to localhost so it is unreachable from the internet directly — only Apache can talk to it. Then control public access with ufw (Ubuntu’s firewall).

Step 4 — enable the site and reload

Apache won’t read a file in sites-available until you enable it with a2ensite, which creates a symlink (a shortcut) in sites-enabled. Then test the config and reload.

sudo a2ensite myapp.conf
sudo apache2ctl configtest
sudo systemctl reload apache2

Output:

Enabling site myapp.
Syntax OK

Syntax OK means your config is valid. Always run configtest before reloading — a bad config can take the whole server down.

Step 5 — open the firewall and verify

Allow web traffic through ufw and test the proxy with curl:

sudo ufw allow 'Apache Full'
curl -I http://myapp.example.com

Output:

HTTP/1.1 200 OK
Date: Mon, 16 Jun 2026 10:22:14 GMT
Server: Apache/2.4.58 (Ubuntu)
X-Powered-By: Express
Content-Type: text/html; charset=utf-8

The 200 OK with X-Powered-By: Express confirms Apache reached your backend app and returned its response.

Apache vs Nginx as a reverse proxy

PointApache (mod_proxy)Nginx
Config styleXML-like <VirtualHost> blocksBlock syntax with location
Proxy directiveProxyPass / ProxyPassReverseproxy_pass
Preserve hostProxyPreserveHost Onproxy_set_header Host $host;
Strength.htaccess, huge module ecosystemHigh concurrency, low memory
When to pickYou already run Apache or need its modulesYou want maximum performance under load

Best practices

  • Bind your backend app to 127.0.0.1 only, never a public interface.
  • Run sudo apache2ctl configtest before every reload or restart.
  • Keep ProxyPreserveHost On so the backend builds correct absolute URLs.
  • Set X-Forwarded-Proto and X-Forwarded-For so your app knows the real protocol and client IP.
  • Disable ProxyRequests (it is off by default) — leaving it on creates an open forward proxy, a serious security hole.
  • Add TLS with Let’s Encrypt and mod_ssl so traffic between the client and Apache is encrypted.
  • Give each app its own .conf file and log files for easy debugging.
Last updated June 15, 2026
Was this helpful?