OneLinersCommand workbench
Guides
Networking & DNS / Services & Applications

Configure Apache as an HTTP and WebSocket reverse proxy

Proxy a private application through Apache with explicit timeouts, forwarded headers, WebSocket upgrades, health checks, and no accidental forward-proxy exposure.

50 min8 stepsChanges system stateRevision 2
Save or explore
Save to collectionCreate a collection in the sidebar first.
0 of 8 steps completed
Goal

Publish one upstream application safely while keeping the backend private and preserving client and scheme information.

Supported environments
  • Apache HTTP Server 2.4.47+
  • Ubuntu Server 24.04 LTS
Prerequisites
  • Console and recovery access Keep an independent console session and a copy of the current Apache configuration before changing listeners, modules, or access rules.
  • DNS and network plan Resolve every production hostname to the intended public address and list the exact management and client networks that require access.getent ahosts {{domain}} && ip route show
  • Port ownership Confirm that TCP port 443 is free or already belongs to the intended Apache instance.sudo ss -lntp 'sport = :443'
  • Rollback artifact Snapshot the host or create an archive of the active configuration and web roots before the first material change.
Operating boundary

OneLiners never runs these steps or stores secrets. Review placeholders, versions, current state, and change-control requirements before using a command.

Full guide

What you will build

System
  • An Apache HTTPS virtual host that reverse-proxies ordinary HTTP requests and one explicit WebSocket path to a backend bound only to loopback.
  • An idempotent, site-scoped proxy fragment with bounded connect/response timeouts, correct redirect rewriting, reviewed forwarded scheme/port context, and `ProxyRequests Off`.
  • A failure-tested service boundary with direct and public health probes, evidence from both log layers, a fast backend-outage response, and a rollback that never exposes the private application listener.
Observable outcome
  • The backend answers directly on the intended private socket, Apache answers the public hostname, and only `/socket/` permits a WebSocket upgrade before the catch-all route.
  • Apache cannot be used as an open forward proxy, the backend receives the intended host and scheme context, and upstream redirects return through the public origin.
  • Stopping the controlled backend produces a quick observable 502/503 and recovery succeeds after restart without changing firewall exposure.

Architecture

How the parts fit together

Apache terminates public TLS, selects the named virtual host, and acts as a reverse proxy—not a general client-selected forward proxy. `ProxyPass` routes a narrowly defined WebSocket prefix first and the remaining root path second to a loopback-only backend. `ProxyPassReverse` rewrites selected upstream redirect headers. Apache supplies reviewed scheme and port metadata, while the application decides which proxy hops to trust. Explicit connection and response timeouts bound failure. Apache and application logs retain separate edge and execution evidence.

Public Apache TLS vhostOwns Internet exposure, hostname selection, certificate, request limits, proxy mapping, and edge logs.
Site proxy includeKeeps proxy directives scoped, reviewable, repeatable, and separable from the main virtual-host file.
Loopback backendServes application HTTP and WebSocket endpoints without a public network listener.
mod_proxy_httpTransports HTTP requests and responses between Apache and the private upstream.
WebSocket upgrade mappingAllows protocol upgrade only on the documented path and only to the intended backend.
Dual-layer observabilityCorrelates edge status/timing with backend health and application errors.
  1. A client resolves the public domain, negotiates TLS with Apache, and sends a request to the selected vhost.
  2. Apache rejects forward-proxy behavior and evaluates specific proxy mappings before the catch-all root mapping.
  3. For ordinary traffic, Apache opens an HTTP connection to loopback; for `/socket/`, it permits the reviewed WebSocket upgrade.
  4. The backend receives Host plus explicit scheme/port context, processes the request, and returns a response or redirect.
  5. Apache rewrites applicable redirect headers to the public origin, records edge evidence, and returns the result.

Assumptions

  • Apache already serves valid HTTPS for `{{domain}}` and its virtual-host file is explicit, enabled, backed up, and free of unrelated tenants.
  • The backend service is managed by `{{backendService}}`, binds only to `127.0.0.1:{{backendPort}}`, and exposes a non-secret `/health` response plus the documented `/socket/` endpoint.
  • The application supports operation behind a reverse proxy and has a defined list of trusted proxy addresses; it does not blindly trust client-supplied forwarding headers.
  • The public request-body, header, timeout, rate, authentication, and WebSocket-origin requirements are known before exposure.
  • A controlled backend interruption is acceptable during the explicit failure rehearsal; otherwise use a staging instance with identical routing.

Key concepts

Reverse proxy
A server-owned mapping from a public route to a predetermined upstream; the client cannot choose arbitrary destinations.
Forward proxy
A proxy where clients request external destinations. `ProxyRequests On` can create serious abuse risk and is not needed here.
ProxyPass ordering
More specific path mappings must precede a catch-all route so they are not shadowed.
WebSocket upgrade
An HTTP protocol transition to a long-lived bidirectional connection, permitted only on the intended route.
Trusted proxy
A known hop whose overwritten forwarding metadata the application is configured to accept; arbitrary client headers are untrusted.
Connection timeout
The maximum time to establish an upstream connection, distinct from the time allowed for a complete response.

Before you copy

Values used in this guide

{{domain}}

Public Apache ServerName and HTTPS origin.

Example: chat.example.com
{{backendPort}}

Loopback TCP port owned by the private application.

Example: 3000
{{backendService}}

Exact systemd unit controlled during the failure rehearsal.

Example: chat-app.service

Security and production boundaries

  • Keep `ProxyRequests Off`; enabling proxy modules does not by itself create a forward proxy, but an explicit on-state or permissive proxy section can.
  • Bind the backend to loopback or a protected service network. Never compensate for a proxy error by exposing its development listener publicly.
  • Client-supplied `X-Forwarded-*` values are spoofable unless the edge removes/overwrites them and the application trusts only the edge address.
  • Preserving Host is appropriate only when the application validates allowed hosts. Otherwise it can feed host-header attacks into URL generation, password resets, or cache keys.
  • WebSocket endpoints need authentication, authorization, Origin policy, message limits, idle limits, and connection monitoring beyond the Apache upgrade route.
  • Timeouts and body/header limits reduce resource exhaustion but must reflect legitimate uploads, streaming, and long-lived socket behavior.

Stop before continuing if

  • Stop if the backend listens publicly, its health result is not deterministic, the intended systemd unit is unknown, or direct behavior is already unhealthy.
  • Do not reload if the include is duplicated, catch-all precedes the WebSocket path, `ProxyRequests` is not Off, or configtest/vhost mapping differs.
  • Do not trust forwarding headers until the application trust boundary and edge overwrite behavior are proven.
  • Stop if public Host, scheme, redirect, cookie, WebSocket, authentication, or absolute-URL behavior differs from direct application expectations.
  • Abort the failure test if the named service has unrelated dependents or the outage exceeds the approved window.
01

verification

Verify the backend directly

read-only

Confirm the private listener, health endpoint, response identity, and WebSocket path before introducing Apache.

Why this step matters

Separating backend health from proxy behavior prevents Apache from hiding an application, listener, route, or service failure that already exists.

What to understand

Use privileged socket inspection to prove address, port, process, and network namespace. A loopback bind is an explicit boundary.

Test `/health`, a normal response, Host behavior, headers, and the actual WebSocket route directly. Health should avoid secrets and expensive dependencies unless its contract says otherwise.

Record backend latency and current service logs so post-proxy regressions have a baseline.

System changes

  • No persistent change; reads listener, service, and direct HTTP state.

Syntax explained

ss -lntp 'sport = :…'
Shows the exact listening TCP socket and owner.
curl --fail --silent
Requires an HTTP success response while returning only the health body.
curl --head
Records direct response status and headers without body transfer.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo ss -lntp 'sport = :{{backendPort}}' && curl --fail --silent http://127.0.0.1:{{backendPort}}/health && curl --head http://127.0.0.1:{{backendPort}}/
Example output / evidence
LISTEN 127.0.0.1:3000
ok
HTTP/1.1 200 OK

Checkpoint: Checkpoint: upstream

Continue whenThe intended service alone listens on loopback, health returns its documented marker quickly, and direct HTTP/WebSocket behavior is known.

Stop whenThe listener is public/unknown, direct health fails, response identity differs, or the WebSocket route is undocumented.

If this step fails

No process owns the expected port.

Likely causeThe service is stopped, failed, socket-activated elsewhere, containerized, or configured for another port.

Safe checks
  • systemctl status chat-app.service --no-pager
  • sudo journalctl -u chat-app.service -n 100 --no-pager
  • sudo ss -lntp

ResolutionRepair and prove the backend lifecycle before configuring Apache.

Security notes

  • Do not publish a debug health endpoint containing versions, environment, database state, or secrets.

Alternatives

  • Target a protected service-network address when Apache and backend are separate hosts, with mutual network policy and health checks.

Stop conditions

  • The listener is public/unknown, direct health fails, response identity differs, or the WebSocket route is undocumented.
02

command

Back up the target virtual host

caution

Copy the exact active site file and record its checksum before adding proxy directives.

Why this step matters

The virtual host is the routing and TLS boundary, so preserving the exact file, metadata, checksum, and enabled symlink supports fast targeted rollback.

What to understand

Resolve the enabled symlink to confirm the saved available file is the active one and archive any existing include fragments.

Compare owner/mode and checksum, and record current external response before editing.

A file copy does not capture application configuration; retain the backend's independent release rollback.

System changes

  • Creates a metadata-preserving copy beside the site configuration and a checksum record.

Syntax explained

cp --archive
Preserves mode, owner, timestamps, and links where applicable.
sha256sum
Provides integrity evidence for the saved site file.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo cp --archive /etc/apache2/sites-available/{{domain}}.conf /etc/apache2/sites-available/{{domain}}.conf.before-proxy && sudo sha256sum /etc/apache2/sites-available/{{domain}}.conf.before-proxy
Example output / evidence
ed90...  /etc/apache2/sites-available/example.com.conf.before-proxy

Checkpoint: Checkpoint: backup

Continue whenThe saved file is root-controlled, checksum verifies, and it matches the currently enabled site's pre-proxy state.

Stop whenThe enabled site resolves elsewhere, backup cannot be verified, or generated configuration will overwrite manual edits.

If this step fails

The copied file is not the enabled vhost.

Likely causeThe site symlink points to a differently named/generated file.

Safe checks
  • readlink -f /etc/apache2/sites-enabled/chat.example.com.conf
  • sudo apache2ctl -S

ResolutionBack up the actual active source and document its deployment owner.

Security notes

  • Site files can disclose internal endpoints and certificate paths; protect backup access.

Alternatives

  • Use a version-controlled declarative vhost and tested deployment rollback when it is the authoritative source.

Stop conditions

  • The enabled site resolves elsewhere, backup cannot be verified, or generated configuration will overwrite manual edits.
03

command

Enable only the proxy modules required

caution

Enable HTTP, WebSocket, and header support; keep forward proxying explicitly disabled.

Why this step matters

Apache needs only the transport and header modules used by the design; module state should be explicit before adding directives.

What to understand

mod_proxy is the core, mod_proxy_http carries HTTP and modern WebSocket upgrades, headers overwrites reviewed scheme context, and proxy_wstunnel remains for compatibility.

Enabling modules changes symlinks but does not yet create routing. Search the whole config for existing proxy behavior before reload.

Keep `ProxyRequests Off` in the site and verify no other global file enables forward proxying.

System changes

  • Enables Apache module symlinks for proxy, HTTP transport, WebSocket compatibility, and headers.

Syntax explained

a2enmod proxy
Enables the common proxy framework.
proxy_http
Adds HTTP upstream transport and modern upgrade support.
proxy_wstunnel
Adds legacy/specific WebSocket tunneling compatibility.
headers
Allows controlled request-header overwrite at the edge.
Command
sudo a2enmod proxy proxy_http proxy_wstunnel headers && sudo apache2ctl -M | grep -E 'proxy|headers'
Example output / evidence
proxy_module (shared)
proxy_http_module (shared)
proxy_wstunnel_module (shared)
headers_module (shared)

Checkpoint: Checkpoint: modules

Continue whenRequired modules load once, no unreviewed proxy modules/routes appear, and forward proxying remains disabled.

Stop whenModule enablement changes unrelated dependency state, an existing proxy is undocumented, or config search finds `ProxyRequests On`.

If this step fails

Apache reports an unknown ProxyPass parameter.

Likely causeThe installed Apache version predates the selected upgrade or timeout behavior.

Safe checks
  • apache2ctl -v
  • sudo apache2ctl configtest

ResolutionUse syntax documented for the installed release or update through the approved distribution path.

Security notes

  • Unused proxy protocols expand attack surface; do not enable AJP, FTP, CONNECT, or balancing modules without need.

Alternatives

  • On Apache 2.4.47+, omit proxy_wstunnel when testing proves mod_proxy_http handles the required upgrades.

Stop conditions

  • Module enablement changes unrelated dependency state, an existing proxy is undocumented, or config search finds `ProxyRequests On`.
04

config

Add bounded upstream proxying

caution

Create a dedicated included fragment that maps the application root to the loopback backend, preserves Host, bounds connect and response timeouts, and reverses upstream redirects. A sentinel prevents duplicate includes on rerun.

Why this step matters

A dedicated include makes the route atomic and reviewable, while a sentinel prevents reruns from adding repeated Include directives to the site.

What to understand

The upstream is a literal loopback address and reviewed port; never interpolate a client-controlled destination.

Five seconds bounds connection establishment and 60 seconds bounds normal responses. Streaming or long jobs require an intentional route-specific design.

Host is preserved and scheme/port are overwritten by Apache. Configure the application to trust only this proxy hop.

ProxyPassReverse handles selected redirect headers, not cookies, HTML, JavaScript, or application base-URL logic.

System changes

  • Creates the site proxy include and inserts one marked IncludeOptional line inside the selected vhost.

Syntax explained

ProxyRequests Off
Disables client-selected forward proxy requests.
ProxyPreserveHost On
Passes the public Host header to the backend.
connectiontimeout=5
Bounds upstream TCP connection establishment.
timeout=60
Bounds response I/O for the mapping.
ProxyPassReverse
Rewrites supported upstream redirect headers to the public proxy origin.
RequestHeader set
Overwrites scheme/port metadata instead of appending spoofable client values.
File /etc/apache2/sites-available/{{domain}}.conf
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

printf 'ProxyRequests Off\nProxyPreserveHost On\nProxyPass / http://127.0.0.1:{{backendPort}}/ connectiontimeout=5 timeout=60\nProxyPassReverse / http://127.0.0.1:{{backendPort}}/\nRequestHeader set X-Forwarded-Proto "https"\nRequestHeader set X-Forwarded-Port "443"\n' | sudo tee /etc/apache2/sites-available/{{domain}}-proxy.inc >/dev/null && sudo grep -q 'ONELINERS PROXY INCLUDE' /etc/apache2/sites-available/{{domain}}.conf || sudo sed -i '\#</VirtualHost>#i\  # ONELINERS PROXY INCLUDE\n  IncludeOptional /etc/apache2/sites-available/{{domain}}-proxy.inc' /etc/apache2/sites-available/{{domain}}.conf
Example output / evidence
ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/ connectiontimeout=5 timeout=60

Checkpoint: Checkpoint: proxy config

Continue whenOne site-scoped include contains the exact private upstream, bounded timeouts, reverse mapping, and explicit forward-proxy off-state.

Stop whenThe vhost has multiple trust domains, marker/include already differs, upstream is non-private, or diff touches another site.

If this step fails

The include is outside VirtualHost and affects other sites.

Likely causeThe main site has an unusual structure or the insertion matched an unintended closing block.

Safe checks
  • sudo grep -n -A3 -B3 'ONELINERS PROXY INCLUDE' /etc/apache2/sites-available/chat.example.com.conf
  • sudo apache2ctl -S

ResolutionRestore the saved site and place one reviewed include manually or declaratively in the exact vhost.

Security notes

  • The backend must validate Host and accept forwarded context only from Apache's address.

Alternatives

  • Manage the complete vhost and included fragment through configuration management instead of an inline insertion.

Stop conditions

  • The vhost has multiple trust domains, marker/include already differs, upstream is non-private, or diff touches another site.
05

config

Route the WebSocket endpoint explicitly

caution

On Apache 2.4.47+, place the known WebSocket path before the catch-all mapping and use upgrade=websocket only for that path instead of permitting arbitrary protocol tunneling.

Why this step matters

A WebSocket route is a distinct long-lived protocol boundary and must precede the root mapping so only the documented path receives upgrade behavior.

What to understand

The idempotency check prevents duplicate path rules. Review trailing slashes because ProxyPass path mapping semantics depend on them.

Test authentication and Origin policy in the application; Apache routing alone does not authorize a connection.

Plan idle timeout, maximum connections, message size, deployment draining, and log/metric visibility.

System changes

  • Adds one explicit `/socket/` mapping before the catch-all line in the site proxy include.

Syntax explained

ProxyPass /socket/
Selects only the documented public socket path.
upgrade=websocket
Allows the WebSocket protocol upgrade for this mapping.
grep -q … ||
Makes the insertion safe to rerun when the exact rule already exists.
File /etc/apache2/sites-available/{{domain}}.conf
Configuration
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo grep -q '^ProxyPass /socket/ .*upgrade=websocket$' /etc/apache2/sites-available/{{domain}}-proxy.inc || sudo sed -i '\#^ProxyPass / http://#i ProxyPass /socket/ http://127.0.0.1:{{backendPort}}/socket/ upgrade=websocket' /etc/apache2/sites-available/{{domain}}-proxy.inc
Example output / evidence
ProxyPass /socket/ http://127.0.0.1:3000/socket/ upgrade=websocket

Checkpoint: Checkpoint: websocket

Continue whenThe specific WebSocket mapping appears once before `/`, targets the same private backend, and a real handshake returns 101.

Stop whenThe application route differs, catch-all precedes it, arbitrary upgrade is enabled, or authentication/Origin requirements are unknown.

If this step fails

The route works over HTTP polling but never upgrades.

Likely causeRule ordering, path mismatch, missing upgrade support, or backend handshake rejection.

Safe checks
  • sudo grep -n '^ProxyPass' /etc/apache2/sites-available/chat.example.com-proxy.inc
  • sudo tail -n 100 /var/log/apache2/chat.example.com-error.log

ResolutionCorrect the exact ordered path and inspect backend handshake evidence with a WebSocket client.

Security notes

  • Long-lived unauthenticated sockets can exhaust workers and bypass ordinary request controls; enforce policy in the application.

Alternatives

  • Use the application's supported long-polling mode only when its scaling and timeout behavior are explicitly accepted.

Stop conditions

  • The application route differs, catch-all precedes it, arbitrary upgrade is enabled, or authentication/Origin requirements are unknown.
06

verification

Validate proxy direction and syntax

read-only

Confirm syntax, dump the virtual host, and prove ProxyRequests remains Off before reload.

Why this step matters

Syntax, vhost selection, route order, included scope, and forward-proxy state must be proven before the public listener adopts the new mapping.

What to understand

Parse the full include graph, dump vhosts, inspect all ProxyRequests declarations, and print ordered ProxyPass rules.

Search all enabled files, not only the new fragment; an old global `ProxyRequests On` remains dangerous.

Do not treat `ProxyRequests Off` text alone as proof—perform a controlled external negative proxy test after activation.

System changes

  • No persistent change; parses and audits enabled Apache configuration.

Syntax explained

apache2ctl configtest
Parses the complete configuration before reload.
apache2ctl -S
Shows which vhost owns the public name/address.
grep -R ProxyRequests
Audits forward-proxy state across enabled files.
Command
sudo apache2ctl configtest && sudo apache2ctl -S && sudo grep -R '^\s*ProxyRequests' /etc/apache2/sites-enabled
Example output / evidence
Syntax OK
ProxyRequests Off

Checkpoint: Checkpoint: validate

Continue whenSyntax is valid, one intended vhost includes the fragment, socket route precedes root, and every active ProxyRequests state is Off.

Stop whenParser, vhost map, include scope, route order, or proxy-direction audit differs from expectation.

If this step fails

configtest reports ProxyPass cannot occur in this context.

Likely causeThe Include line landed outside the intended VirtualHost or inside an unsupported section.

Safe checks
  • sudo grep -n -A5 -B5 'ONELINERS PROXY INCLUDE' /etc/apache2/sites-available/chat.example.com.conf

ResolutionRestore and place the include directly within the correct VirtualHost.

Security notes

  • A valid parser cannot determine business authorization or trusted-proxy correctness; those remain explicit review gates.

Alternatives

  • Run the same parser and negative proxy tests on a staging host before deployment.

Stop conditions

  • Parser, vhost map, include scope, route order, or proxy-direction audit differs from expectation.
07

warning

Reload and test proxied HTTP

danger

Request the public hostname, confirm application content and forwarded scheme behavior, then inspect both Apache and upstream logs.

Why this step matters

A graceful reload activates the validated route while public probes, backend logs, and Apache logs prove end-to-end host, scheme, status, and health behavior.

What to understand

Test the public origin without bypassing certificate verification. Inspect redirect, cookies, absolute URLs, authentication, and a representative application transaction.

Compare Apache access/error evidence with application logs and a request ID where available.

Run a negative forward-proxy request from an authorized test client and verify Apache rejects it.

System changes

  • Reloads Apache and creates normal proxy/application log records and client connections.

Syntax explained

systemctl reload apache2
Applies parsed configuration gracefully where possible.
curl --fail --head
Requires public success and records edge headers.
tail domain-error.log
Reads site-specific proxy errors after activation.
Command
Fill variables0/1 ready

Values stay on this page and are never sent or saved.

sudo systemctl reload apache2 && curl --fail --head https://{{domain}}/ && curl --fail https://{{domain}}/health && sudo tail -n 20 /var/log/apache2/{{domain}}-error.log
Example output / evidence
HTTP/2 200
ok

Checkpoint: Checkpoint: reload

Continue whenHTTPS and health succeed through Apache, application context is correct, WebSocket handshake works, and forward-proxy requests fail.

Stop whenAny tenant, certificate, redirect, cookie, auth, scheme, log, latency, or negative-proxy result differs from baseline.

If this step fails

The application enters an HTTPS redirect loop.

Likely causeIt does not trust the edge scheme or multiple layers overwrite forwarding headers inconsistently.

Safe checks
  • curl -vkI --max-redirs 0 https://chat.example.com/
  • sudo grep -n RequestHeader /etc/apache2/sites-available/chat.example.com-proxy.inc

ResolutionDefine one trusted edge and one public base URL; remove duplicate header manipulation.

Security notes

  • Never use `curl -k` as the acceptance test; certificate failure is a real deployment blocker.

Alternatives

  • Canary the vhost on a second address and switch DNS/load-balancer traffic after the same probes pass.

Stop conditions

  • Any tenant, certificate, redirect, cookie, auth, scheme, log, latency, or negative-proxy result differs from baseline.
08

verification

Confirm failure is bounded and observable

caution

During a controlled window, stop the test backend, confirm Apache returns 502/503 quickly rather than hanging, restart it, and verify recovery.

Why this step matters

A proxy is operationally safe only when upstream absence fails quickly, visibly, and reversibly instead of tying up clients and Apache workers.

What to understand

Confirm the exact systemd service and dependent units before stopping it; use staging when shared dependencies make production interruption unsafe.

Measure HTTP status and total time with a 10-second client ceiling, then inspect edge and service logs.

Restart the backend, prove its direct health first, then public health and a WebSocket reconnect.

System changes

  • Temporarily stops and starts the named backend service, producing controlled 5xx and recovery evidence.

Syntax explained

systemctl stop/start
Controls the exact reviewed backend lifecycle.
curl --max-time 10
Bounds the client observation during deliberate failure.
--write-out
Records status and elapsed time without response-body ambiguity.
Command
Fill variables0/2 ready

Values stay on this page and are never sent or saved.

sudo systemctl stop {{backendService}} && curl --max-time 10 --output /dev/null --write-out '%{http_code} %{time_total}\n' https://{{domain}}/ || true; sudo systemctl start {{backendService}} && curl --fail https://{{domain}}/health
Example output / evidence
503 0.012
ok

Checkpoint: Checkpoint: failure test

Continue whenFailure returns a quick 502/503, Apache remains healthy, logs identify upstream refusal, and direct/public recovery succeeds.

Stop whenThe service has unreviewed dependents, Apache hangs, other tenants regress, restart fails, or recovery is incomplete.

If this step fails

The controlled stop causes unrelated applications to fail.

Likely causeThe backend unit or port is shared despite the inventory.

Safe checks
  • systemctl list-dependencies --reverse chat-app.service
  • sudo ss -lntp 'sport = :3000'

ResolutionRestart immediately, restore service, and redesign isolation before repeating.

Security notes

  • Do not weaken timeout or expose the backend to hide failure; preserve a private, observable boundary.

Alternatives

  • Inject the failure into an identical staging backend when production interruption is not authorized.

Stop conditions

  • The service has unreviewed dependents, Apache hangs, other tenants regress, restart fails, or recovery is incomplete.

Finish line

Verification checklist

Proxy safetysudo apache2ctl configtest && sudo grep -R '^\s*ProxyRequests Off' /etc/apache2/sites-enabledSyntax OK and no forward proxy is enabled.
Application healthcurl --fail https://{{domain}}/healthReturns the backend health marker through Apache.

Recovery guidance

Common problems and safe checks

Apache returns 502 immediately.

Likely causeThe backend is stopped, bound to another address/port, or the proxy fragment targets the wrong endpoint.

Safe checks
  • sudo ss -lntp 'sport = :3000'
  • systemctl status chat-app.service --no-pager
  • curl -v http://127.0.0.1:3000/health
  • sudo tail -n 100 /var/log/apache2/chat.example.com-error.log

ResolutionRepair the private service or exact upstream target; keep the listener private and retest directly before reloading Apache.

Requests hang until a client timeout.

Likely causeThe upstream accepts connections but blocks, response timeout is unsuitable, or the application waits on a dependency.

Safe checks
  • curl --max-time 10 -v http://127.0.0.1:3000/health
  • sudo journalctl -u chat-app.service -n 100 --no-pager
  • sudo ss -ntp 'dport = :3000'

ResolutionFix the blocked dependency or endpoint; change timeout only from measured legitimate behavior and capacity impact.

The application redirects to localhost or HTTP.

Likely causeIt does not trust the edge scheme/host context, generates absolute URLs from backend transport, or ProxyPassReverse does not match the upstream location.

Safe checks
  • curl -skI https://chat.example.com/
  • curl -sI http://127.0.0.1:3000/
  • sudo grep -nE 'ProxyPass|ProxyPreserveHost|RequestHeader' /etc/apache2/sites-available/chat.example.com-proxy.inc

ResolutionConfigure one documented proxy trust model and public base URL in the application; do not accept arbitrary client forwarding headers.

WebSocket handshake returns 200/404 instead of 101.

Likely causeThe catch-all route shadows `/socket/`, path differs, upgrade headers are stripped, or the backend does not support that endpoint.

Safe checks
  • sudo grep -n '^ProxyPass' /etc/apache2/sites-available/chat.example.com-proxy.inc
  • sudo apache2ctl configtest
  • sudo journalctl -u chat-app.service -n 100 --no-pager

ResolutionPut the exact socket path before `/`, prove the backend endpoint, then test with a WebSocket-capable client.

An external proxy test can reach arbitrary destinations.

Likely causeAnother included file enables `ProxyRequests On` or grants broad forward-proxy access.

Safe checks
  • sudo grep -R -n '^\s*ProxyRequests' /etc/apache2
  • sudo apache2ctl -t -D DUMP_RUN_CFG
  • sudo apache2ctl -S

ResolutionDisable forward proxying immediately, inspect access logs for abuse, restrict egress if necessary, and treat unexpected use as an incident.

After the procedure

Alternatives and next steps

Consider these alternatives

  • Serve the application directly only on a private network when public TLS, centralized policy, and shared ingress are unnecessary.
  • Use a Unix-domain socket for a compatible upstream to remove the loopback TCP listener.
  • Use a dedicated load balancer or service mesh when active health checks, multiple backends, circuit breaking, or fleet-level discovery are required.
  • On modern Apache, `upgrade=websocket` through mod_proxy_http is preferred for this mapping; retain mod_proxy_wstunnel only for documented compatibility needs.

Operate it safely

  • Add application-aware external probes for HTTP health and a real authenticated WebSocket handshake, with latency and failure alerts.
  • Correlate a sanitized request ID through Apache and application logs without trusting a client-provided ID blindly.
  • Define request-body/header limits, rate controls, WebSocket idle/message limits, maintenance behavior, and overload response.
  • For multiple replicas, introduce reviewed balancing, health, drain, retry, and session-state behavior rather than copying a single-backend route.

Reference

Frequently asked questions

Should ProxyPreserveHost always be On?

No. Use it when the application expects and validates the public host; otherwise configure an explicit upstream host and public base URL.

Why put the WebSocket mapping before `/`?

Apache evaluates mappings by configuration order; the catch-all root would otherwise consume that path first.

Does ProxyPassReverse rewrite HTML or JavaScript URLs?

No. It rewrites selected response headers such as Location; application-generated body content needs correct application proxy awareness.

Recovery

Rollback

Restore the saved virtual host and disable proxy modules only when no other site uses them.

  1. Restore {{domain}}.conf.before-proxy over the active site file.
  2. Run apache2ctl configtest, reload Apache, and verify the prior site response.
  3. Disable proxy modules only after apache2ctl -M and a configuration search prove they are unused elsewhere.
  4. Keep the backend bound to loopback throughout rollback; do not expose it as a shortcut.

Evidence

Sources and review

Verified 2026-07-25Review due 2026-10-23
Apache mod_proxyofficialApache HTTP Server 2.4 documentationofficialApache mod_headersofficial