October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
VGSources
Blog

How to Run Node.js Behind Apache on Ubuntu (Reverse Proxy and WebSockets)

A practical Ubuntu guide to placing Node.js behind Apache: build the reverse-proxy virtual host, keep ProxyRequests disabled, map HTTP and WebSocket paths correctly, validate safely, and diagnose common failures.
Length10 min Posted Quest giverVGSources Team
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Run your Node.js application on a private loopback port, then let Apache accept public HTTP(S) traffic and reverse-proxy requests to that process. The essential configuration is ProxyRequests Off, a ProxyPass route, and a matching ProxyPassReverse route. Apache 2.4.47 and later can tunnel WebSockets through mod_proxy_http with upgrade=websocket; older releases require mod_proxy_wstunnel.

What “Node.js behind Apache” means

Node.js still runs the application server. Apache does not execute your JavaScript; it receives requests on the public address, selects a virtual host, and forwards each request to Node on an internal address such as 127.0.0.1:3000. The response travels back through Apache to the browser.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

This arrangement gives one public web server a stable place to handle virtual hosts, TLS termination, access logging, compression, and static files while each Node application listens on its own local port. Binding the application to loopback also prevents clients from bypassing Apache and reaching the Node listener directly. Use the exact interface and port your application is configured to use; the examples below use 127.0.0.1:3000.

Request flow

  1. A browser requests http://example.com/.
  2. Apache matches ServerName example.com in the virtual host.
  3. ProxyPass forwards the path to http://127.0.0.1:3000/.
  4. Node generates the response, and ProxyPassReverse adjusts redirect-related response headers before Apache returns the response to the browser.

ProxyPassReverse rewrites the Location, Content-Location, and URI response headers. It does not rewrite links embedded in an HTML body. If an application emits absolute links that point at its internal hostname or port, use the application’s public URL settings or add the separately provided mod_proxy_html solution rather than assuming ProxyPassReverse will edit the page.

Before you configure Apache

Confirm the application and port

Start the Node application locally and verify that it answers on the intended port before introducing Apache. A request made on the server should return your application’s normal response:

curl -i http://127.0.0.1:3000/

If this fails, fix the Node process first. A reverse proxy cannot compensate for a crashed process, a different listening port, or an application bound only to an address Apache cannot reach.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose a loopback listener

Configure the application to listen on 127.0.0.1 (or another deliberately restricted local interface) and the selected port. Apache must be able to connect to that address, while outside clients should use Apache’s public address. Keep the port consistent in every place it appears: the Node startup command, the ProxyPass target, health checks, and any process manager configuration.

Identify the Apache release

WebSocket configuration depends on Apache’s version. Check the installed version:

apache2ctl -v
Apache version WebSocket approach Important detail
2.4.47 and later mod_proxy_http with upgrade=websocket Protocol Upgrade tunnelling is handled by the HTTP proxy module; mod_proxy_wstunnel is deprecated.
2.4.46 and earlier mod_proxy_wstunnel Use a ws:// or wss:// route for the WebSocket path.

Enable the Apache proxy modules

Ubuntu’s Apache package keeps many modules disabled until you enable them. The usual module set for an HTTP reverse proxy is proxy and proxy_http; add proxy_wstunnel when an older Apache release needs it. The a2enmod command changes the local Apache configuration, so use the module names available in your Ubuntu package.

sudo a2enmod proxy
sudo a2enmod proxy_http
# Apache 2.4.46 and earlier, when WebSockets are required:
sudo a2enmod proxy_wstunnel

Reload only after validating the complete configuration. If a module name is unavailable, inspect the package’s enabled-module directory and the Apache error output rather than copying a directive from a different release.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Create the reverse-proxy virtual host

Minimal HTTP configuration

Create or edit the site’s virtual-host file under Ubuntu’s Apache configuration directory. Replace the example hostname with the DNS name that points to this server.

<VirtualHost *:80>
    ServerName example.com

    ProxyRequests Off
    ProxyPreserveHost Off

    ProxyPass        "/" "http://127.0.0.1:3000/"
    ProxyPassReverse "/" "http://127.0.0.1:3000/"
</VirtualHost>

ProxyRequests Off is a security boundary: it keeps Apache in reverse-proxy mode instead of acting as a forward proxy for arbitrary clients. Apache’s mod_proxy documentation explicitly states, “It is not necessary to turn ProxyRequests on in order to configure a reverse proxy.” Do not enable it as a way to make reverse proxying work.

Understand ProxyPreserveHost

The directive defaults to Off, and Apache documentation says it should normally remain off. With the default, the backend receives a host value appropriate to the proxy connection. Set ProxyPreserveHost On only when the application or a proxied name-based virtual host specifically requires the browser’s original Host header. Changing it can affect tenant selection, URL generation, and host-based security checks, so make the choice deliberately rather than treating it as a universal Node.js requirement.

Match trailing slashes

Keep the slash convention identical on both sides of each mapping. If the first argument ends with /, the backend argument should also end with /; if one omits it, omit it from the other as well. The matching form in the example maps every path below the public root without accidentally joining path segments.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Enable the site and test it

On Ubuntu, site files are commonly enabled with a symbolic link through a2ensite. Use the filename you actually created:

sudo a2ensite example.com.conf
sudo apache2ctl configtest

Proceed only when the command reports a successful syntax check. Then reload Apache so existing connections are treated as gracefully as the service permits:

sudo systemctl reload apache2

Request the public hostname and compare it with the direct local request:

curl -i http://example.com/

A response from the Node application confirms the basic route. A default Apache page, a “503 Service Unavailable” response, or a connection refusal points to a different stage of the setup; use the troubleshooting section below instead of adding random proxy directives.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Configure WebSockets correctly

Apache 2.4.47 and newer

For current Apache releases, add an Upgrade-aware route using mod_proxy_http. The route’s public path must match the path used by the client and by the Node WebSocket server:

<VirtualHost *:80>
    ServerName example.com

    ProxyRequests Off
    ProxyPreserveHost Off

    ProxyPass        "/ws/" "http://127.0.0.1:3000/ws/" upgrade=websocket
    ProxyPassReverse "/ws/" "http://127.0.0.1:3000/ws/"

    ProxyPass        "/" "http://127.0.0.1:3000/"
    ProxyPassReverse "/" "http://127.0.0.1:3000/"
</VirtualHost>

The WebSocket mapping appears before the catch-all HTTP mapping. Apache’s documentation gives this ordering for mixed traffic: the more specific WebSocket route is evaluated first, then the general HTTP route handles everything else.

The upgrade=websocket parameter tells Apache to tunnel the protocol Upgrade request. It does not create a WebSocket endpoint in Node; your application must already accept connections at /ws/.

Apache 2.4.46 and earlier

Older releases use mod_proxy_wstunnel. Enable that module and map the WebSocket path with a WebSocket backend URL:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
<VirtualHost *:80>
    ServerName example.com

    ProxyRequests Off
    ProxyPreserveHost Off

    ProxyPass        "/ws/" "ws://127.0.0.1:3000/ws/"
    ProxyPassReverse "/ws/" "ws://127.0.0.1:3000/ws/"

    ProxyPass        "/" "http://127.0.0.1:3000/"
    ProxyPassReverse "/" "http://127.0.0.1:3000/"
</VirtualHost>

Do not blindly keep an old mod_proxy_wstunnel recipe on a newer server: that module is deprecated, and Apache 2.4.47 introduced HTTP-proxy Upgrade handling as the preferred path. The directive ProxyWebsocketFallbackToProxyHttp defaults to On; it controls whether the WebSocket tunnel module hands eligible requests to mod_proxy_http.

Use the correct public scheme

If Apache terminates TLS, the browser connects with wss://example.com/ws/ while Apache can connect to Node over ws://127.0.0.1:3000/ws/ on the private side. If TLS is passed through to a TLS-speaking backend instead, use the corresponding wss:// target and certificate arrangement. Keep the public and backend schemes conceptually separate: the browser-facing scheme is not automatically the same as the local hop.

Path mapping and application URLs

Root-mounted application

The root mapping shown above sends /assets/app.js to the backend as /assets/app.js. This is the least surprising layout when the Node application was built to run at the site root.

Application mounted below a prefix

You can expose an app below a prefix, but the application must know its public base path. For example:

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
ProxyPass        "/game/" "http://127.0.0.1:3000/"
ProxyPassReverse "/game/" "http://127.0.0.1:3000/"

With this mapping, Apache removes the public /game/ prefix before forwarding. The app must generate links, redirects, cookie paths, and WebSocket URLs that work under /game/; otherwise requests for scripts or sockets may appear to “randomly” fall outside the proxy route. If changing the app’s base URL is impossible, map the backend path so that the path it expects remains intact and test every redirect.

Redirects and absolute links

When Node responds with a redirect, ProxyPassReverse can rewrite the relevant response headers so the client sees the public address. It cannot alter an absolute URL written inside an HTML document or JavaScript bundle. Configure the application’s public origin first. Consider mod_proxy_html only for a deliberate HTML-rewriting design, because rewriting generated content can affect scripts, caching, and security policies.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Run the Node process reliably

Apache can proxy only while the Node process is running. For production, use a process supervisor and run the application as a non-root account with a controlled environment. A typical systemd unit is a deployment pattern rather than a requirement of Apache:

[Unit]
Description=Node application
After=network.target

[Service]
Type=simple
User=nodeapp
WorkingDirectory=/srv/nodeapp
Environment=NODE_ENV=production
ExecStart=/usr/bin/node /srv/nodeapp/server.js
Restart=always

[Install]
WantedBy=multi-user.target

Adjust the Node executable path, working directory, account, and startup file to your installation. After placing a unit in the systemd service directory, reload systemd, enable the service, and start it. Verify the service’s local response before reloading Apache. Keep secrets out of the unit file when your deployment platform offers a safer environment-file or secret-management mechanism.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Validate the complete setup

Check each layer independently

  1. Check the Node process status with your chosen supervisor.
  2. Request the backend directly from the server: curl -i http://127.0.0.1:3000/.
  3. Run sudo apache2ctl configtest and correct every syntax error.
  4. Reload Apache, then request the public hostname with curl -i.
  5. Test a known redirect and confirm its Location header uses the public host and path.
  6. Open the application’s WebSocket feature and inspect the browser network panel for a successful protocol upgrade.

Read the right logs

When Apache returns an error, inspect the Apache error log at the same time as the Node process log. A proxy connection-refused message usually means Node is stopped, listening on another port, or bound to an inaccessible address. A successful HTTP response followed by a WebSocket disconnect usually indicates a route, Upgrade handling, path, or application-level handshake problem. The Apache error log is the first place to look for module-loading and proxy-connection diagnostics.

Troubleshooting by symptom

Apache reports a syntax error

  • Run apache2ctl configtest again to capture the exact file and line.
  • Check quote characters, closing </VirtualHost> tags, and directive spelling.
  • Confirm that every directive’s module is enabled and available in this Apache build.
  • Only reload after the test succeeds.

The public request shows Apache’s default page

  • Confirm DNS resolves to this server and that the request’s hostname matches ServerName.
  • Make sure the intended site is enabled and another virtual host is not winning the match.
  • Reload Apache after enabling or editing the site.

Apache returns 502 or 503

  • Run the direct curl request against 127.0.0.1:3000.
  • Compare the configured port with the Node startup configuration.
  • Check whether Node is listening on loopback IPv4 while Apache is attempting a different address.
  • Read the Apache error log and the Node supervisor log for the same timestamp.

Redirects point to the internal port

  • Keep ProxyPassReverse paired with every ProxyPass mapping.
  • Check the application’s external URL, host, and protocol settings.
  • Remember that ProxyPassReverse changes response headers, not HTML body links.

WebSockets return 404, 302, or disconnect

  • Verify that the client path exactly matches the WebSocket ProxyPass path.
  • On Apache 2.4.47 or newer, use upgrade=websocket with the HTTP proxy module.
  • On Apache 2.4.46 or earlier, enable and use mod_proxy_wstunnel.
  • Place the WebSocket mapping before the catch-all / HTTP mapping.
  • Check that the Node server actually accepts WebSocket upgrades at that path and is not redirecting them to a login or canonical URL.
  • Inspect Apache’s error log while reproducing the failure; a direct Node connection working does not prove the proxied route is correct.

Security and maintenance decisions

Do not create a forward proxy

Leave ProxyRequests Off. Reverse proxying selects fixed backend destinations from your configuration; forward proxying accepts client-supplied destinations and can expose the server to abuse.

Keep the backend private

Use loopback binding when the application is intended to be reached only through Apache. If another local service must connect to Node, choose an explicit private interface and firewall policy rather than exposing the application listener to the public network by default.

Preserve the original host only when required

Start with ProxyPreserveHost Off. Turn it on for a documented application requirement, then test host-based routing, redirects, cookies, and any allow-list logic that depends on the incoming host.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep routes specific

Put specific paths such as /ws/ before a root catch-all. Use matching trailing slashes, pair forward and reverse mappings, and document which Node process owns each port. These small conventions prevent an apparently valid request from being sent to the wrong handler.

Configuration checklist

  • Node answers on the chosen local address and port.
  • The public DNS name matches the Apache virtual host.
  • proxy and proxy_http are enabled.
  • ProxyRequests Off is present.
  • ProxyPreserveHost is left off unless the application needs the original host.
  • ProxyPass and ProxyPassReverse use the same public and backend paths, including trailing-slash style.
  • The WebSocket route precedes the HTTP catch-all and uses the syntax appropriate to the Apache version.
  • apache2ctl configtest succeeds before every reload.
  • Direct backend, public HTTP, redirects, and WebSocket upgrades have each been tested.
  • Apache and Node logs are available for the next failure.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More quests from Patch Notes

  1. How to Set Up a RedM (RDR2) Server in 2026: License Key, txAdmin and server.cfg, Step by StepBlog11min
  2. Best RedM (RDR2) Server Hosting in 2026: Comparing Four Hosts on Slots, Memory and PriceBlog10min
  3. How to Host a Mindustry Server in 2026: server-release.jar, Port 6567 and the Commands That MatterBlog7min
Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.