In Apache httpd with mod_proxy, what does ProxyPassReverse do that ProxyPass does not, and what breaks in production if you configure ProxyPass alone?
answer
- one directive per direction
- the response headers are the missing half
- Location points at 127.0.0.1
- headers only, never the HTML body
- trailing slashes must agree
basics
~20 sProxyPass maps an inbound URL path to a backend and forwards requests. ProxyPassReverse works on the way back, rewriting Location, Content-Location and URI response headers so a backend redirect pointing at the internal address is translated into the public URL.
solid answer
~40 s`ProxyPass` is one-directional: it takes requests under a URL prefix and forwards them to the backend. Nothing about it touches the response headers. When the backend answers with a redirect, it typically builds an absolute URL from its own address — `Location: http://127.0.0.1:8080/app/login`. Forwarded verbatim, that sends the browser to an address it cannot reach, which shows up as a login loop or a connection failure right after a form post. `ProxyPassReverse` fixes exactly that: it rewrites `Location`, `Content-Location` and `URI` headers on responses, mapping the backend URL back into the public URL space. It is header-only — it does not touch links inside the HTML body, and cookies need `ProxyPassReverseCookieDomain` and `ProxyPassReverseCookiePath` separately. The usual pairing is one `ProxyPassReverse` per `ProxyPass`, with matching trailing slashes on both.
code
apacheconf · 19 lines<VirtualHost *:443>
ServerName app.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/certs/app.crt
SSLCertificateKeyFile /etc/ssl/private/app.key
ProxyRequests Off
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto "https"
# Serve static assets locally, do not proxy them
ProxyPass /static/ !
Alias /static/ /srv/www/app-static/
ProxyPass / http://127.0.0.1:8080/
ProxyPassReverse / http://127.0.0.1:8080/
ProxyPassReverseCookiePath / /
</VirtualHost>go deeper
Be able to write a working ProxyPass plus ProxyPassReverse pair and say in one sentence that the second one fixes redirect URLs coming back from the backend.
Explain that ProxyPassReverse rewrites the Location, Content-Location and URI response headers, and reproduce the classic symptom: a post-login redirect sending the browser to 127.0.0.1 because only ProxyPass was configured.
Show you can choose between rewriting on the way out and telling the backend its real public identity, and that you know which URL leaks — bodies, cookies, emails — each approach fails to fix.
Frame it as where URL identity should live: a proxy that translates addresses forever is a coupling, so decide whether backends are configured with their public base URL and audited for it, or the edge owns the rewrite permanently.
## Two directions, two directives A reverse proxy has to rewrite in both directions, and Apache splits that job across two directives that people routinely assume are one. `ProxyPass` handles the request direction. It claims a slice of the public URL space and forwards matching requests to a backend origin: ```apacheconf ProxyPass /app/ http://127.0.0.1:8080/app/ ProxyPassReverse /app/ http://127.0.0.1:8080/app/ ``` `ProxyPassReverse` handles the response direction — and only a narrow, specific part of it: the `Location`, `Content-Location` and `URI` headers. Where a backend response names the internal URL in one of those headers, the header is rewritten to the corresponding public URL. ## Why the backend leaks its own address at all Application frameworks build absolute redirect URLs from what they believe their own address to be — typically the `Host` header they received, or a configured base URL. Behind Apache, the `Host` they receive is by default the one from the `ProxyPass` target, so a redirect after login becomes `http://127.0.0.1:8080/app/dashboard`. The browser obeys it, tries to connect to 127.0.0.1 on its own machine, and the user sees a failure or an endless redirect. The response itself was fine; only the header was wrong. ## The two ways to solve it **Rewrite on the way out.** That is `ProxyPassReverse`. The backend keeps thinking it lives at 127.0.0.1:8080 and Apache translates on each response. **Tell the backend the truth.** `ProxyPreserveHost On` forwards the client's original `Host` header instead of the backend's, so the application generates public URLs itself. Combined with `RequestHeader set X-Forwarded-Proto https` (mod_headers) — and an application that honours it — this is often the cleaner arrangement, because it also fixes URLs the app writes into HTML bodies, emails and API payloads, which `ProxyPassReverse` can never reach. Note that preserving the host changes what the backend sees for vhost selection and logging, so it is a decision, not a default. In practice mature setups do both. ## The trailing-slash rule If the `ProxyPass` target URL ends in `/`, the path must too, and vice versa. Mismatched slashes silently produce concatenated paths — `/app` mapped to `http://127.0.0.1:8080/app/` turns a request for `/apple` into something you did not intend, because `/app` is a prefix match on the URL string, not a path-segment match. Keep the slashes symmetrical, or use `ProxyPass` inside a `<Location>` block, where the path argument is taken from the container and dropped from the directive. ## What ProxyPassReverse does not do - **HTML bodies.** Absolute links, form actions and script `src` values embedded in the page are untouched. Rewriting those requires mod_proxy_html or, better, fixing the application to emit relative or correctly-based URLs. - **Cookies.** A backend setting `Domain=internal.local` or `Path=/app-internal` needs `ProxyPassReverseCookieDomain` and `ProxyPassReverseCookiePath`. - **Other headers.** `Link`, `Refresh` and application-specific headers carrying URLs are not in scope. ## Ordering and matching `ProxyPass` rules are evaluated in the order written, first match wins, so more specific prefixes must be listed before broader ones — `/app/api/` before `/app/`. A `ProxyPass ... !` exclusion opts a subtree out of proxying so Apache serves it locally, which is how static assets are kept off the application server. Also worth separating in your head: `ProxyRequests` controls **forward**-proxy behaviour and must stay `Off` on a reverse proxy. It is unrelated to `ProxyPass`, and leaving it `On` turns the server into an open proxy that strangers can relay through. ## Diagnosing it in thirty seconds Request the failing URL with a client that shows headers and does not follow redirects, and read the `Location` value. If it names the backend's address or port, `ProxyPassReverse` is missing or its mapping does not match the `ProxyPass` one. If it names the right host but the wrong scheme — `http://` where the client used HTTPS — the problem is instead the backend's view of the protocol, which is a forwarded-header question, not a `ProxyPassReverse` one.
- After adding ProxyPassReverse the redirects are correct, but links inside the rendered pages still point at the backend. Why?Because ProxyPassReverse only rewrites the `Location`, `Content-Location` and `URI` response headers — it never parses or edits the response body. Absolute URLs baked into HTML need either mod_proxy_html to rewrite them, or the real fix: have the application emit root-relative URLs, or tell it its public base address so it generates correct absolute ones itself.
- When would you use ProxyPreserveHost On instead of relying on ProxyPassReverse?When the backend generates public URLs in more places than redirect headers — HTML, JSON payloads, emails, signed callbacks. Preserving the original Host lets the application build correct URLs at the source, which ProxyPassReverse cannot reach. The cost is that the backend now sees externally-supplied hostnames, so it must not trust Host blindly for routing or link generation without validation.
- What goes wrong if ProxyPass has a trailing slash on the target but not on the path?The path is matched as a URL prefix, not by path segment, and the mismatched slashes concatenate. `ProxyPass /app http://127.0.0.1:8080/app/` will also capture `/apple` and produce a mangled backend URI. Keep both sides symmetrical, or put ProxyPass inside a `<Location>` block so the path comes from the container and cannot drift.
- Why must ProxyRequests stay Off on a reverse proxy?ProxyRequests enables forward-proxy handling, where clients ask the server to fetch arbitrary third-party URLs on their behalf. Turned on, anyone who can reach the port has an open relay — usable for abuse traffic, for reaching internal hosts the server can see, and for having your IP blamed. It is entirely unrelated to ProxyPass, which works with ProxyRequests Off.
saying these in an interview costs you the question
- Assuming ProxyPass rewrites responses as well as requests
- Thinking ProxyPassReverse rewrites links inside the HTML body
- Believing ProxyRequests On is needed for ProxyPass to work
- Ignoring the trailing-slash pairing between path and target
- Expecting cookie Domain and Path to be fixed automatically