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