Skip to main content

Setup Apache2 with Nodejs & Reverse Proxy

Welcome, this document serves as a guide on how to setup a Node.js application with an already established Apache2 server using a reverse proxy to defer specific URL requests to the Node.js application. For the purposes of this guide we will be using an Ubuntu Server.

Requirements

  • A pre-established Apache2 Server
  • An owned Domain Name or Sub-Domain for the Node.js application to point to

Installing Node.js on Ubuntu

Once connected to the Host Server, use the following to download and install nvm:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash

To avoid restarting the shell, use the following:

\. "$HOME/.nvm/nvm.sh"

Download and install Node.js:

nvm install 23

To verify the Node.js version use:

node -v

Alternatively you may use:

nvm current

To verify the npm version:

npm -v

Create a Nodejs Application

First, create the directory:

mkr project_name

Go to the directory and create a nodeapp.js file:

cd project_name
nano nodeapp.js

Configure nodeapp.js to the following:

const http = require('http');
const hostname = 'host_server_ip';
const port = 3000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Welcome to Node.js!\n');
});

server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

To run the nodejs application use the following:

node nodeapp.js

Test the application using the URL output from nodejs, it will look like:

Server running at http://host_server_ip:3000/

Setup pm2

Install pm2 with the following:

npm install pm2@latest -g

pm2 is a Process Management package that is used to provide utility when launching node applications. By using pm2 there is no need to setup the node application being created as a system service in order to monitor or ensure the application is running.

Use npm2 to launch a node application with:

pm2 start nodeapp.js

To set the start up script for pm2:

pm2 startup

pm2 Process Management

Reload application:

pm2 reload app_name

Stop Application:

pm2 stop app_name

Restart Application:

pm2 restart app_name

Delete Application:

pm2 delete app_name

Configure Apache2 Reverse Proxy to Point to Nodejs Application

Once the nodejs application is launched and running as expected, adjust the Virtual Host configuration file under /etc/apache2/sites-available/your-website.com.conf to contain the following:

<VirtualHost *:80>
...

# Proxy directives to forward traffic to a backend
ProxyRequests Off
ProxyPreserveHost On
ProxyVia Full

# Allow proxying
<Proxy *>
Require all granted
</Proxy>

# ProxyPass and ProxyPassReverse to forward requests to the backend
ProxyPass "/api" http://127.0.0.1:3000
ProxyPassReverse "/api" http://127.0.0.1:3000

...
</VirtualHost>

Please ensure that the port associated with ProxyPass & ProxyPassReverse should match the port that the nodejs application is hosted on.

Enable the required proxy modules for Apache2:

a2enmod proxy proxy_http rewrite headers expires

Restart the Apache2 service:

systemctl restart apache20

When visiting http://yourdomain/api, the website will now redirect to the nodejs application. Additional steps may be taken to customise the application such as running an Express API to serve JSON Requests with Basic Authentication or configuring an SSL for the nodejs application to allow for a more secure network.

Create Express API to Serve JSON Requests

Create a new directory for a nodejs application, or inside the root directory for a pre-existing nodejs application use the following to install the Express Package:

npm install express

In addition to this, install the basic authentication package:

npm install express-basic-auth

Configure the node application .js file to the following:

const express = require('express');  
const basicAuth = require('express-basic-auth');
const fs = require('fs');
const path = require('path');
const hostname = 'localhost';
const app = express();
const port = 3000;

// Basic Authentication middleware
app.use(basicAuth({
users: { 'username': 'password' }, // Change these to your preferred username and password
challenge: true, // This prompts the user for authentication
realm: 'Protected Area' // The name of the authentication prompt
}));

// Serve JSON files
app.get('/data/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(__dirname, 'data', `${filename}.json`); // Assuming JSON files are in a 'data' folder

// Check if file exists fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return res.status(404).send({ error: 'File not found' });
}

try {
const jsonData = JSON.parse(data); // Parse and send the JSON data
res.json(jsonData);
} catch (parseErr) {
res.status(500).send({ error: 'Error parsing JSON file' });
}
});
});

// Start the server
app.listen(port, () => {
console.log(`Server is running on http://${hostname}:${port}`);
});

Within the root of the nodejs application directory, add the following directory:

mkdir data

Create the file demo.json within the /data directory:

nano data/demo.json

The json data may be any sample data, such as:

@root/data
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}

Using pm2, start the nodejs application and if the applications running port on the local machine is set as the reverse proxy location then visiting the website for the domain with /api will now reach the nodejs application above and prompt the user for credentials if visiting via a browser.

Alternatively, this URL may be used to fetch data using a request. The following may be used as an example of how to fetch json using Basic Authentication from the hosted URL using JavaScript:

const username = 'username';  
const password = 'password';
const site_url = 'http://example.com';

// Create the Base64-encoded credentials
const credentials = btoa(`${username}:${password}`);

fetch('site_url', {
method: 'GET', // or 'POST', 'PUT', etc.
headers: {
'Authorization': `Basic ${credentials}`,
'Content-Type': 'application/json', // Optional: if you're sending JSON data
},
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // Parse the response as JSON
})
.then(data => {
console.log('Data received:', data); // Handle the data
})
.catch(error => {
console.error('Error with the fetch operation:', error);
});

There are still additional improvements that can be made, and this configuration while adequate for testing purposes would is not suitable for large scale deployment due to the password being contained within the application code directly for the purpose of this document.

In order to ensure that any application is deployable to a larger scale, the following would be required to finalise the setup:

  • SSL Encryption for the Node.js Application
  • Create a Node.js Application that uses Express.js & MongoDB to provide Secure User Registration