Search code examples
node.jsapacheexpressdeployment

How can I run node and apache on the same server to allow the user to access api through https?


I am running Node and Apache on the same server, where node is the backend server, requested by Axios to collect user data from the front end.

I used Apache to request an SSL certificate through certbot and was successful. I am trying to deploy node backend to access my endpoint ie (website.com/endpoint).

I am able to see the test index.html, located in the website folder. When I try aws.website.com/endpoint I get the server time out and the 404 not found error.

The location of my app in the Linux server is var/www/website.com instead of the default var/www/html path.

My question: How can I run node and apache on the same server to allow the user to access the app through https?

***UPDATE: you need the node app to run on a separate port in my case 3001, and Apache to run on a separate port, ie 80, and use a reverse proxy via mod-proxy.

Here is the 000-default.conf file:

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://18.191.235.31/
    ProxyPassReverse / http://18.191.235.31/
        ServerName aws.website.com
        ServerAdmin example@localhost
        DocumentRoot /var/www/aws.backend-dg.com
</VirtualHost>

Here are my config files for the Apache virtual host: website/com.conf:

<VirtualHost *:80>
    ServerName aws.website.com
    ...
    DocumentRoot /var/www/aws.website.com
    ...
RewriteEngine on
RewriteCond %{SERVER_NAME} =aws.website.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

Here is my ‘website.com.le-ssl.conf’ (manually generated by certbot)`

<IfModule mod_ssl.c>
<VirtualHost *:443>
    ServerName aws.website.com
    ....
    DocumentRoot /var/www/aws.website.com
    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

SSLCertificateFile /etc/letsencrypt/live/website/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/website/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
</IfModule>

Here is my default-ssl.conf

<IfModule mod_ssl.c>
        <VirtualHost _default_:443>
            DocumentRoot /var/www/aws.backend-dg.com
            ....
            ErrorLog ${APACHE_LOG_DIR}/error.log
            CustomLog ${APACHE_LOG_DIR}/access.log combined
            ....
            SSLEngine on
            ....
            SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
            SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
            ....
        </VirtualHost>
</IfModule>

App.js file

const cors = require("cors");
const mongoose = require("mongoose");
const express = require("express");
const logger = require("morgan");
const app = express();
require("./models/Email");
const routes = require("./routes/routes");

app.use(cors());
app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Methods", "DELETE, PUT, GET, POST");
  res.header(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept"
  );
  next();
});
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Express only serves static assets in production
if (process.env.NODE_ENV === "production") {
  app.use(express.static("client/build"));
}
app.get('*',(req, res) => {
    res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
app.use(express.json());
app.use(logger("dev"));
app.use("/", routes);
app.use("/search", express.static("search"));

const mongoURI =
  "mongodb+srv://somename@cluster................";

const conn = mongoose.createConnection(mongoURI);

mongoose.connect(
  mongoURI,
  { useNewUrlParser: true },
  { useUnifiedTopology: true }
);

conn.once("open", () => {
  console.log("Connection Successful");
});

conn.on("error", console.error.bind(console, "MongoDB connection error:"));

 const server = app.listen(3001, () => {
 console.log(`Express running PORT ${server.address().port}`);
 });


const express = require("express");
const router = express.Router();
const emailController = require('../controllers/EmailController');

router.get('/', emailController.baseRoute);
router.post('back/get-email', emailController.createEmail);

module.exports = router;

Solution

  • The answer is to secure the node backend server in order for the user to stay on the secure HTTPS address. You do this by assigning SSL keys to the node server. Then edit the app.js file to connect to HTTPS, not HTTP. The above app.js connects to HTTP only. So this needed to be fixed. Finally, edit your reverse proxies in the Apache virtual hosts to fwd HTTP to HTTPS.

    In my case, the frontend was secure via HTTPS SSL certificates from let's encrypt (certbot) ie https://website.com, but the backend was not secure, so when I tried to go to https://websote/endpoint there was an error. The reason for this was because the backend app.js file connected to an HTTP server when it needed to connect to HTTPS.

    Note: if you have a secure website/app and you make an Axios request to a non-secure HTTP address then you will get a cors error stating you need to change HTTP to HTTPS. You do this by making the node.js server secure.

    Here is the breakdown:


    1a. Create a separate directory in the node folder to hold the certs. sudo mkdir directory_name

    1b. *Copy SSL certificates from the Apache server to the Node server directory.
    sudo cp etc/letsencrypt/live/your_webiste_folder/privkey.pem /var/www/directory_name
    sudo cp /etc/letsencrypt/live/your_website_folder/fullchain.pem /var/www/directory_name
    click here for guide about copying files and its contents

    1c Assign permission rules to that folder. I used ubuntu as my group because root had the ownership of the directory and the keys.
    click this link to create a group and learn about permissions
    -step a. change ownership of group that owns the key files
    -NOTE: you need to do this with root, type the following cmd: sudo -i
    chown group_name /var/path_to_webiste/cert_directory
    -step b. change ownership of keys in group
    chown group_name /var/path_to_website/privkey.pem,
    chown group_name /var/path_to_website/fullchain.pem,

    1d Check to see if permissions were applied successfully ls -l
    -Below 3rd line is an example of permissions of directory to root
    -Below 4th line is an example of permissions of directory to group_name (ubuntu). You want to see this line in your code.

    root@ip-123-45-67-890:/var/www# ls -l
    total 8
    drwxr-xr-x 6 root   root   4096 Oct 20 23:50 directory_name
    drwxr-xr-x 2 ubuntu ubuntu 4096 Oct 20 16:10 directory_name
    

    Click here to learn more


    2. Edit app.js or server.js to connect to HTTPS server, NOT HTTP
    Note: when you provide the location paths to the SSL certificates directories in the Node folder in app.js or server.js, Node will NOT be able to see them, this is why you need to assign permissions.

    App.js:

       //imports
       ......
        
        //use node over secure server
        const https = require("https"),
            fs = require("fs");
        
        const sslServer =  https.createServer (
        {
          key: fs.readFileSync('/var/path_to_privkey.pem'),
          cert: fs.readFileSync('/var/path_to_fullchain.pem'),
        },
        app  <---MAKE SURE TO PASS APP IN
        )
        
       ......
       ......
       
    sslServer.listen(3000, () => {
      console.log(`Express running PORT 3000 working!`)
    });
    

    Click here for a walkthrough process for above app.js code

    1. Assign firewall permission rules to allow the port you are using from the app.js file ie 3001 to be allowed through the command ufw.
      -note: if you try to connect to the port and you get a timeout, then your firewall settings for apache are not allowing the port to come through, this is why you need to edit the apache firewall permissions sudo ufw allow 3001

    2. Edit 000-default.conf, website.com.conf, and website.com-le-ssl files to fwd from HTTP to HTTPS. -note: website.com.conf-le-ssl is generated by certbot from letsencrypt

    000-default.conf:

    <VirtualHost *:80>
            ServerName website.com
            ServerAlias www.website.com
            DocumentRoot /var/www/website.com
    
            ProxyPreserveHost On
            ProxyRequests Off
            ProxyPass /get-email https://1.234.567.890:3001/
            ProxyPassReverse /get-email https://1.234.567.890:3001/
    
            ErrorLog ${APACHE_LOG_DIR}/error.log
            CustomLog ${APACHE_LOG_DIR}/access.log combined
    </VirtualHost>
    

    website.com.conf:

    <VirtualHost *:80>
        ServerName website.com
        ServerAlias www.website.com
        DocumentRoot /var/www/aws.backend-dg.com
    
        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined
    
        ProxyPreserveHost Off
        ProxyRequests Off
        ProxyPass / http://1.234.567.890:3001/
        ProxyPassReverse / https://1.234.567.890:3001/
    
        RewriteEngine on
        RewriteCond %{SERVER_NAME} =aws.backend-dg.com [OR]
        RewriteCond %{SERVER_NAME} =3.144.239.224:3001 [OR]
        RewriteCond %{SERVER_NAME} =aws.backend-dg.com:3001 [OR]
        RewriteCond %{SERVER_NAME} =www.aws.backend-dg.com
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
    </VirtualHost>
    

    website.com-le-ssl.conf:

    <IfModule mod_ssl.c>
    <VirtualHost *:443>
      ServerName website.com
      ServerAlias www.website.com
      DocumentRoot /var/www/website.com
    
      SSLProxyEngine On
      ProxyPass /get-email http://1.234.567.890:3001/
      ProxyPassReverse /get-email https://1.234.567.890:3001/
      ProxyPreserveHost Off
      ProxyRequests Off
    
      SSLEngine On
      SSLCertificateFile /etc/letsencrypt/live/website.com/fullchain.pem
      SSLCertificateKeyFile /etc/letsencrypt/live/website.com/privkey.pem
    </VirtualHost>
    </IfModule>